From 8b240d4c22852ff76faced47ab7ab87e73c32092 Mon Sep 17 00:00:00 2001 From: Jordan Alliot Date: Tue, 23 Aug 2011 20:35:38 +0100 Subject: [PATCH 1/3] Implementation of kernel.event_subscriber tag for services. --- .../ContainerAwareEventDispatcher.php | 27 ++++++ .../Compiler/RegisterKernelListenersPass.php | 13 +++ .../ContainerAwareEventDispatcherTest.php | 44 +++++++++ .../RegisterKernelListenersPassTest.php | 89 +++++++++++++++++++ 4 files changed, 173 insertions(+) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/RegisterKernelListenersPassTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/ContainerAwareEventDispatcher.php b/src/Symfony/Bundle/FrameworkBundle/ContainerAwareEventDispatcher.php index f73d9fcdc5..102bedb2d5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/ContainerAwareEventDispatcher.php +++ b/src/Symfony/Bundle/FrameworkBundle/ContainerAwareEventDispatcher.php @@ -21,6 +21,7 @@ use Symfony\Component\EventDispatcher\Event; * * @author Fabien Potencier * @author Bernhard Schussek + * @author Jordan Alliot */ class ContainerAwareEventDispatcher extends EventDispatcher { @@ -103,6 +104,32 @@ class ContainerAwareEventDispatcher extends EventDispatcher return parent::getListeners($eventName); } + /** + * Adds a service as event subscriber + * + * If this service is created by a factory, its class value must be correctly filled. + * The service's class must implement Symfony\Component\EventDispatcher\EventSubscriberInterface. + * + * @param string $serviceId The service ID of the subscriber service + * @param string $class The service's class name + */ + public function addSubscriberService($serviceId, $class) + { + $refClass = new \ReflectionClass($class); + $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; + if (!$refClass->implementsInterface($interface)) { + throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $serviceId, $interface)); + } + + foreach ($class::getSubscribedEvents() as $eventName => $params) { + if (is_string($params)) { + $this->listenerIds[$eventName][] = array($serviceId, $params, 0); + } else { + $this->listenerIds[$eventName][] = array($serviceId, $params[0], $params[1]); + } + } + } + /** * {@inheritDoc} * diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/RegisterKernelListenersPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/RegisterKernelListenersPass.php index 656c08b26f..4d9daf0c3f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/RegisterKernelListenersPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/RegisterKernelListenersPass.php @@ -43,5 +43,18 @@ class RegisterKernelListenersPass implements CompilerPassInterface $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority)); } } + + foreach ($container->findTaggedServiceIds('kernel.event_subscriber') as $id => $attributes) { + // We must assume that the class value has been correcly filled, even if the service is created by a factory + $class = $container->getDefinition($id)->getClass(); + + $refClass = new \ReflectionClass($class); + $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; + if (!$refClass->implementsInterface($interface)) { + throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); + } + + $definition->addMethodCall('addSubscriberService', array($id, $class)); + } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/ContainerAwareEventDispatcherTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/ContainerAwareEventDispatcherTest.php index d32818e95f..0083a88b43 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/ContainerAwareEventDispatcherTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/ContainerAwareEventDispatcherTest.php @@ -14,6 +14,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests; use Symfony\Component\DependencyInjection\Container; use Symfony\Bundle\FrameworkBundle\ContainerAwareEventDispatcher; use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\DependencyInjection\Scope; class ContainerAwareEventDispatcherTest extends \PHPUnit_Framework_TestCase @@ -39,6 +40,36 @@ class ContainerAwareEventDispatcherTest extends \PHPUnit_Framework_TestCase $dispatcher->dispatch('onEvent', $event); } + public function testAddASubscriberService() + { + $event = new Event(); + + $service = $this->getMock('Symfony\Bundle\FrameworkBundle\Tests\SubscriberService'); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container = new Container(); + $container->set('service.subscriber', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addSubscriberService('service.subscriber', 'Symfony\Bundle\FrameworkBundle\Tests\SubscriberService'); + + $dispatcher->dispatch('onEvent', $event); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testTriggerASubscriberDoesntImplementInterface() + { + $dispatcher = new ContainerAwareEventDispatcher(new Container()); + $dispatcher->addSubscriberService('service.subscriber', 'Symfony\Bundle\FrameworkBundle\Tests\Service'); + } + public function testPreventDuplicateListenerService() { $event = new Event(); @@ -174,3 +205,16 @@ class Service { } } + +class SubscriberService implements EventSubscriberInterface +{ + static function getSubscribedEvents() { + return array( + 'onEvent' => 'onEvent', + ); + } + + function onEvent(Event $e) + { + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/RegisterKernelListenersPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/RegisterKernelListenersPassTest.php new file mode 100644 index 0000000000..8e301383f8 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/RegisterKernelListenersPassTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RegisterKernelListenersPass; + +class RegisterKernelListenersPassTest extends \PHPUnit_Framework_TestCase +{ + /** + * Tests that event subscribers not implementing EventSubscriberInterface + * trigger an exception. + * + * @expectedException \InvalidArgumentException + */ + public function testEventSubscriberWithoutInterface() + { + // one service, not implementing any interface + $services = array( + 'my_event_subscriber' => array(0 => array()), + ); + + $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('stdClass')); + + $builder = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder'); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.event_listener here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->onConsecutiveCalls(array(), $services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->returnValue($definition)); + + $registerListenersPass = new RegisterKernelListenersPass(); + $registerListenersPass->process($builder); + } + + public function testValidEventSubscriber() + { + $services = array( + 'my_event_subscriber' => array(0 => array()), + ); + + $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\SubscriberService')); + + $builder = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder'); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.event_listener here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->onConsecutiveCalls(array(), $services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->returnValue($definition)); + + $registerListenersPass = new RegisterKernelListenersPass(); + $registerListenersPass->process($builder); + } +} + +class SubscriberService implements \Symfony\Component\EventDispatcher\EventSubscriberInterface +{ + static function getSubscribedEvents() {} +} From 21cf0ac5d73da703db70c3ed09959d9a781233c1 Mon Sep 17 00:00:00 2001 From: Jordan Alliot Date: Thu, 29 Sep 2011 17:49:53 +0200 Subject: [PATCH 2/3] Backported new behaviour from PR #2148 and removed check for interface at run-time --- .../ContainerAwareEventDispatcher.php | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/ContainerAwareEventDispatcher.php b/src/Symfony/Bundle/FrameworkBundle/ContainerAwareEventDispatcher.php index 102bedb2d5..44fc5c1d3e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/ContainerAwareEventDispatcher.php +++ b/src/Symfony/Bundle/FrameworkBundle/ContainerAwareEventDispatcher.php @@ -107,25 +107,20 @@ class ContainerAwareEventDispatcher extends EventDispatcher /** * Adds a service as event subscriber * - * If this service is created by a factory, its class value must be correctly filled. - * The service's class must implement Symfony\Component\EventDispatcher\EventSubscriberInterface. - * * @param string $serviceId The service ID of the subscriber service - * @param string $class The service's class name + * @param string $class The service's class name (which must implement EventSubscriberInterface) */ public function addSubscriberService($serviceId, $class) { - $refClass = new \ReflectionClass($class); - $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; - if (!$refClass->implementsInterface($interface)) { - throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $serviceId, $interface)); - } - foreach ($class::getSubscribedEvents() as $eventName => $params) { if (is_string($params)) { $this->listenerIds[$eventName][] = array($serviceId, $params, 0); - } else { + } elseif (is_string($params[0])) { $this->listenerIds[$eventName][] = array($serviceId, $params[0], $params[1]); + } else { + foreach ($params as $listener) { + $this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0); + } } } } From 3223c5a1f3c153a586d48b327f41bca8e883767d Mon Sep 17 00:00:00 2001 From: Jordan Alliot Date: Thu, 29 Sep 2011 18:17:30 +0200 Subject: [PATCH 3/3] Removed now useless test --- .../Tests/ContainerAwareEventDispatcherTest.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/ContainerAwareEventDispatcherTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/ContainerAwareEventDispatcherTest.php index 0083a88b43..41b3da66b4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/ContainerAwareEventDispatcherTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/ContainerAwareEventDispatcherTest.php @@ -61,15 +61,6 @@ class ContainerAwareEventDispatcherTest extends \PHPUnit_Framework_TestCase $dispatcher->dispatch('onEvent', $event); } - /** - * @expectedException \InvalidArgumentException - */ - public function testTriggerASubscriberDoesntImplementInterface() - { - $dispatcher = new ContainerAwareEventDispatcher(new Container()); - $dispatcher->addSubscriberService('service.subscriber', 'Symfony\Bundle\FrameworkBundle\Tests\Service'); - } - public function testPreventDuplicateListenerService() { $event = new Event();