From 34efe40371ebeb85c5b63d883ae68134c185c658 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 1 Oct 2019 16:24:58 +0200 Subject: [PATCH] [EventDispatcher] A compiler pass for aliased userland events. --- .../Resources/config/services.xml | 4 -- .../Bundle/FrameworkBundle/composer.json | 1 + .../Bundle/SecurityBundle/SecurityBundle.php | 14 ++++++ .../DependencyInjection/EventExtension.php | 26 ++++++++++ .../Bundle/EventBundle/EventBundle.php | 18 +++++++ .../EventSubscriber/TestSubscriber.php | 38 +++++++++++++++ .../Tests/Functional/EventAliasTest.php | 48 +++++++++++++++++++ .../Functional/app/AliasedEvents/bundles.php | 20 ++++++++ .../Functional/app/AliasedEvents/config.yml | 2 + .../Component/EventDispatcher/CHANGELOG.md | 5 ++ .../AddEventAliasesPass.php | 42 ++++++++++++++++ .../RegisterListenersPassTest.php | 29 +++++++++++ .../Component/HttpKernel/composer.json | 2 +- 13 files changed, 244 insertions(+), 5 deletions(-) create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/EventBundle/DependencyInjection/EventExtension.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/EventBundle/EventBundle.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/EventBundle/EventSubscriber/TestSubscriber.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/EventAliasTest.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/bundles.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/config.yml create mode 100644 src/Symfony/Component/EventDispatcher/DependencyInjection/AddEventAliasesPass.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml index e8a11ad242..02677f149f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml @@ -23,10 +23,6 @@ kernel.view kernel.exception kernel.terminate - security.authentication.success - security.authentication.failure - security.interactive_login - security.switch_user workflow.guard workflow.leave workflow.transition diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 8b8a56ecaa..d75499f3fa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -78,6 +78,7 @@ "symfony/messenger": "<4.3", "symfony/mime": "<4.4", "symfony/property-info": "<3.4", + "symfony/security-bundle": "<4.4", "symfony/serializer": "<4.2", "symfony/stopwatch": "<3.4", "symfony/translation": "<4.4", diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index ee8da39eac..24cf5569b8 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -33,7 +33,14 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMe use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\LdapFactory; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass; use Symfony\Component\HttpKernel\Bundle\Bundle; +use Symfony\Component\Security\Core\AuthenticationEvents; +use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent; +use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; +use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; +use Symfony\Component\Security\Http\Event\SwitchUserEvent; +use Symfony\Component\Security\Http\SecurityEvents; /** * Bundle. @@ -68,5 +75,12 @@ class SecurityBundle extends Bundle $container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->addCompilerPass(new RegisterCsrfTokenClearingLogoutHandlerPass()); $container->addCompilerPass(new RegisterTokenUsageTrackingPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 200); + + $container->addCompilerPass(new AddEventAliasesPass([ + AuthenticationSuccessEvent::class => AuthenticationEvents::AUTHENTICATION_SUCCESS, + AuthenticationFailureEvent::class => AuthenticationEvents::AUTHENTICATION_FAILURE, + InteractiveLoginEvent::class => SecurityEvents::INTERACTIVE_LOGIN, + SwitchUserEvent::class => SecurityEvents::SWITCH_USER, + ])); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/EventBundle/DependencyInjection/EventExtension.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/EventBundle/DependencyInjection/EventExtension.php new file mode 100644 index 0000000000..34159fd09b --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/EventBundle/DependencyInjection/EventExtension.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\EventBundle\DependencyInjection; + +use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\EventBundle\EventSubscriber\TestSubscriber; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\Extension; + +final class EventExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container): void + { + $container->register('test_subscriber', TestSubscriber::class) + ->setPublic(true) + ->addTag('kernel.event_subscriber'); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/EventBundle/EventBundle.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/EventBundle/EventBundle.php new file mode 100644 index 0000000000..5c0ece872e --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/EventBundle/EventBundle.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\EventBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +final class EventBundle extends Bundle +{ +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/EventBundle/EventSubscriber/TestSubscriber.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/EventBundle/EventSubscriber/TestSubscriber.php new file mode 100644 index 0000000000..0a907008ec --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/EventBundle/EventSubscriber/TestSubscriber.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\EventBundle\EventSubscriber; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent; +use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; +use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; +use Symfony\Component\Security\Http\Event\SwitchUserEvent; + +final class TestSubscriber implements EventSubscriberInterface +{ + public $calledMethods = []; + + public static function getSubscribedEvents(): array + { + return [ + AuthenticationSuccessEvent::class => 'onAuthenticationSuccess', + AuthenticationFailureEvent::class => 'onAuthenticationFailure', + InteractiveLoginEvent::class => 'onInteractiveLogin', + SwitchUserEvent::class => 'onSwitchUser', + ]; + } + + public function __call(string $name, array $arguments) + { + $this->calledMethods[$name] = ($this->calledMethods[$name] ?? 0) + 1; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/EventAliasTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/EventAliasTest.php new file mode 100644 index 0000000000..3e4fa76f2e --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/EventAliasTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\AuthenticationEvents; +use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent; +use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; +use Symfony\Component\Security\Http\Event\SwitchUserEvent; +use Symfony\Component\Security\Http\SecurityEvents; + +final class EventAliasTest extends AbstractWebTestCase +{ + public function testAliasedEvents(): void + { + $client = $this->createClient(['test_case' => 'AliasedEvents', 'root_config' => 'config.yml']); + $container = $client->getContainer(); + $dispatcher = $container->get('event_dispatcher'); + + $dispatcher->dispatch(new AuthenticationSuccessEvent($this->createMock(TokenInterface::class)), AuthenticationEvents::AUTHENTICATION_SUCCESS); + $dispatcher->dispatch(new AuthenticationFailureEvent($this->createMock(TokenInterface::class), new AuthenticationException()), AuthenticationEvents::AUTHENTICATION_FAILURE); + $dispatcher->dispatch(new InteractiveLoginEvent($this->createMock(Request::class), $this->createMock(TokenInterface::class)), SecurityEvents::INTERACTIVE_LOGIN); + $dispatcher->dispatch(new SwitchUserEvent($this->createMock(Request::class), $this->createMock(UserInterface::class), $this->createMock(TokenInterface::class)), SecurityEvents::SWITCH_USER); + + $this->assertEquals( + [ + 'onAuthenticationSuccess' => 1, + 'onAuthenticationFailure' => 1, + 'onInteractiveLogin' => 1, + 'onSwitchUser' => 1, + ], + $container->get('test_subscriber')->calledMethods + ); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/bundles.php new file mode 100644 index 0000000000..d7b7c498f8 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/bundles.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\SecurityBundle\SecurityBundle; +use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\EventBundle\EventBundle; + +return [ + new FrameworkBundle(), + new SecurityBundle(), + new EventBundle(), +]; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/config.yml new file mode 100644 index 0000000000..bdd94fd0af --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/config.yml @@ -0,0 +1,2 @@ +imports: + - { resource: ./../config/framework.yml } diff --git a/src/Symfony/Component/EventDispatcher/CHANGELOG.md b/src/Symfony/Component/EventDispatcher/CHANGELOG.md index 7653cad1c0..d607b808a0 100644 --- a/src/Symfony/Component/EventDispatcher/CHANGELOG.md +++ b/src/Symfony/Component/EventDispatcher/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.4.0 +----- + +* `AddEventAliasesPass` has been added, allowing applications and bundles to extend the event alias mapping used by `RegisterListenersPass`. + 4.3.0 ----- diff --git a/src/Symfony/Component/EventDispatcher/DependencyInjection/AddEventAliasesPass.php b/src/Symfony/Component/EventDispatcher/DependencyInjection/AddEventAliasesPass.php new file mode 100644 index 0000000000..c4ea50f786 --- /dev/null +++ b/src/Symfony/Component/EventDispatcher/DependencyInjection/AddEventAliasesPass.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * This pass allows bundles to extend the list of event aliases. + * + * @author Alexander M. Turek + */ +class AddEventAliasesPass implements CompilerPassInterface +{ + private $eventAliases; + private $eventAliasesParameter; + + public function __construct(array $eventAliases, string $eventAliasesParameter = 'event_dispatcher.event_aliases') + { + $this->eventAliases = $eventAliases; + $this->eventAliasesParameter = $eventAliasesParameter; + } + + public function process(ContainerBuilder $container): void + { + $eventAliases = $container->hasParameter($this->eventAliasesParameter) ? $container->getParameter($this->eventAliasesParameter) : []; + + $container->setParameter( + $this->eventAliasesParameter, + array_merge($eventAliases, $this->eventAliases) + ); + } +} diff --git a/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php b/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php index 433762a4e8..c21d6ca1cf 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass; use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -67,6 +68,9 @@ class RegisterListenersPassTest extends TestCase $builder->register('my_event_subscriber', AliasedSubscriber::class) ->addTag('kernel.event_subscriber'); + $eventAliasPass = new AddEventAliasesPass([CustomEvent::class => 'custom_event']); + $eventAliasPass->process($builder); + $registerListenersPass = new RegisterListenersPass(); $registerListenersPass->process($builder); @@ -79,6 +83,14 @@ class RegisterListenersPassTest extends TestCase 0, ], ], + [ + 'addListener', + [ + 'custom_event', + [new ServiceClosureArgument(new Reference('my_event_subscriber')), 'onCustomEvent'], + 0, + ], + ], ]; $this->assertEquals($expectedCalls, $builder->getDefinition('event_dispatcher')->getMethodCalls()); } @@ -202,8 +214,12 @@ class RegisterListenersPassTest extends TestCase $container = new ContainerBuilder(); $container->setParameter('event_dispatcher.event_aliases', [AliasedEvent::class => 'aliased_event']); $container->register('foo', InvokableListenerService::class)->addTag('kernel.event_listener', ['event' => AliasedEvent::class, 'method' => 'onEvent']); + $container->register('bar', InvokableListenerService::class)->addTag('kernel.event_listener', ['event' => CustomEvent::class, 'method' => 'onEvent']); $container->register('event_dispatcher'); + $eventAliasPass = new AddEventAliasesPass([CustomEvent::class => 'custom_event']); + $eventAliasPass->process($container); + $registerListenersPass = new RegisterListenersPass(); $registerListenersPass->process($container); @@ -217,6 +233,14 @@ class RegisterListenersPassTest extends TestCase 0, ], ], + [ + 'addListener', + [ + 'custom_event', + [new ServiceClosureArgument(new Reference('bar')), 'onEvent'], + 0, + ], + ], ]; $this->assertEquals($expectedCalls, $definition->getMethodCalls()); } @@ -249,6 +273,7 @@ final class AliasedSubscriber implements EventSubscriberInterface { return [ AliasedEvent::class => 'onAliasedEvent', + CustomEvent::class => 'onCustomEvent', ]; } } @@ -256,3 +281,7 @@ final class AliasedSubscriber implements EventSubscriberInterface final class AliasedEvent { } + +final class CustomEvent +{ +} diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index 01b77c8665..51f0d919a7 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -19,7 +19,7 @@ "php": "^7.1.3", "symfony/error-handler": "^4.4|^5.0", "symfony/error-renderer": "^4.4|^5.0", - "symfony/event-dispatcher": "^4.3", + "symfony/event-dispatcher": "^4.4", "symfony/http-foundation": "^4.4|^5.0", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-php73": "^1.9",