与Doctrine ORM,Doctrine PHPCR-ODM和事件监听器的循环依赖关系

时间:2014-06-05 06:56:51

标签: php symfony doctrine-orm doctrine doctrine-phpcr

我正在开发一个复杂的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的what is described in the Doctrine documentation的精确实现。)

现在的问题是我还希望在同一个应用程序中执行相反的操作 - 也就是说,我还希望有一个ORM实体,它具有一个填充引用或ODM文档引用的侦听器。 / p>

如果不添加更多代码,请说我将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之间的双向监听器在一个项目中像这样工作?

不幸的是,我发现很少有这方面的例子。到目前为止,我的解决方法是创建一个服务来处理这些对象的加载/持久化,然后通过它运行所有内容,但与使用优雅的事件驱动系统相比,它似乎非常hack。

1 个答案:

答案 0 :(得分:0)

根据上面评论中提到的正确答案清理这个旧问题:注入完整的容器。通常我会避免这种情况,但事实证明这是解决这一特定问题的唯一方法。

如果有人正在寻找加载基于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())
    );
  }
}

这允许文档类将ID存储到ORM模型实体,并且每次加载文档时,它都会填充文档中对ORM模型的实际引用。这使得它的行为就像它一直都知道ORM属性一样,而且效果很好。

此示例使用PHPCR-ODM,但也适用于MongoDB ODM。