Doctrine onFlush事件监听器服务的Symfony循环引用异常

时间:2014-08-30 00:16:19

标签: php symfony doctrine-orm

我为Doctrine onFlush事件监听器创建了一个服务。在这个监听器中,我想引用我在另一个服务中的常用函数来检查实体的快捷方式路径。 其他服务使用实体管理器来执行此操作,因此该其他服务的服务定义将doctrine实体管理器作为构造函数参数注入。但是如果我在我的主onFlush事件监听器中包含其他服务,我会得到一个关于循环引用的讨厌错误。

我可以让这个entity_helper服务接受设置函数setEntityManager($entityManager)中的实体管理器。但这意味着每当我在其他地方使用此entity_helper服务时,我必须始终传入EntityManager。也许那没关系,但这是唯一的解决方案吗?开始时我的逻辑/理解是否有问题? (我是Symfony的新手,所以我经常出错。)

图表A:讨厌的错误

Fatal error: Uncaught exception 'Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException' with message 'Circular reference detected for service "doctrine.dbal.cms_connection", path: "doctrine.dbal.cms_connection".' in /var/www/core/cms/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php:456 Stack trace: #0 /var/www/core/cms/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php(604): Symfony\Component\DependencyInjection\Dumper\PhpDumper->addServiceInlinedDefinitionsSetup('doctrine.dbal.c...', Object(Symfony\Component\DependencyInjection\Definition)) #1 /var/www/core/cms/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php(630): Symfony\Component\DependencyInjection\Dumper\PhpDumper->addService('doctrine.dbal.c...', Object(Symfony\Component\DependencyInjection\Definition)) #2 /var/www/core/cms/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php(117): Symfony\Componen in /var/www/core/cms/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php on line 456

Services.yml

# This is the helper class for all entities
gutensite_cms.entity_helper:
    class: Gutensite\CmsBundle\Service\EntityHelper
    # This causes the Circular Reference Error when this service is included in Event Listener
    arguments: [ "@doctrine.orm.cms_entity_manager" ]
    # Passing via a setter injection also causes the same error.
    #calls:
    #    - [setEntityManager, ['@doctrine.orm.cms_entity_manager']]


# An event listener for any entity that is Versionable
gutensite_cms.listener.is_versionable:
    class: Gutensite\CmsBundle\EventListener\IsVersionableListener
    #only pass in the services we need
    arguments: [ "@gutensite_cms.entity_helper" ]
    tags:
        - { name: doctrine.event_listener, event: onFlush }

Gutensite \ CmsBundle \服务\ EntityHelper

namespace Gutensite\CmsBundle\Service;

use Doctrine\ORM\EntityManager;

class EntityHelper {

    /**
     * @var $em EntityManager
     */
    private $em;

    public function __construct(EntityManager $entityManager) {
        $this->em = $entityManager;
    }

    /**
     * Get the bundle shortcut path for an entity based on it's namespace.
     *
     * As an example, if your entity is Gutensite\CmsBundle\Entity\View\ViewVersion the function will return
     * GutensiteCmsBundle:View\ViewVersion
     *
     * @param $entity
     * @return string
     */
    public function getEntityBundleShortcut($entity) {
        // wrap get_class() in the entityManager metadata function to avoid returning cached proxy class
        $path = explode('\Entity\\', $this->em->getClassMetadata(get_class($entity))->getName());
        return str_replace('\\', '', $path[0]).':'.$path[1];
    }

}

Gutensite \ CmsBundle \事件监听\ IsVersionableListener

namespace Gutensite\CmsBundle\EventListener;

use Doctrine\ORM\Event\OnFlushEventArgs;
use Gutensite\CmsBundle\Service\EntityHelper;


/**
 * Class IsVersionableListener
 * @package Gutensite\CmsBundle\EventListener
 */
class IsVersionableListener
{

    /*
    private $entityHelper;

    public function __construct(EntityHelper $entityHelper) {
        $this->entityHelper = $entityHelper;
    }
    */

