动态学说关系的反面方法

时间:2017-12-21 08:37:23

标签: php symfony doctrine-orm doctrine

我有几个带标签的实体。我已根据此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() { /*..*/ };
}

我可以使用$category->getTags()$product->getTags()获取所有代码。这非常有效。这是一篇很老的博文,但显然没有过时。

但由于没有Tag::getProducts()方法,因此没有简单的方法可以获得与标签相关的所有产品。

理想情况下,我希望Tag::getRelatedEntities()返回TaggableEntityInterface[],因此该集合包含ProductCategory个实体。

在我的模板中,我想到了这样的事情:

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

获得Tag::getRelatedEntities()和/或Tag::getProducts()方法的最佳方法是什么?

当然,我可以在我的Tag实体上手动创建getProducts方法,但这种方法打破了动态关系的想法。在这个例子中,我只有一个产品和类别实体,但实际上我有许多实体是Taggable。

1 个答案:

答案 0 :(得分:2)

您无法在Doctrine中创建指向多个不同实体的单一关系。换句话说,如果Tag具有relatedEntities,则它必须是同一类的对象数组或至少是映射的超类。

你能做的是编写一个迭代所有Doctrine托管类的服务,检查它是否实现了给定的接口,按标签搜索这些实体并将它们全部合并到一个数组中。

示例代码:

$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]));
    }
}

如果您想从模板轻松访问此功能,也可以将其包装成twig函数。

代码未经过实际测试,因此根据您的使用情况,可能需要进行一些更正或改进。