Skip to content

Instantly share code, notes, and snippets.

@Taluu
Last active August 29, 2015 14:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Taluu/2b07700c99edcd8ef8aa to your computer and use it in GitHub Desktop.
Save Taluu/2b07700c99edcd8ef8aa to your computer and use it in GitHub Desktop.
<?php
namespace Acme\TagBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM,
Doctrine\Common\Collections\ArrayCollection;
/**
* Tag Entity
*
* @ORM\Entity(repositoryClass="Acme\TagBundle\Entity\TagRepository")
* @ORM\Table(uniqueConstraints={@ORM\UniqueConstraint(name="hash_idx", columns={"hash"})})
*/
class Tag
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @Assert\NotNull
* @Assert\NotBlank
*
* @ORM\Column(type="string", nullable=false)
*/
private $name;
/**
* @var TagRelation[]
*
* @ORM\OneToMany(targetEntity="Acme\TagBundle\Entity\TagRelation", mappedBy="tag", cascade={"persist", "merge", "remove"}, orphanRemoval=true)
*/
private $relations;
/** @var TaggableInterface[] */
private $assignees;
public function __construct($name = null)
{
$this->name = $name;
$this->assignees = new ArrayCollection;
$this->relations = new ArrayCollection;
}
public function getId()
{
return $this->id;
}
/** @return string */
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
public function getAssignees()
{
if (null === $this->assignees) {
$this->assignees = new ArrayCollection;
}
return $this->assignees;
}
public function hasAssignment(TaggableInterface $object)
{
return $this->getAssignees()->contains($object);
}
public function unassign(TaggableInterface $object)
{
if ($this->hasAssignment($object)) {
$this->getAssignees()->removeElement($object);
}
return $this;
}
public function assign(TaggableInterface $object)
{
if (!$this->hasAssignment($object)) {
$this->getAssignees()->add($object);
}
return $this;
}
public function getRelations()
{
return $this->relations;
}
public function addRelation(TagRelation $relation)
{
$this->getRelations()->add($relation);
return $this;
}
public function removeRelation(TagRelation $relation)
{
$this->getRelations()->removeElement($relation);
return $this;
}
}
<?php
namespace Acme\TagBundle\Entity;
use OutOfBoundsException;
/**
* Interface saying that an object can have tags
*
* Should be used with the TagTrait
*/
interface TaggableInterface
{
/** @return integer */
public function getId();
/** @return Tag[] */
public function getTags();
/** Set the collection of tags of this object */
public function setTags($tags);
/** Add a tag into this object */
public function addTag(Tag $tag);
/** Remove the tag from this object */
public function removeTag(Tag $tag);
/**
* Checks if the tag $name is assigned to this object
*
* @param string $name Tag's name
* @return boolean true if it exists, false otherwise
*/
public function hasTag($name);
/**
* Get the tag named $name
*
* @param string $name Tag's name
*
* @return Tag
* @throws OutOfBoundsException If the tag is not affiliated to this object
*/
public function getTag($name);
/**
* Checks if the tags are synchronized with this object
*
* This checks that all tags elements in the tags collection is a Tag
* If there is something else, it is considered as not synchronized quite yet
*
* @return boolean
*/
public function areTagsSynchronized();
}
<?php
namespace Acme\TagBundle\Entity;
use Doctrine\ORM\Mapping as ORM,
Doctrine\ORM\Proxy\Proxy;
/**
* Tag Relation Class
*
* @ORM\Entity
* @ORM\Table(indexes={@ORM\Index(name="object_idx", columns={"object_class", "object_id"})})
*/
class TagRelation
{
/**
* @ORM\Id
* @ORM\Column(type="string", name="object_class")
*/
private $objectClass;
/**
* @ORM\Id
* @ORM\Column(type="integer", name="object_id")
*/
private $objectId;
/**
* @var Tag
*
* @ORM\Id
* @ORM\ManyToOne(targetEntity="Acme\TagBundle\Entity\Tag", inversedBy="relations")
*/
private $tag;
public function __construct(Tag $tag, TaggableInterface $object)
{
$this->tag = $tag;
$this->setRelatedObject($object);
}
/** @var Tag */
public function getTag()
{
return $this->tag;
}
public function setRelatedObject(TaggableInterface $object)
{
$this->objectId = $object->getId();
$this->objectClass = $object instanceof Proxy
? get_parent_class($object)
: get_class($object);
return $this;
}
/**
* Checks if the given object matches what is in this relation
*
* @return boolean true if it is the same object, false otherwise
*/
public function matches(TaggableInterface $object) {
$class = $object instanceof Proxy
? get_parent_class($object)
: get_class($object);
return $object->getId() === $this->objectId && $class === $this->objectClass;
}
/**
* Get objectClass
*
* @return string
*/
public function getObjectClass()
{
return $this->objectClass;
}
/**
* Get objectId
*
* @return integer
*/
public function getObjectId()
{
return $this->objectId;
}
}
<?php
namespace Acme\TagBundle\Entity;
use Traversable,
OutOfBoundsException,
InvalidArgumentException;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Trait that implements the gist of the tags
*
* BEWARE : You _must_ define a `tags` property in the class you're using this trait !
*/
trait TagTrait
{
/** {@inheritDoc} */
public function getTags()
{
if (null === $this->tags) {
$this->tags = new ArrayCollection;
}
return $this->tags;
}
/** {@inheritDoc} */
public function setTags($tags)
{
if (!$tags instanceof Traversable && !is_array($tags)) {
throw new InvalidArgumentException('Traversable or array expected, had ' . (is_object($tags) ? get_class($tags) : gettype($tags)));
}
$this->tags = new ArrayCollection;
foreach ($tags as $tag) {
$this->addTag($tag);
}
return $this;
}
/** {@inheritDoc} */
public function addTag(Tag $tag)
{
if (!$tag->hasAssignment($this)) {
$tag->assign($this);
}
/**
* Check if the given Tag already exists in the object's tags collection.
* We don't use `ArrayCollection::contains()` because it's not be able to
* compare the empty object from a FormType and a complete Tag.
*/
if (!$this->getTags()->exists(function ($key, $element) use ($tag) { return $tag->getHash() === $element->getHash(); })) {
$this->getTags()->add($tag);
}
return $this;
}
/** {@inheritDoc} */
public function removeTag(Tag $tag)
{
if ($tag->hasAssignment($this)) {
$tag->unassign($this);
}
$this->getTags()->removeElement($tag);
return $this;
}
/** {@inheritDoc} */
public function hasTag($hash)
{
foreach ($this->getTags() as $tag) {
if ($hash !== $tag->getHash()) {
continue;
}
return true;
}
return false;
}
/** {@inheritDoc} */
public function getTag($hash)
{
if (!$this->getTags()->exists(function ($key, Tag $tag) use ($hash) { return $hash === $tag->getHash(); })) {
throw new OutOfBoundsException(sprintf('The tag "%s" is not assigned to this object ("%s":%d)', $hash, get_class($this), $this->getId()));
}
return $this->getTags()->filter(function (Tag $tag) use ($hash) { return $hash === $tag->getHash(); })->first();
}
/** {@inheritDoc} */
public function areTagsSynchronized()
{
if ($this->getTags()->isEmpty()) {
return false;
}
return !$this->getTags()->exists(function ($key, $tag) { return !$tag instanceof Tag; });
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment