diff --git a/DependencyInjection/Compiler/EntityListenerPass.php b/DependencyInjection/Compiler/EntityListenerPass.php index 02abda421..124f2eb7c 100644 --- a/DependencyInjection/Compiler/EntityListenerPass.php +++ b/DependencyInjection/Compiler/EntityListenerPass.php @@ -4,6 +4,7 @@ use Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver; use Doctrine\Bundle\DoctrineBundle\Mapping\EntityListenerServiceResolver; +use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -44,10 +45,10 @@ public function process(ContainerBuilder $container) $resolver->setPublic(true); if (isset($attributes['entity']) && isset($attributes['event'])) { - $this->attachToListener($container, $name, $id, $attributes); + $this->attachToListener($container, $name, $this->getConcreteDefinitionClass($container->findDefinition($id), $container, $id), $attributes); } - $resolverClass = $this->getResolverClass($resolver, $container); + $resolverClass = $this->getResolverClass($resolver, $container, $resolverId); $resolverSupportsLazyListeners = is_a($resolverClass, EntityListenerServiceResolver::class, true); $lazyByAttribute = isset($attributes['lazy']) && $attributes['lazy']; @@ -65,7 +66,7 @@ public function process(ContainerBuilder $container) throw new InvalidArgumentException(sprintf('The service "%s" must not be abstract as this entity listener is lazy-loaded.', $id)); } - $resolver->addMethodCall('registerService', [$listener->getClass(), $id]); + $resolver->addMethodCall('registerService', [$this->getConcreteDefinitionClass($listener, $container, $id), $id]); // if the resolver uses the default class we will use a service locator for all listeners if ($resolverClass === ContainerEntityListenerResolver::class) { @@ -87,7 +88,7 @@ public function process(ContainerBuilder $container) } } - private function attachToListener(ContainerBuilder $container, $name, $id, array $attributes) + private function attachToListener(ContainerBuilder $container, $name, string $class, array $attributes) { $listenerId = sprintf('doctrine.orm.%s_listeners.attach_entity_listeners', $name); @@ -95,32 +96,49 @@ private function attachToListener(ContainerBuilder $container, $name, $id, array return; } - $serviceDef = $container->getDefinition($id); - $args = [ $attributes['entity'], - $serviceDef->getClass(), + $class, $attributes['event'], ]; if (isset($attributes['method'])) { $args[] = $attributes['method']; - } elseif (! method_exists($serviceDef->getClass(), $attributes['event']) && method_exists($serviceDef->getClass(), '__invoke')) { + } elseif (! method_exists($class, $attributes['event']) && method_exists($class, '__invoke')) { $args[] = '__invoke'; } $container->findDefinition($listenerId)->addMethodCall('addEntityListener', $args); } - private function getResolverClass(Definition $resolver, ContainerBuilder $container) : string + private function getResolverClass(Definition $resolver, ContainerBuilder $container, string $id) : string { - $resolverClass = $resolver->getClass(); + $resolverClass = $this->getConcreteDefinitionClass($resolver, $container, $id); if (substr($resolverClass, 0, 1) === '%') { // resolve container parameter first - $resolverClass = $container->getParameterBag()->resolveValue($resolver->getClass()); + $resolverClass = $container->getParameterBag()->resolveValue($resolverClass); } return $resolverClass; } + + private function getConcreteDefinitionClass(Definition $definition, ContainerBuilder $container, string $id) : string + { + $class = $definition->getClass(); + if ($class) { + return $class; + } + + while ($definition instanceof ChildDefinition) { + $definition = $container->findDefinition($definition->getParent()); + + $class = $definition->getClass(); + if ($class) { + return $class; + } + } + + throw new InvalidArgumentException(sprintf('The service "%s" must define its class.', $id)); + } } diff --git a/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php b/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php index bab4a8aa0..0d6153d14 100644 --- a/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php +++ b/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php @@ -860,7 +860,8 @@ public function testAttachEntityListenerTag() ['EntityListener1', 'entity_listener1'], ['Doctrine\Bundle\DoctrineBundle\Tests\DependencyInjection\Fixtures\InvokableEntityListener', 'invokable_entity_listener'], ['Doctrine\Bundle\DoctrineBundle\Tests\DependencyInjection\Fixtures\InvokableEntityListener', 'invokable_entity_listener'], - ], 3); + ['ParentEntityListener', 'children_entity_listener'], + ], 4); $listener = $container->getDefinition('doctrine.orm.em2_entity_listener_resolver'); $this->assertDICDefinitionMethodCallOnce($listener, 'registerService', ['EntityListener2', 'entity_listener2']); @@ -869,6 +870,7 @@ public function testAttachEntityListenerTag() $this->assertDICDefinitionMethodCallAt(0, $attachListener, 'addEntityListener', ['My/Entity1', 'EntityListener1', 'postLoad']); $this->assertDICDefinitionMethodCallAt(1, $attachListener, 'addEntityListener', ['My/Entity1', 'Doctrine\Bundle\DoctrineBundle\Tests\DependencyInjection\Fixtures\InvokableEntityListener', 'loadClassMetadata', '__invoke']); $this->assertDICDefinitionMethodCallAt(2, $attachListener, 'addEntityListener', ['My/Entity1', 'Doctrine\Bundle\DoctrineBundle\Tests\DependencyInjection\Fixtures\InvokableEntityListener', 'postPersist']); + $this->assertDICDefinitionMethodCallAt(3, $attachListener, 'addEntityListener', ['My/Entity3', 'ParentEntityListener', 'postLoad']); $attachListener = $container->getDefinition('doctrine.orm.em2_listeners.attach_entity_listeners'); $this->assertDICDefinitionMethodCallOnce($attachListener, 'addEntityListener', ['My/Entity2', 'EntityListener2', 'preFlush', 'preFlushHandler']); diff --git a/Tests/DependencyInjection/Fixtures/config/xml/orm_attach_entity_listener_tag.xml b/Tests/DependencyInjection/Fixtures/config/xml/orm_attach_entity_listener_tag.xml index e8fd2a2b4..16fc5cc5d 100644 --- a/Tests/DependencyInjection/Fixtures/config/xml/orm_attach_entity_listener_tag.xml +++ b/Tests/DependencyInjection/Fixtures/config/xml/orm_attach_entity_listener_tag.xml @@ -19,6 +19,14 @@ + + + + + + + + diff --git a/Tests/DependencyInjection/Fixtures/config/yml/orm_attach_entity_listener_tag.yml b/Tests/DependencyInjection/Fixtures/config/yml/orm_attach_entity_listener_tag.yml index 346ee27f6..d3d4b5197 100644 --- a/Tests/DependencyInjection/Fixtures/config/yml/orm_attach_entity_listener_tag.yml +++ b/Tests/DependencyInjection/Fixtures/config/yml/orm_attach_entity_listener_tag.yml @@ -2,12 +2,12 @@ services: entity_listener1: class: EntityListener1 tags: - - { name: doctrine.orm.entity_listener, entity: My/Entity1, event: postLoad} + - { name: doctrine.orm.entity_listener, entity: My/Entity1, event: postLoad } entity_listener2: class: EntityListener2 tags: - - { name: doctrine.orm.entity_listener, entity_manager: em2, entity: My/Entity2, event: preFlush, method: preFlushHandler} + - { name: doctrine.orm.entity_listener, entity_manager: em2, entity: My/Entity2, event: preFlush, method: preFlushHandler } invokable_entity_listener: class: Doctrine\Bundle\DoctrineBundle\Tests\DependencyInjection\Fixtures\InvokableEntityListener @@ -15,6 +15,18 @@ services: - { name: doctrine.orm.entity_listener, entity: My/Entity1, event: loadClassMetadata } - { name: doctrine.orm.entity_listener, entity: My/Entity1, event: postPersist } + grand_parent_entity_listener: + class: GrandParentEntityListener + + parent_entity_listener: + parent: grand_parent_entity_listener + class: ParentEntityListener + + children_entity_listener: + parent: parent_entity_listener + tags: + - { name: doctrine.orm.entity_listener, entity: My/Entity3, event: postLoad } + doctrine: dbal: default_connection: default