3

I am trying to inject one of my services into an EntityListener in order to call some application specific behaviour when an entity gets updated.

My Logger service, used to store events in a LogEntry entity in my database:

class Logger
{
    /**
     * @var EntityManager $manager The doctrine2 manager
     */
   protected $manager;
   //...
}

The listener:

class EntityListener
{
    public function __construct(Logger $logger)
    {
        $this->logger = $logger;
         // ...
    }
}

And the service definitions in my service.yml:

listener:
    class: Namespace\EntityListener
    arguments: [@logger]
    tags:
        - { name: doctrine.event_listener, event: preUpdate }

logger:
    class: Namespace\Logger
    arguments: [@doctrine.orm.entity_manager]

Unfortunately it results in a ServiceCircularReferenceException:

Circular reference detected for service "doctrine.orm.default_entity_manager", path: "doctrine.orm.default_entity_manager -> doctrine.dbal.default_connection -> listener -> logger".

The problem obviously is that I inject the doctrine into the my service while it is also automatically injected into my listener. How do I proceed? I found a very similar question but the accepted answer is to inject the container which is obviously not favourable.

Any suggestions on how to solve my issue would be appreciated.


Small side note: I would like to avoid a solution depending on lazy services if possible.

Community
  • 1
  • 1
ferdynator
  • 6,245
  • 3
  • 27
  • 56
  • maybe some smart person has a better solution. But for now I will do exactly that :/ – ferdynator Dec 11 '15 at 15:55
  • The basic problem is that the entity manager service is dependent on it's listeners. Having a listener be dependent on the entity manager is what causes the circular reference. Injecting the container is a workaround. You might also be able to have the listener itself pass the entity manager to the logger. It can also be worthwhile to study how some existing loggers work: https://github.com/Atlantic18/DoctrineExtensions – Cerad Dec 11 '15 at 15:59
  • Of course. On the other hand unnecessarily injecting the container is typically considered to be a very bad thing indeed. Simple is not always best. And no, it does not have to be setter injection. – Cerad Dec 11 '15 at 16:05
  • @Cerad IMHO looking at existing loggers does not solve the underlying problem. I tried to give an example with a simple use case but the issue remains for any service depending on doctrine being injected into a listener. – ferdynator Dec 11 '15 at 16:13
  • Sure but why not have the listener pass the entity manager to the logger? $this->logger->log($em,$whatever); And of course if you decide to pass the container (which is basically lazy loading the entity manager in this case) then that is fine as well. Is the logger class being called from anywhere besides a listener? If not then let your listener be the logger and the whole problem goes away. – Cerad Dec 11 '15 at 16:25
  • Yes the logger is used all over the application. Setter-Injection might be a way. Or does Symfony check for circular references then as well? – ferdynator Dec 11 '15 at 16:48
  • It's been a long time, have you found the solution? – miikes Sep 12 '16 at 08:26
  • Indeed. I'll add an answer now. – ferdynator Sep 12 '16 at 08:48

2 Answers2

1

First of all I switched from an EventListener to an EventSubscriber. From the docs:

Doctrine defines two types of objects that can listen to Doctrine events: listeners and subscribers. Both are very similar, but listeners are a bit more straightforward.

It turns out one can access the ObjectManager via the passed $args-parameter like so:

/** @var Doctrine\Common\Persistence\ObjectManager $manager */
$manager = $args->getObjectManager();

So either use it directly in the callback:

public function postUpdate(LifecycleEventArgs $args)
{
    $manager = $args->getObjectManager();
    // ...

...or set it to an object field:

/** @var ObjectManager $manager */
private $manager;

public function postUpdate(LifecycleEventArgs $args)
{
    $this->manager = $args->getObjectManager();
    // ...
ferdynator
  • 6,245
  • 3
  • 27
  • 56
0

After struggling with the same problem, I found out that using lazy loading solved my issue.

listener:
    class: AppBundle\EventListener\OrderDoctrineListener
    tags:
        - { name: doctrine.event_listener, event: postPersist, lazy: true }
Claude
  • 469
  • 4
  • 6