diff --git a/DependencyInjection/Compiler/EntityListenerPass.php b/DependencyInjection/Compiler/EntityListenerPass.php
index ab0cc00d2..f1eff8fe5 100644
--- a/DependencyInjection/Compiler/EntityListenerPass.php
+++ b/DependencyInjection/Compiler/EntityListenerPass.php
@@ -2,8 +2,12 @@
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;
+use Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver;
+use Doctrine\Bundle\DoctrineBundle\Mapping\EntityListenerServiceResolver;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
@@ -19,6 +23,8 @@ public function process(ContainerBuilder $container)
{
$resolvers = $container->findTaggedServiceIds('doctrine.orm.entity_listener');
+ $lazyServiceReferencesByResolver = [];
+
foreach ($resolvers as $id => $tagAttributes) {
foreach ($tagAttributes as $attributes) {
$name = isset($attributes['entity_manager']) ? $attributes['entity_manager'] : $container->getParameter('doctrine.default_entity_manager');
@@ -41,35 +47,44 @@ public function process(ContainerBuilder $container)
$this->attachToListener($container, $name, $id, $attributes);
}
- if (isset($attributes['lazy']) && $attributes['lazy']) {
+ $resolverClass = $this->getResolverClass($resolver, $container);
+ $resolverSupportsLazyListeners = is_a($resolverClass, EntityListenerServiceResolver::class, true);
+
+ $lazyByAttribute = isset($attributes['lazy']) && $attributes['lazy'];
+ if ($lazyByAttribute && ! $resolverSupportsLazyListeners) {
+ throw new InvalidArgumentException(sprintf(
+ 'Lazy-loaded entity listeners can only be resolved by a resolver implementing %s.',
+ EntityListenerServiceResolver::class
+ ));
+ }
+
+ if (! isset($attributes['lazy']) && $resolverSupportsLazyListeners || $lazyByAttribute) {
$listener = $container->findDefinition($id);
if ($listener->isAbstract()) {
throw new InvalidArgumentException(sprintf('The service "%s" must not be abstract as this entity listener is lazy-loaded.', $id));
}
- $interface = 'Doctrine\\Bundle\\DoctrineBundle\\Mapping\\EntityListenerServiceResolver';
- $class = $resolver->getClass();
-
- if (substr($class, 0, 1) === '%') {
- // resolve container parameter first
- $class = $container->getParameterBag()->resolveValue($resolver->getClass());
- }
+ $resolver->addMethodCall('registerService', [$listener->getClass(), $id]);
- if (! is_a($class, $interface, true)) {
- throw new InvalidArgumentException(
- sprintf('Lazy-loaded entity listeners can only be resolved by a resolver implementing %s.', $interface)
- );
+ // if the resolver uses the default class we will use a service locator for all listeners
+ if ($resolverClass === ContainerEntityListenerResolver::class) {
+ if (! isset($lazyServiceReferencesByResolver[$resolverId])) {
+ $lazyServiceReferencesByResolver[$resolverId] = [];
+ }
+ $lazyServiceReferencesByResolver[$resolverId][$id] = new Reference($id);
+ } else {
+ $listener->setPublic(true);
}
-
- $listener->setPublic(true);
-
- $resolver->addMethodCall('registerService', [$listener->getClass(), $id]);
} else {
$resolver->addMethodCall('register', [new Reference($id)]);
}
}
}
+
+ foreach ($lazyServiceReferencesByResolver as $resolverId => $listenerReferences) {
+ $container->findDefinition($resolverId)->setArgument(0, ServiceLocatorTagPass::register($container, $listenerReferences));
+ }
}
private function attachToListener(ContainerBuilder $container, $name, $id, array $attributes)
@@ -94,4 +109,16 @@ private function attachToListener(ContainerBuilder $container, $name, $id, array
$container->findDefinition($listenerId)->addMethodCall('addEntityListener', $args);
}
+
+ private function getResolverClass(Definition $resolver, ContainerBuilder $container) : string
+ {
+ $resolverClass = $resolver->getClass();
+
+ if (substr($resolverClass, 0, 1) === '%') {
+ // resolve container parameter first
+ $resolverClass = $container->getParameterBag()->resolveValue($resolver->getClass());
+ }
+
+ return $resolverClass;
+ }
}
diff --git a/Mapping/ContainerAwareEntityListenerResolver.php b/Mapping/ContainerAwareEntityListenerResolver.php
index 4345015b7..89f8320d7 100644
--- a/Mapping/ContainerAwareEntityListenerResolver.php
+++ b/Mapping/ContainerAwareEntityListenerResolver.php
@@ -2,107 +2,9 @@
namespace Doctrine\Bundle\DoctrineBundle\Mapping;
-use InvalidArgumentException;
-use RuntimeException;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-class ContainerAwareEntityListenerResolver implements EntityListenerServiceResolver
+/**
+ * @deprecated since DoctrineBundle 1.11 and will be removed in 2.0. Use ContainerEntityListenerResolver instead.
+ */
+class ContainerAwareEntityListenerResolver extends ContainerEntityListenerResolver
{
- /** @var ContainerInterface */
- private $container;
-
- /** @var object[] Map to store entity listener instances. */
- private $instances = [];
-
- /** @var string[] Map to store registered service ids */
- private $serviceIds = [];
-
- public function __construct(ContainerInterface $container)
- {
- $this->container = $container;
- }
-
- /**
- * {@inheritdoc}
- */
- public function clear($className = null)
- {
- if ($className === null) {
- $this->instances = [];
-
- return;
- }
-
- $className = $this->normalizeClassName($className);
-
- if (! isset($this->instances[$className])) {
- return;
- }
-
- unset($this->instances[$className]);
- }
-
- /**
- * {@inheritdoc}
- */
- public function register($object)
- {
- if (! is_object($object)) {
- throw new InvalidArgumentException(sprintf('An object was expected, but got "%s".', gettype($object)));
- }
-
- $className = $this->normalizeClassName(get_class($object));
-
- $this->instances[$className] = $object;
- }
-
- /**
- * {@inheritdoc}
- */
- public function registerService($className, $serviceId)
- {
- $this->serviceIds[$this->normalizeClassName($className)] = $serviceId;
- }
-
- /**
- * {@inheritdoc}
- */
- public function resolve($className)
- {
- $className = $this->normalizeClassName($className);
-
- if (! isset($this->instances[$className])) {
- if (isset($this->serviceIds[$className])) {
- $this->instances[$className] = $this->resolveService($this->serviceIds[$className]);
- } else {
- $this->instances[$className] = new $className();
- }
- }
-
- return $this->instances[$className];
- }
-
- /**
- * @param string $serviceId
- *
- * @return object
- */
- private function resolveService($serviceId)
- {
- if (! $this->container->has($serviceId)) {
- throw new RuntimeException(sprintf('There is no service named "%s"', $serviceId));
- }
-
- return $this->container->get($serviceId);
- }
-
- /**
- * @param string $className
- *
- * @return string
- */
- private function normalizeClassName($className)
- {
- return trim($className, '\\');
- }
}
diff --git a/Mapping/ContainerEntityListenerResolver.php b/Mapping/ContainerEntityListenerResolver.php
new file mode 100644
index 000000000..9645838d8
--- /dev/null
+++ b/Mapping/ContainerEntityListenerResolver.php
@@ -0,0 +1,103 @@
+container = $container;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear($className = null)
+ {
+ if ($className === null) {
+ $this->instances = [];
+
+ return;
+ }
+
+ $className = $this->normalizeClassName($className);
+
+ unset($this->instances[$className]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function register($object)
+ {
+ if (! is_object($object)) {
+ throw new InvalidArgumentException(sprintf('An object was expected, but got "%s".', gettype($object)));
+ }
+
+ $className = $this->normalizeClassName(get_class($object));
+
+ $this->instances[$className] = $object;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function registerService($className, $serviceId)
+ {
+ $this->serviceIds[$this->normalizeClassName($className)] = $serviceId;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function resolve($className)
+ {
+ $className = $this->normalizeClassName($className);
+
+ if (! isset($this->instances[$className])) {
+ if (isset($this->serviceIds[$className])) {
+ $this->instances[$className] = $this->resolveService($this->serviceIds[$className]);
+ } else {
+ $this->instances[$className] = new $className();
+ }
+ }
+
+ return $this->instances[$className];
+ }
+
+ /**
+ * @return object
+ */
+ private function resolveService(string $serviceId)
+ {
+ if (! $this->container->has($serviceId)) {
+ throw new RuntimeException(sprintf('There is no service named "%s"', $serviceId));
+ }
+
+ return $this->container->get($serviceId);
+ }
+
+ private function normalizeClassName(string $className) : string
+ {
+ return trim($className, '\\');
+ }
+}
diff --git a/Resources/config/orm.xml b/Resources/config/orm.xml
index 0fe60c165..ab9dd5bbe 100644
--- a/Resources/config/orm.xml
+++ b/Resources/config/orm.xml
@@ -62,7 +62,7 @@
Doctrine\ORM\Mapping\AnsiQuoteStrategy
- Doctrine\Bundle\DoctrineBundle\Mapping\ContainerAwareEntityListenerResolver
+ Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver
Doctrine\ORM\Cache\DefaultCacheFactory
diff --git a/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php b/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php
index 6f8793df4..a26437388 100644
--- a/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php
+++ b/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php
@@ -16,6 +16,7 @@
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\ServiceLocator;
abstract class AbstractDoctrineExtensionTest extends TestCase
{
@@ -831,7 +832,7 @@ public function testEntityListenerResolver()
$this->assertDICDefinitionMethodCallOnce($definition, 'setEntityListenerResolver', [new Reference('doctrine.orm.em2_entity_listener_resolver')]);
$listener = $container->getDefinition('doctrine.orm.em1_entity_listener_resolver');
- $this->assertDICDefinitionMethodCallOnce($listener, 'register', [new Reference('entity_listener1')]);
+ $this->assertDICDefinitionMethodCallOnce($listener, 'registerService', ['EntityListener', 'entity_listener1']);
$listener = $container->getDefinition('entity_listener_resolver');
$this->assertDICDefinitionMethodCallOnce($listener, 'register', [new Reference('entity_listener2')]);
@@ -849,10 +850,10 @@ public function testAttachEntityListenerTag()
$this->compileContainer($container);
$listener = $container->getDefinition('doctrine.orm.em1_entity_listener_resolver');
- $this->assertDICDefinitionMethodCallOnce($listener, 'register', [new Reference('entity_listener1')]);
+ $this->assertDICDefinitionMethodCallOnce($listener, 'registerService', ['EntityListener1', 'entity_listener1']);
$listener = $container->getDefinition('doctrine.orm.em2_entity_listener_resolver');
- $this->assertDICDefinitionMethodCallOnce($listener, 'register', [new Reference('entity_listener2')]);
+ $this->assertDICDefinitionMethodCallOnce($listener, 'registerService', ['EntityListener2', 'entity_listener2']);
$attachListener = $container->getDefinition('doctrine.orm.em1_listeners.attach_entity_listeners');
$this->assertDICDefinitionMethodCallOnce($attachListener, 'addEntityListener', ['My/Entity1', 'EntityListener1', 'postLoad']);
@@ -893,12 +894,39 @@ public function testAttachLazyEntityListener()
$this->compileContainer($container);
$resolver1 = $container->getDefinition('doctrine.orm.em1_entity_listener_resolver');
- $this->assertDICDefinitionMethodCallOnce($resolver1, 'registerService', ['EntityListener1', 'entity_listener1']);
+ $this->assertDICDefinitionMethodCallAt(0, $resolver1, 'registerService', ['EntityListener1', 'entity_listener1']);
+ $this->assertDICDefinitionMethodCallAt(1, $resolver1, 'register', [new Reference('entity_listener3')]);
+ $this->assertDICDefinitionMethodCallAt(2, $resolver1, 'registerService', ['EntityListener4', 'entity_listener4']);
+
+ $serviceLocatorReference = $resolver1->getArgument(0);
+ $this->assertInstanceOf(Reference::class, $serviceLocatorReference);
+ $serviceLocatorDefinition = $container->getDefinition((string) $serviceLocatorReference);
+ $this->assertSame(ServiceLocator::class, $serviceLocatorDefinition->getClass());
+ $serviceLocatorMap = $serviceLocatorDefinition->getArgument(0);
+ $this->assertSame(['entity_listener1', 'entity_listener4'], array_keys($serviceLocatorMap));
$resolver2 = $container->findDefinition('custom_entity_listener_resolver');
$this->assertDICDefinitionMethodCallOnce($resolver2, 'registerService', ['EntityListener2', 'entity_listener2']);
}
+ public function testAttachLazyEntityListenerForCustomResolver()
+ {
+ $container = $this->getContainer([]);
+ $loader = new DoctrineExtension();
+ $container->registerExtension($loader);
+ $container->addCompilerPass(new EntityListenerPass());
+
+ $this->loadFromFile($container, 'orm_entity_listener_custom_resolver');
+
+ $this->compileContainer($container);
+
+ $resolver = $container->getDefinition('custom_entity_listener_resolver');
+ $this->assertTrue($resolver->isPublic());
+ $this->assertEmpty($resolver->getArguments(), 'We must not change the arguments for custom services.');
+ $this->assertDICDefinitionMethodCallOnce($resolver, 'registerService', ['EntityListener', 'entity_listener']);
+ $this->assertTrue($container->getDefinition('entity_listener')->isPublic());
+ }
+
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage EntityListenerServiceResolver
@@ -1007,6 +1035,8 @@ private function assertDICDefinitionMethodCallAt($pos, Definition $definition, $
{
$calls = $definition->getMethodCalls();
if (! isset($calls[$pos][0])) {
+ $this->fail(sprintf('Method call at position %s not found!', $pos));
+
return;
}
diff --git a/Tests/DependencyInjection/Fixtures/CustomEntityListenerServiceResolver.php b/Tests/DependencyInjection/Fixtures/CustomEntityListenerServiceResolver.php
new file mode 100644
index 000000000..76a43a279
--- /dev/null
+++ b/Tests/DependencyInjection/Fixtures/CustomEntityListenerServiceResolver.php
@@ -0,0 +1,48 @@
+resolver = $resolver;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear($className = null)
+ {
+ $this->resolver->clear($className);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function resolve($className)
+ {
+ return $this->resolver->resolve($className);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function register($object)
+ {
+ $this->resolver->register($object);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function registerService($className, $serviceId)
+ {
+ $this->resolver->registerService($className, $serviceId);
+ }
+}
diff --git a/Tests/DependencyInjection/Fixtures/config/xml/orm_attach_lazy_entity_listener.xml b/Tests/DependencyInjection/Fixtures/config/xml/orm_attach_lazy_entity_listener.xml
index 65358d08f..1da2f822d 100644
--- a/Tests/DependencyInjection/Fixtures/config/xml/orm_attach_lazy_entity_listener.xml
+++ b/Tests/DependencyInjection/Fixtures/config/xml/orm_attach_lazy_entity_listener.xml
@@ -7,7 +7,7 @@
http://symfony.com/schema/dic/doctrine http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
-
+
@@ -17,6 +17,12 @@
+
+
+
+
+
+
diff --git a/Tests/DependencyInjection/Fixtures/config/xml/orm_entity_listener_custom_resolver.xml b/Tests/DependencyInjection/Fixtures/config/xml/orm_entity_listener_custom_resolver.xml
new file mode 100644
index 000000000..69734f4e4
--- /dev/null
+++ b/Tests/DependencyInjection/Fixtures/config/xml/orm_entity_listener_custom_resolver.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/DependencyInjection/Fixtures/config/yml/orm_attach_lazy_entity_listener.yml b/Tests/DependencyInjection/Fixtures/config/yml/orm_attach_lazy_entity_listener.yml
index 530186187..f312827e9 100644
--- a/Tests/DependencyInjection/Fixtures/config/yml/orm_attach_lazy_entity_listener.yml
+++ b/Tests/DependencyInjection/Fixtures/config/yml/orm_attach_lazy_entity_listener.yml
@@ -1,6 +1,6 @@
services:
custom_entity_listener_resolver:
- class: Doctrine\Bundle\DoctrineBundle\Mapping\ContainerAwareEntityListenerResolver
+ class: Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver
arguments:
- '@service_container'
@@ -14,6 +14,16 @@ services:
tags:
- { name: doctrine.orm.entity_listener, entity_manager: em2, lazy: true }
+ entity_listener3:
+ class: EntityListener3
+ tags:
+ - { name: doctrine.orm.entity_listener, lazy: false }
+
+ entity_listener4:
+ class: EntityListener4
+ tags:
+ - { name: doctrine.orm.entity_listener }
+
doctrine:
dbal:
default_connection: default
diff --git a/Tests/DependencyInjection/Fixtures/config/yml/orm_entity_listener_custom_resolver.yml b/Tests/DependencyInjection/Fixtures/config/yml/orm_entity_listener_custom_resolver.yml
new file mode 100644
index 000000000..32c705feb
--- /dev/null
+++ b/Tests/DependencyInjection/Fixtures/config/yml/orm_entity_listener_custom_resolver.yml
@@ -0,0 +1,23 @@
+services:
+ custom_entity_listener_resolver:
+ class: Doctrine\Bundle\DoctrineBundle\Tests\DependencyInjection\Fixtures\CustomEntityListenerServiceResolver
+ public: false
+
+ entity_listener:
+ class: EntityListener
+ public: false
+ tags:
+ - { name: doctrine.orm.entity_listener }
+
+doctrine:
+ dbal:
+ default_connection: default
+ connections:
+ default:
+ dbname: db
+
+ orm:
+ default_entity_manager: em1
+ entity_managers:
+ em1:
+ entity_listener_resolver: custom_entity_listener_resolver
diff --git a/Tests/DependencyInjection/Fixtures/config/yml/orm_entity_listener_resolver.yml b/Tests/DependencyInjection/Fixtures/config/yml/orm_entity_listener_resolver.yml
index 2d93aeeae..bab9df4a2 100644
--- a/Tests/DependencyInjection/Fixtures/config/yml/orm_entity_listener_resolver.yml
+++ b/Tests/DependencyInjection/Fixtures/config/yml/orm_entity_listener_resolver.yml
@@ -1,14 +1,14 @@
services:
entity_listener_resolver:
- class: \Doctrine\ORM\Mapping\DefaultEntityListenerResolver
+ class: Doctrine\ORM\Mapping\DefaultEntityListenerResolver
entity_listener1:
- class: \EntityListener
+ class: EntityListener
tags:
- { name: doctrine.orm.entity_listener }
entity_listener2:
- class: \EntityListener
+ class: EntityListener
tags:
- { name: doctrine.orm.entity_listener, entity_manager: em2 }
diff --git a/Tests/Mapping/ContainerAwareEntityListenerResolverTest.php b/Tests/Mapping/ContainerEntityListenerResolverTest.php
similarity index 90%
rename from Tests/Mapping/ContainerAwareEntityListenerResolverTest.php
rename to Tests/Mapping/ContainerEntityListenerResolverTest.php
index 696c77419..f6fa7bb7c 100644
--- a/Tests/Mapping/ContainerAwareEntityListenerResolverTest.php
+++ b/Tests/Mapping/ContainerEntityListenerResolverTest.php
@@ -2,14 +2,14 @@
namespace Doctrine\Bundle\DoctrineBundle\Tests\Mapping;
-use Doctrine\Bundle\DoctrineBundle\Mapping\ContainerAwareEntityListenerResolver;
+use Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject;
-use Symfony\Component\DependencyInjection\ContainerInterface;
+use Psr\Container\ContainerInterface;
-class ContainerAwareEntityListenerResolverTest extends TestCase
+class ContainerEntityListenerResolverTest extends TestCase
{
- /** @var ContainerAwareEntityListenerResolver */
+ /** @var ContainerEntityListenerResolver */
private $resolver;
/** @var ContainerInterface|PHPUnit_Framework_MockObject_MockObject */
@@ -19,8 +19,8 @@ protected function setUp()
{
parent::setUp();
- $this->container = $this->getMockForAbstractClass('\Symfony\Component\DependencyInjection\ContainerInterface');
- $this->resolver = new ContainerAwareEntityListenerResolver($this->container);
+ $this->container = $this->createMock(ContainerInterface::class);
+ $this->resolver = new ContainerEntityListenerResolver($this->container);
}
public function testResolveClass()