与Doctrine ORM、Doctrine PHPCR-ODM和事件侦听器的循环依赖关系
-
21-12-2019 - |
题
我正在研究一个复杂的Symfony项目,该项目将Doctrine ORM对象与Doctrine PHPCR-ODM文档混合在一起。一切正常,但我一直无法解决容器中侦听器之间的循环依赖注入问题。
场景是,我有多个ODM文档在加载时设置ORM引用,这是通过事件侦听器完成的。一个示例配置是:
services.yml
:
example.event_listener.my_document:
class: Example\Common\EventListener\MyDocumentEventListener
arguments: [@doctrine]
tags:
- { name: doctrine_phpcr.event_listener, event: postLoad }
- { name: doctrine_phpcr.event_listener, event: prePersist }
Example\Common\EventListener\MyDocumentEventListener.php
:
namespace Example\Common\EventListener;
use Example\Common\ODM\Document\MyDocument;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ODM\PHPCR\DocumentManager;
/**
* Listener for {@link Example\Common\ODM\Document\MyDocument} events.
*/
class MyDocumentEventListener
{
/*
* @var Doctrine\Common\Persistence\ManagerRegistry
*/
private $managerRegistry;
/**
* Constructor.
*
* @param Doctrine\Common\Persistence\ManagerRegistry $documentManager A Doctrine {@link Doctrine\Common\Persistence\ManagerRegistry}.
*/
public function __construct(ManagerRegistry $managerRegistry)
{
$this->managerRegistry = $managerRegistry;
}
/**
* After loading a document, ensure that the references exist
* to each ORM dependency.
*
* @param Doctrine\Common\Persistence\Event\LifecycleEventArgs $args
*/
public function postLoad(LifecycleEventArgs $args)
{
if (get_class($args->getObject()) == 'Example\Common\ODM\Document\MyDocument') {
$this->loadDependencies($args->getObject(), $args->getObjectManager());
}
}
/**
* Prior to persisting a document, ensure that the references exist
* to each ORM dependency.
*
* @param Doctrine\Common\Persistence\Event\LifecycleEventArgs $args
*/
public function prePersist(LifecycleEventArgs $args)
{
if (get_class($args->getObject()) == 'Example\Common\ODM\Document\MyDocument') {
$this->loadDependencies($args->getObject(), $args->getObjectManager());
}
}
/**
* Pull relational information from the ORM database to populate
* those fields in the {@link Example\Common\ODM\Document\MyDocument} document that
* require it. Each field is populated as a reference, so it will be
* loaded from the database only if necessary.
*
* @param Example\Common\ODM\Document\MyDocument $document The MyDocument to load dependencies for.
* @param Doctrine\ODM\PHPCR\DocumentManager $documentManager The DocumentManager for the MyDocument.
*/
private function loadDependencies(MyDocument $document, DocumentManager $documentManager)
{
$reflectionClass = $documentManager->getClassMetadata(get_class($document))->getReflectionClass();
$exampleProperty = $reflectionClass->getProperty('example');
$exampleProperty->setAccessible(true);
$exampleProperty->setValue(
$document,
$this->managerRegistry->getManager()->getReference('Example\Common\ORM\Entity\MyEntity', $document->getExampleId())
);
}
}
以上所有的工作都非常好 MyDocument
物体。(这基本上是一个确切的实现 教义文档中描述的内容 用于混合ORM和MongoDB ODM)。
现在的问题是,当我也想在同一个应用程序中做相反的事情时-也就是说,我也想有一个ORM实体,它有一个侦听器,它填充对ODM文档的引用或引用。
在不添加更多代码的情况下,假设我扩展了我的 services.yml
配置到:
example.event_listener.my_document:
class: Example\Common\EventListener\MyDocumentEventListener
arguments: [@doctrine]
tags:
- { name: doctrine_phpcr.event_listener, event: postLoad }
- { name: doctrine_phpcr.event_listener, event: prePersist }
example.event_listener.my_entity:
class: Example\Common\EventListener\MyEntityEventListener
arguments: [@doctrine_phpcr]
tags:
- { name: doctrine.event_listener, event: prePersist }
- { name: doctrine.event_listener, event: postLoad }
这现在将失败,因为我们有一个循环依赖:容器尝试将ODM侦听器注入到 DocumentManager
的听众,这反过来又试图注入 EntityManager
, ,然后尝试注入自己的侦听器,每个侦听器都尝试注入 DocumentManager
, ,等等。(请注意,此示例使用 Registry
而不是经理,但结果是一样的)。
我已经尝试了一些不同的方法来解决这个问题,但还没有找到一个有效的方法。有没有人能够在ORM和ODM之间获得双向侦听器,以便在单个项目中这样工作?
不幸的是,我在这方面找到了很少的例子。到目前为止,我的解决方法是创建一个服务来处理这些对象的加载/持久化,然后通过它运行所有内容,但与使用优雅的事件驱动系统相比,它似乎非常hackish。
解决方案
清理这个老问题,结果是正确的答案,如上面的评论所述:注满容器。通常我会避免这种情况,但事实证明这是解决这个特定问题的唯一方法。
如果有人正在寻找加载基于ORM的依赖关系的ODM侦听器的示例,这里是我所获得的工作示例:
服务定义:
example.event_listener.odm.my_document:
class: Example\Common\EventListener\MyDocumentEventListener
arguments: [@service_container]
tags:
- { name: doctrine_phpcr.event_listener, event: postLoad }
听众:
<?php
namespace Example\Common\EventListener;
use Example\Common\Document\MyDocument;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\ODM\PHPCR\DocumentManager;
use Symfony\Component\DependencyInjection\Container;
class MyDocumentEventListener
{
/*
* @var \Symfony\Component\DependencyInjection\Container
*/
private $container;
/**
* Constructor.
*
* @param \Symfony\Component\DependencyInjection\Container $container A Symfony dependency injection container.
*/
public function __construct(Container $container)
{
$this->container = $container;
}
/**
* After loading a document, ensure that the references exist
* to each ORM dependency.
*
* @param \Doctrine\Common\Persistence\Event\LifecycleEventArgs $args
*/
public function postLoad(LifecycleEventArgs $args)
{
if (get_class($args->getObject()) == 'Example\Common\Document\MyDocument') {
$this->loadDependencies($args->getObject(), $args->getObjectManager());
}
}
/**
* Pull relational information from the ORM database to populate
* those fields in the document that require it. Each field is
* populated as a reference, so it will be loaded from the database only
* if necessary.
*
* @param \Example\Common\Document\MyDocument $document The document to load dependencies for.
* @param \Doctrine\ODM\PHPCR\DocumentManager $documentManager The DocumentManager for the document.
*/
private function loadDependencies(MyDocument $document, DocumentManager $documentManager)
{
$documentReflectionClass = $documentManager->getClassMetadata(get_class($document))->getReflectionClass();
$someOrmProperty = $documentReflectionClass->getProperty('orm_property');
$someOrmProperty->setAccessible(true);
$someOrmProperty->setValue(
$document,
$this->container->get('doctrine.orm.entity_manager')->getReference('Example\Common\Model\MyModel', $document->getOrmPropertyId())
);
}
}
这允许document类将ID存储到ORM模型实体,并且每次加载文档时,它都会填充对文档内ORM模型的实际引用。这使得它表现得好像它一直都知道ORM属性,并且工作得很好。
此示例使用PHPCR-ODM,但对于MongoDB ODM也可以正常工作。