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.