Symfony循环引用异常用于Doctrine onFlush事件侦听器服务

时间:2021-02-27 06:46:56

I have created a service for a Doctrine onFlush event listener. In this listener I would like to reference a common function I have in another service to check the entity's shortcut path. That other service uses the entity manager to do this, so the service definition for that other service injects the doctrine entity manager as a constructor argument. But if I include that other service in my main onFlush event listener, I get a nasty error about circular references.

我已经为flush event listener上的Doctrine创建了一个服务。在这个侦听器中,我想引用另一个服务中的一个公共函数来检查实体的快捷路径。另一个服务使用实体管理器执行此操作,因此其他服务的服务定义将doctrine实体管理器作为构造函数参数注入。但是如果我在主onFlush事件监听器中包含了其他服务,那么我就会对循环引用产生严重的错误。

I could make this entity_helper service accept the Entity Manager in a set function setEntityManager($entityManager). But that means whenever I use this entity_helper service anywhere else, I have to always pass in the EntityManager. Maybe that's okay, but is that the only solution here? Is there something wrong with my logic/understanding to begin with? (I am new to Symfony so I'm frequently wrong).

我可以让这个entity_helper服务接受set函数setEntityManager($entityManager)中的实体管理器。但这意味着无论何时我在其他地方使用entity_helper服务,我都必须始终传递EntityManager。也许这没问题,但这是唯一的解吗?我的逻辑/理解有什么问题吗?(我对Symfony不熟,所以我经常错。)

Exhibit A: Nasty Error

展览:严重的错误

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

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\Service\EntityHelper

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\EventListener\IsVersionableListener

Gutensite \ CmsBundle \ EventListener \ 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 个解决方案

#1


4  

The basic solution to this (as I indicated) is to remove the construct or setter injection of the EntityManager from the service definition (and the service class). Instead you have to pass the EntityManager into the function that needs it. This prevents the circular reference.

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

I opted for this instead of a creating a setEntityManager because it seems clunky to have to set that on the EntityHelper service, before calling the function. It seems better to just pass it to the functions that need it directly.

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

Here are the changes:

Services.yml

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\Service\EntityHelper

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\EventListener\IsVersionableListener

Gutensite \ CmsBundle \ EventListener \ 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);
                }

            }
        }
    }
}

#1


4  

The basic solution to this (as I indicated) is to remove the construct or setter injection of the EntityManager from the service definition (and the service class). Instead you have to pass the EntityManager into the function that needs it. This prevents the circular reference.

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

I opted for this instead of a creating a setEntityManager because it seems clunky to have to set that on the EntityHelper service, before calling the function. It seems better to just pass it to the functions that need it directly.

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

Here are the changes:

Services.yml

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\Service\EntityHelper

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\EventListener\IsVersionableListener

Gutensite \ CmsBundle \ EventListener \ 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);
                }

            }
        }
    }
}