From b3ac93829a12032c8deb2175f05decfd627762ed Mon Sep 17 00:00:00 2001 From: David Maicher Date: Thu, 3 May 2018 09:30:44 +0200 Subject: [PATCH] [Doctrine Bridge] fix priority for doctrine event listeners --- ...gisterEventListenersAndSubscribersPass.php | 146 ++++++++--------- ...erEventListenersAndSubscribersPassTest.php | 155 ++++++++++++++---- 2 files changed, 194 insertions(+), 107 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php index e7015fd1e4119..3ffd924e519e3 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php @@ -11,22 +11,22 @@ namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; /** * Registers event listeners and subscribers to the available doctrine connections. * * @author Jeremy Mikola * @author Alexander + * @author David Maicher */ class RegisterEventListenersAndSubscribersPass implements CompilerPassInterface { private $connections; - private $container; private $eventManagers; private $managerTemplate; private $tagPrefix; @@ -53,97 +53,97 @@ public function process(ContainerBuilder $container) return; } - $taggedSubscribers = $container->findTaggedServiceIds($this->tagPrefix.'.event_subscriber', true); - $taggedListeners = $container->findTaggedServiceIds($this->tagPrefix.'.event_listener', true); - - if (empty($taggedSubscribers) && empty($taggedListeners)) { - return; - } - - $this->container = $container; $this->connections = $container->getParameter($this->connections); - $sortFunc = function ($a, $b) { - $a = isset($a['priority']) ? $a['priority'] : 0; - $b = isset($b['priority']) ? $b['priority'] : 0; - - return $a > $b ? -1 : 1; - }; - - if (!empty($taggedSubscribers)) { - $subscribersPerCon = $this->groupByConnection($taggedSubscribers); - foreach ($subscribersPerCon as $con => $subscribers) { - $em = $this->getEventManager($con); + $this->addTaggedSubscribers($container); + $this->addTaggedListeners($container); + } - uasort($subscribers, $sortFunc); - foreach ($subscribers as $id => $instance) { - $em->addMethodCall('addEventSubscriber', array(new Reference($id))); + private function addTaggedSubscribers(ContainerBuilder $container) + { + $subscriberTag = $this->tagPrefix.'.event_subscriber'; + $taggedSubscribers = $this->findAndSortTags($subscriberTag, $container); + + foreach ($taggedSubscribers as $taggedSubscriber) { + list($id, $tag) = $taggedSubscriber; + $connections = isset($tag['connection']) ? array($tag['connection']) : array_keys($this->connections); + foreach ($connections as $con) { + if (!isset($this->connections[$con])) { + throw new RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s', $con, $taggedSubscriber, implode(', ', array_keys($this->connections)))); } - } - } - if (!empty($taggedListeners)) { - $listenersPerCon = $this->groupByConnection($taggedListeners, true); - foreach ($listenersPerCon as $con => $listeners) { - $em = $this->getEventManager($con); - - uasort($listeners, $sortFunc); - foreach ($listeners as $id => $instance) { - $em->addMethodCall('addEventListener', array( - array_unique($instance['event']), - isset($instance['lazy']) && $instance['lazy'] ? $id : new Reference($id), - )); - } + $this->getEventManagerDef($container, $con)->addMethodCall('addEventSubscriber', array(new Reference($id))); } } } - private function groupByConnection(array $services, $isListener = false) + private function addTaggedListeners(ContainerBuilder $container) { - $grouped = array(); - foreach ($allCons = array_keys($this->connections) as $con) { - $grouped[$con] = array(); - } + $listenerTag = $this->tagPrefix.'.event_listener'; + $taggedListeners = $this->findAndSortTags($listenerTag, $container); + + foreach ($taggedListeners as $taggedListener) { + list($id, $tag) = $taggedListener; + $taggedListenerDef = $container->getDefinition($id); + if (!isset($tag['event'])) { + throw new InvalidArgumentException(sprintf('Doctrine event listener "%s" must specify the "event" attribute.', $id)); + } - foreach ($services as $id => $instances) { - foreach ($instances as $instance) { - if ($isListener) { - if (!isset($instance['event'])) { - throw new InvalidArgumentException(sprintf('Doctrine event listener "%s" must specify the "event" attribute.', $id)); - } - $instance['event'] = array($instance['event']); - - if (!empty($instance['lazy'])) { - $this->container->getDefinition($id)->setPublic(true); - } + $connections = isset($tag['connection']) ? array($tag['connection']) : array_keys($this->connections); + foreach ($connections as $con) { + if (!isset($this->connections[$con])) { + throw new RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s', $con, $id, implode(', ', array_keys($this->connections)))); } - $cons = isset($instance['connection']) ? array($instance['connection']) : $allCons; - foreach ($cons as $con) { - if (!isset($grouped[$con])) { - throw new RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s', $con, $id, implode(', ', array_keys($this->connections)))); - } - - if ($isListener && isset($grouped[$con][$id])) { - $grouped[$con][$id]['event'] = array_merge($grouped[$con][$id]['event'], $instance['event']); - } else { - $grouped[$con][$id] = $instance; - } + if ($lazy = !empty($tag['lazy'])) { + $taggedListenerDef->setPublic(true); } + + // we add one call per event per service so we have the correct order + $this->getEventManagerDef($container, $con)->addMethodCall('addEventListener', array(array($tag['event']), $lazy ? $id : new Reference($id))); } } + } + + private function getEventManagerDef(ContainerBuilder $container, $name) + { + if (!isset($this->eventManagers[$name])) { + $this->eventManagers[$name] = $container->getDefinition(sprintf($this->managerTemplate, $name)); + } - return $grouped; + return $this->eventManagers[$name]; } - private function getEventManager($name) + /** + * Finds and orders all service tags with the given name by their priority. + * + * The order of additions must be respected for services having the same priority, + * and knowing that the \SplPriorityQueue class does not respect the FIFO method, + * we should not use this class. + * + * @see https://bugs.php.net/bug.php?id=53710 + * @see https://bugs.php.net/bug.php?id=60926 + * + * @param string $tagName + * @param ContainerBuilder $container + * + * @return array + */ + private function findAndSortTags($tagName, ContainerBuilder $container) { - if (null === $this->eventManagers) { - $this->eventManagers = array(); - foreach ($this->connections as $n => $id) { - $this->eventManagers[$n] = $this->container->getDefinition(sprintf($this->managerTemplate, $n)); + $sortedTags = array(); + + foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $tags) { + foreach ($tags as $attributes) { + $priority = isset($attributes['priority']) ? $attributes['priority'] : 0; + $sortedTags[$priority][] = array($serviceId, $attributes); } } - return $this->eventManagers[$name]; + if ($sortedTags) { + krsort($sortedTags); + $sortedTags = call_user_func_array('array_merge', $sortedTags); + } + + return $sortedTags; } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php index 25776f86695af..7e99a7d9356c2 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php @@ -15,6 +15,7 @@ use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterEventListenersAndSubscribersPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; class RegisterEventListenersAndSubscribersPassTest extends TestCase { @@ -56,12 +57,18 @@ public function testProcessEventListenersWithPriorities() $container ->register('a', 'stdClass') + ->setPublic(false) + ->addTag('doctrine.event_listener', array( + 'event' => 'bar', + )) ->addTag('doctrine.event_listener', array( 'event' => 'foo', 'priority' => -5, )) ->addTag('doctrine.event_listener', array( - 'event' => 'bar', + 'event' => 'foo_bar', + 'priority' => 3, + 'lazy' => true, )) ; $container @@ -70,12 +77,34 @@ public function testProcessEventListenersWithPriorities() 'event' => 'foo', )) ; + $container + ->register('c', 'stdClass') + ->addTag('doctrine.event_listener', array( + 'event' => 'foo_bar', + 'priority' => 4, + )) + ; $this->process($container); - $this->assertEquals(array('b', 'a'), $this->getServiceOrder($container, 'addEventListener')); - - $calls = $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls(); - $this->assertEquals(array('foo', 'bar'), $calls[1][1][0]); + $methodCalls = $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls(); + + $this->assertEquals( + array( + array('addEventListener', array(array('foo_bar'), new Reference('c'))), + array('addEventListener', array(array('foo_bar'), new Reference('a'))), + array('addEventListener', array(array('bar'), new Reference('a'))), + array('addEventListener', array(array('foo'), new Reference('b'))), + array('addEventListener', array(array('foo'), new Reference('a'))), + ), + $methodCalls + ); + + // not lazy so must be reference + $this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $methodCalls[0][1][1]); + + // lazy so id instead of reference and must mark service public + $this->assertSame('a', $methodCalls[1][1][1]); + $this->assertTrue($container->getDefinition('a')->isPublic()); } public function testProcessEventListenersWithMultipleConnections() @@ -88,15 +117,86 @@ public function testProcessEventListenersWithMultipleConnections() 'event' => 'onFlush', )) ; + + $container + ->register('b', 'stdClass') + ->addTag('doctrine.event_listener', array( + 'event' => 'onFlush', + 'connection' => 'default', + )) + ; + + $container + ->register('c', 'stdClass') + ->addTag('doctrine.event_listener', array( + 'event' => 'onFlush', + 'connection' => 'second', + )) + ; + $this->process($container); - $callsDefault = $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls(); + $this->assertEquals( + array( + array('addEventListener', array(array('onFlush'), new Reference('a'))), + array('addEventListener', array(array('onFlush'), new Reference('b'))), + ), + $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls() + ); + + $this->assertEquals( + array( + array('addEventListener', array(array('onFlush'), new Reference('a'))), + array('addEventListener', array(array('onFlush'), new Reference('c'))), + ), + $container->getDefinition('doctrine.dbal.second_connection.event_manager')->getMethodCalls() + ); + } - $this->assertEquals('addEventListener', $callsDefault[0][0]); - $this->assertEquals(array('onFlush'), $callsDefault[0][1][0]); + public function testProcessEventSubscribersWithMultipleConnections() + { + $container = $this->createBuilder(true); - $callsSecond = $container->getDefinition('doctrine.dbal.second_connection.event_manager')->getMethodCalls(); - $this->assertEquals($callsDefault, $callsSecond); + $container + ->register('a', 'stdClass') + ->addTag('doctrine.event_subscriber', array( + 'event' => 'onFlush', + )) + ; + + $container + ->register('b', 'stdClass') + ->addTag('doctrine.event_subscriber', array( + 'event' => 'onFlush', + 'connection' => 'default', + )) + ; + + $container + ->register('c', 'stdClass') + ->addTag('doctrine.event_subscriber', array( + 'event' => 'onFlush', + 'connection' => 'second', + )) + ; + + $this->process($container); + + $this->assertEquals( + array( + array('addEventSubscriber', array(new Reference('a'))), + array('addEventSubscriber', array(new Reference('b'))), + ), + $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls() + ); + + $this->assertEquals( + array( + array('addEventSubscriber', array(new Reference('a'))), + array('addEventSubscriber', array(new Reference('c'))), + ), + $container->getDefinition('doctrine.dbal.second_connection.event_manager')->getMethodCalls() + ); } public function testProcessEventSubscribersWithPriorities() @@ -133,11 +233,17 @@ public function testProcessEventSubscribersWithPriorities() ; $this->process($container); - $serviceOrder = $this->getServiceOrder($container, 'addEventSubscriber'); - $unordered = array_splice($serviceOrder, 0, 3); - sort($unordered); - $this->assertEquals(array('c', 'd', 'e'), $unordered); - $this->assertEquals(array('b', 'a'), $serviceOrder); + + $this->assertEquals( + array( + array('addEventSubscriber', array(new Reference('c'))), + array('addEventSubscriber', array(new Reference('d'))), + array('addEventSubscriber', array(new Reference('e'))), + array('addEventSubscriber', array(new Reference('b'))), + array('addEventSubscriber', array(new Reference('a'))), + ), + $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls() + ); } public function testProcessNoTaggedServices() @@ -157,25 +263,6 @@ private function process(ContainerBuilder $container) $pass->process($container); } - private function getServiceOrder(ContainerBuilder $container, $method) - { - $order = array(); - foreach ($container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls() as list($name, $arguments)) { - if ($method !== $name) { - continue; - } - - if ('addEventListener' === $name) { - $order[] = (string) $arguments[1]; - continue; - } - - $order[] = (string) $arguments[0]; - } - - return $order; - } - private function createBuilder($multipleConnections = false) { $container = new ContainerBuilder(); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy