Doctrine Listener Dependency Injection

Dear Laminas Community,

I’m currently facing the problem that I need to inject services into Doctrine Listeners, I’ve spent the whole day looking/trying for a solution but this seems a rare topic in combination with Laminas.

I’m e.g. trying to Inject a CustomService into a UserListener which should do $stuff during postPersist() Event. I’ve tried working with factories for example but it seems that Doctrine Listeners and the Events work out of the Laminas context.

Is there a clean way to inject services into Doctrine Listeners?

Thanks for your help!
Wayne

How do you create your EntityManager? Do you have any code to see what you’ve tried/already achieved?

I’ve found this for ZF2 but is not suitable for Laminas: https://github.com/doctrine/DoctrineORMModule/issues/292

I’ve tried to adopt this for Laminas but without luck. Anybody out there that knows how to “use Laminas Services within Doctrine Listener”

Pushing this because I ran into a very similar, if not the same, issue. The question stays nearly the same. Is it possible to inject dependencies into an event subscriber or listener?

I did a factory for my event subscriber class. This works fine, until a service is called, that has a Doctrine\ORM\EntityManager dependency itself. I gues this runs kinda cyclic. At least for me it always ends in an out of memory error.

Here 's some example code …

<?php
declare(strict_types=1);
namespace Application\Event\Subscriber;

use Application\Entity\User;
use Doctrine\Common\EventArgs;
use Doctrine\Common\EventSubscriber;

class MyEventSubscriber implements EventSubscriber
{
    protected ?User $user = null;

    public function getSubscribedEvents(): array
    {
        return [
            'onFlush',
        ];
    }

    public function setUser(User $user): void
    {
        $this->user = $user;
    }

    public function onFlush(EventArgs $args): void
    {
        // do something fancy with the user
    }
}

To set the user instance I thought I could use a factory …

declare(strict_types=1);
namespace Application\Event\Subscriber\Factory;

use Application\Enity\User;
use Doctrine\ORM\EntityManager;
use Interop\Container\ContainerInterface;
use Laminas\Authentication\AuthenticationService;
use Laminas\ServiceManager\Factory\FactoryInterface;

class MyEventSubscriberFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $entityManager = $container->get(EntityManager::class);
        $authenticationService = $container->get(AuthenticationService::class);
        $subscriber = new MyEventSubscriber();
        
        if ($authenticationService->hasIdentity()) {
            $user = $entityManager->getRepository(User::class)
                ->findOneByEmail($authenticationService->getIdentity());
            $subscriber->setUser($user);
        }

        return $subscriber;
    }
}

In the module.config.php file the doctrine configuration for the event manager looks as follows:

...
'doctrine' => [
    ...
    'eventmanager' => [
        'orm_default' => [
            'subscribers' => [
                Event\Subscriber\MyEventSubscriber::class,
            ],
        ],
    ],
    ...
],

'service_manager' => [
    'factories' => [
        Event\Subscriber\MyEventSubscriber::class => Event\Subscriber\Factory\MyEventSubscriber::class,
    ],
],
...

When calling the EntityManager class it runs into an infinite loop and ends up with a memory overflow. What else can I do to inject data into an event subscriber?

Okay, I have found a working solution that is not very clean, but does what it is supposed to do without any problems.

The Problem
One can not use factories for Doctrine ORM EventListener or EventSubscriber classes, when within the factory dependencies to other services exist, that use Doctine dependencies. A practical example: I want to inject a Laminas AuthenticationService instance to a Doctrine EventSubscriber class to identify a logged in user, when a specific event is triggered. This AuthenticatenService instance needs a EntityManager instance for a database lookup. This will lead into an infinite loop and finally into an memory overflow.

The Solution
We have to find some lazy loading mechanism, that injects the dependency after the whole Doctrine initalization shizzle is done. For my specific concern I used a delegator class.

<?php
declare(strict_types=1);
namespace Application\Event\Listener;

use Doctrine\Common\EventSubscriber;
use Interop\Container\ContainerInterface;
use Laminas\Authentication\AuthenticationService;

class MayEventSubscriberDelegator extends MyEventSubscriber
{
    protected ContainerInterface $container;

    protected EventSubscriber $subscriber;

    public function __construct(ContainerInterface $container, EventSubscriber $subscriber)
    {
        $this->container = $container;
        $this->subscriber = $subscriber;

        parent::__construct();
    }

    public function createLogEntry(string $action, object $object, LoggableAdapter $adapter): ?LogEntry
    {
        $authenticationService = $this->container->get(AuthenticationService::class);

        if ($authenticationService->hasIdendity()) {
            $user = $adapter->getObjectManager()
                ->getRepository(User::class)
                ->findOneByEmail($authenticationService->getIdentity());
            $this->subscriber->setUser($user);
        }

        return $this->subscriber->createLogEntry($action, $object, $adapter);
    }
}

The delegator factory …

declare(strict_types=1);
namespace Application\Event\Listener\Factory;

use Application\Event\Listener\MyEventSubscriberDelegator;
use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\Factory\DelegatorFactoryInterface;

class MyEventSubscriberDelegatorFactory implements DelegatorFactoryInterface
{
    public function __invoke(ContainerInterface $container, $name, callable $callback, array $options = null)
    {
        $subscriber = call_user_func($callback);
        return new MyEventSubscriberDelegator($container, $subscriber);
    }
}

… and die configuration in module.config.php

...
'doctrine' => [
    ...
    'eventmanager' => [
            'orm_default' => [
                'subscribers' => [
                    Event\Listener\MyEventSubscriber::class,
                ],
            ],
        ],
    ...
],
'service_manager' => [
    'delegators' => [
            Event\Listener\MyEventEventSubscriber::class => [
                Event\Listener\Factory\MyEventSubscriberDelegatorFactory::class,
            ],
        ],
        'factories' => [
            Event\Listener\MyEventSubscriber::class => InvokableFactory::class,
        ],
],
...

Works fine for me but still feels uncomfortable because I feel, that there must be a better solution.