Doctrine ORM, Doctrine PHPCR-ODM 및 이벤트 리스너를 사용한 순환 종속성
-
21-12-2019 - |
문제
나는 Doctrine ORM 개체와 Doctrine PHPCR-ODM 문서를 혼합하는 복잡한 Symfony 프로젝트를 진행하고 있습니다.모든 것이 잘 작동하지만 컨테이너의 리스너 간의 순환 종속성 주입 문제를 해결할 수 없습니다.
시나리오는 로드될 때 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\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을 혼합하는 경우).
이제 문제는 동일한 애플리케이션 내에서 그 반대도 수행하고 싶을 때입니다. 즉, 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.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 사이의 양방향 리스너가 이와 같이 작동하도록 할 수 있었던 사람이 있습니까?
불행히도 이와 관련된 예는 거의 발견되지 않았습니다.지금까지 내 해결 방법은 이러한 개체의 로드/지속을 처리하는 서비스를 만든 다음 이를 통해 모든 것을 실행하는 것이었지만 우아한 이벤트 기반 시스템을 사용하는 것에 비해 매우 해킹적인 것처럼 보입니다.
해결책
위의 설명에 언급된 대로 정답으로 판명된 것으로 이 오래된 질문을 정리합니다.전체 용기를 주입합니다.일반적으로 나는 이것을 피하지만 이것이 이 특정 문제를 해결하는 유일한 방법임이 밝혀졌습니다.
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())
);
}
}
이를 통해 문서 클래스는 ORM 모델 엔터티에 ID를 저장할 수 있으며 문서가 로드될 때마다 문서 내의 ORM 모델에 대한 실제 참조를 채웁니다.이렇게 하면 ORM 속성에 대해 모두 알고 있는 것처럼 동작하고 꽤 잘 작동합니다.
이 예제에서는 PHPCR-ODM을 사용하지만 MongoDB ODM에서도 잘 작동합니다.