    public function onFlush(OnFlushEventArgs $eventArgs)
    {

        // This never is excecuted because of the error
        print('ON FLUSH EVENT EXECUTED');
        exit;

        $em = $eventArgs->getEntityManager();
        $uow = $em->getUnitOfWork();
        $updatedEntities = $uow->getScheduledEntityUpdates();

        foreach($updatedEntities AS $entity) {

            // This is generic listener for all entities that have an isVersionable method (e.g. ViewVersion)
            // TODO: at the moment, we only want to do the following code for the viewVersion entity

            if (method_exists($entity, 'isVersionable') && $entity->isVersionable()) {

                // Get the Correct Repo for this entity
                $entityShortcut = $this->entityHelper->getEntityBundleShortcut($entity);
                $repo = $em->getRepository($entityShortcut);

                // If the repo for this entity has an onFlush method, use it.
                // This allows us to keep the functionality in the entity repo
                if(method_exists($repo, 'onFlush')) {
                    $repo->onFlush($em, $entity);
                }

            }
        }
    }
}

1 个答案:

答案 0 :(得分:4)

对此的基本解决方案(正如我所指出的)是从服务定义(和服务类)中删除EntityManager的构造或setter注入。相反,您必须将EntityManager传递给需要它的函数。这会阻止循环引用。

我选择了这个而不是创建setEntityManager,因为在调用函数之前必须在EntityHelper服务上设置它似乎很笨拙。将它直接传递给需要它的函数似乎更好。

以下是更改:

Services.yml

# This is the helper class for all entities
gutensite_cms.entity_helper:
    class: Gutensite\CmsBundle\Service\EntityHelper
    # Do NOT pass in EntityManager via constructor or injector, because it causes a Circular Reference Error when this service is included in Event Listener


# An event listener for any entity that is Versionable
gutensite_cms.listener.is_versionable:
    class: Gutensite\CmsBundle\EventListener\IsVersionableListener
    #only pass in the services we need
    arguments: [ "@gutensite_cms.entity_helper" ]
    tags:
        - { name: doctrine.event_listener, event: onFlush }

Gutensite \ CmsBundle \服务\ EntityHelper

namespace Gutensite\CmsBundle\Service;

use Doctrine\ORM\EntityManager;

class EntityHelper {


    /**
     * Get the bundle shortcut path for an entity based on it's namespace.
     *
     * As an example, if your entity is Gutensite\CmsBundle\Entity\View\ViewVersion the function will return
     * GutensiteCmsBundle:View\ViewVersion
     *
     * @param $entity
     * @return string
     */
    public function getEntityBundleShortcut(EventManager $eventManager, $entity) {
        // wrap get_class() in the entityManager metadata function to avoid returning cached proxy class
        $path = explode('\Entity\\', $eventManager->getClassMetadata(get_class($entity))->getName());
        return str_replace('\\', '', $path[0]).':'.$path[1];
    }

}

Gutensite \ CmsBundle \事件监听\ IsVersionableListener

namespace Gutensite\CmsBundle\EventListener;

use Doctrine\ORM\Event\OnFlushEventArgs;
use Gutensite\CmsBundle\Service\EntityHelper;


/**
 * Class IsVersionableListener
 * @package Gutensite\CmsBundle\EventListener
 */
class IsVersionableListener
{

    /*
    private $entityHelper;

    public function __construct(EntityHelper $entityHelper) {
        $this->entityHelper = $entityHelper;
    }
    */

    public function onFlush(OnFlushEventArgs $eventArgs)
    {

        $em = $eventArgs->getEntityManager();
        $uow = $em->getUnitOfWork();
        $updatedEntities = $uow->getScheduledEntityUpdates();

        foreach($updatedEntities AS $entity) {

            // This is generic listener for all entities that have an isVersionable method (e.g. ViewVersion)
            // TODO: at the moment, we only want to do the following code for the viewVersion entity

            if (method_exists($entity, 'isVersionable') && $entity->isVersionable()) {

                // Get the Correct Repo for this entity
                $entityShortcut = $this->entityHelper->getEntityBundleShortcut($em, $entity);
                $repo = $em->getRepository($entityShortcut);

                // If the repo for this entity has an onFlush method, use it.
                // This allows us to keep the functionality in the entity repo
                if(method_exists($repo, 'onFlush')) {
                    $repo->onFlush($em, $entity);
                }

            }
        }
    }
}