3

I'm having a couple of entity with tags. I've created a dynamic relationship based in this blog post:

class TaggableListener implements EventSubscriber
{
    /**
     * @return array
     */
    public function getSubscribedEvents()
    {
        return [
            Events::loadClassMetadata
        ];
    }

    /**
     * @param \Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs
     */
    public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
    {
        // the $metadata is the whole mapping info for this class
        $metadata = $eventArgs->getClassMetadata();

        $class = $metadata->getReflectionClass();
        if (!$class->implementsInterface(TaggableEntityInterface::class)) {
            return;
        }

        $namingStrategy = $eventArgs
            ->getEntityManager()
            ->getConfiguration()
            ->getNamingStrategy()
        ;

        $metadata->mapManyToMany([
            'targetEntity'  => Tag::class,
            'fieldName'     => 'tags',
            'cascade'       => ['persist'],
            'joinTable'     => [
                'name'        => $namingStrategy->classToTableName($metadata->getName()) . '__Tags',
                'joinColumns' => [
                    [
                        'name'                  => $namingStrategy->joinKeyColumnName($metadata->getName()),
                        'referencedColumnName'  => $namingStrategy->referenceColumnName(),
                        'onDelete'  => 'CASCADE',
                        'onUpdate'  => 'CASCADE',
                    ],
                ],
                'inverseJoinColumns'    => [
                    [
                        'name'                  => 'tag_id',
                        'referencedColumnName'  => $namingStrategy->referenceColumnName(),
                        'onDelete'  => 'CASCADE',
                        'onUpdate'  => 'CASCADE',
                    ],
                ]
            ]
        ]);
    }
}

interface TaggableEntityInterface
{
    public function addTag(Tag $tags);
    public function removeTag(Tag $tags);
    public function getTags();
}

trait Tags
{
    protected $tags;

    public function addTag(Tag $tags) { /*..*/ };
    public function removeTag(Tag $tags) { /*..*/ };
    public function getTags() { /*..*/ };
}

I can use $category->getTags() or $product->getTags() and get all tags. This works great. It's an old blog post, but apparently not outdated.

But since there is no Tag::getProducts() method, there is no easy way to get all products related to a tag.

Ideally, I want a Tag::getRelatedEntities() which returns TaggableEntityInterface[], so the Collection contains both Product and Category entities.

In my template, I'm thinking of something like this:

<h1>Related entities:</h1>
<ul>
{% for relatedEntity in tag.getRelatedEntities %}
    <li>{{ relatedEntity }}</li> //..implementing `__toString()`
{% endfor %}
</ul>

What's the best way to get a Tag::getRelatedEntities() and/or Tag::getProducts() method?

Of course I can manually create a getProducts method on my Tag entity, but that kind of breaks the idea of a dynamic relationship. In this example I've only got a Product and Category entity, but in reality I've got many entities which are Taggable.

Stephan Vierkant
  • 9,674
  • 8
  • 61
  • 97

1 Answers1

3

You cannot create single relationship in Doctrine pointing to several different Entities. In other words, if Tag would have relatedEntities, it would have to be array of objects of the same class or at least mapped superclass.

What you could do, is to write a service that would iterate over all Doctrine managed classes, check if it implements given interface, search those entities by tag and merge them all into a single array.

Example code:

$entities = [];
foreach ($entityManager->getMetadataFactory()->getAllMetadata() as $classMetadata) {
    if (in_array(TaggableEntityInterface::class, class_implements($classMetadata->getName()))) {
        $repository = $entityManager->getRepository($classMetadata->getName());
        $entities = array_merge($entities, $repository->findBy(['tag' => $tag]));
    }
}

You could also wrap this up into twig function if you want to easily access this functionality from your templates.

The code was not really tested, so it might need some corrections or improvements depending on your use-case.

Stephan Vierkant
  • 9,674
  • 8
  • 61
  • 97
Marius Balčytis
  • 2,601
  • 20
  • 22