From 2ab3caf0806ace9e5c1fdaec14bb94391b0fc8e1 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 11 Feb 2021 17:42:06 +0100 Subject: [PATCH] [DependencyInjection] Autoconfigurable attributes --- .../FrameworkExtension.php | 5 + .../DependencyInjection/CHANGELOG.md | 1 + .../AttributeAutoconfigurationPass.php | 57 +++++++++ .../Compiler/PassConfig.php | 1 + .../DependencyInjection/ContainerBuilder.php | 31 +++++ .../Tests/Compiler/IntegrationTest.php | 121 ++++++++++++++++++ .../Attribute/CustomAutoconfiguration.php | 22 ++++ .../Tests/Fixtures/TaggedService1.php | 20 +++ .../Tests/Fixtures/TaggedService2.php | 19 +++ .../Tests/Fixtures/TaggedService3.php | 31 +++++ .../Fixtures/TaggedService3Configurator.php | 20 +++ .../Attribute/EventListener.php | 28 ++++ .../Component/EventDispatcher/CHANGELOG.md | 5 + .../RegisterListenersPassTest.php | 95 +++++++++++++- .../Tests/Fixtures/CustomEvent.php | 16 +++ .../Fixtures/TaggedInvokableListener.php | 22 ++++ .../Tests/Fixtures/TaggedMultiListener.php | 32 +++++ 17 files changed, 522 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomAutoconfiguration.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService1.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService2.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService3.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService3Configurator.php create mode 100644 src/Symfony/Component/EventDispatcher/Attribute/EventListener.php create mode 100644 src/Symfony/Component/EventDispatcher/Tests/Fixtures/CustomEvent.php create mode 100644 src/Symfony/Component/EventDispatcher/Tests/Fixtures/TaggedInvokableListener.php create mode 100644 src/Symfony/Component/EventDispatcher/Tests/Fixtures/TaggedMultiListener.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 3b7f8ef35d..d41e0a97b9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -58,6 +58,7 @@ use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\EventDispatcher\Attribute\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\Finder\Finder; @@ -549,6 +550,10 @@ class FrameworkExtension extends Extension $container->registerForAutoconfiguration(LoggerAwareInterface::class) ->addMethodCall('setLogger', [new Reference('logger')]); + $container->registerAttributeForAutoconfiguration(EventListener::class, static function (ChildDefinition $definition, EventListener $attribute): void { + $definition->addTag('kernel.event_listener', get_object_vars($attribute)); + }); + if (!$container->getParameter('kernel.debug')) { // remove tagged iterator argument for resource checkers $container->getDefinition('config_cache_factory')->setArguments([]); diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 4dd8a1da63..a6c60db0e2 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Add `ServicesConfigurator::remove()` in the PHP-DSL * Add `%env(not:...)%` processor to negate boolean values * Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8 + * Add autoconfigurable attributes 5.2.0 ----- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php new file mode 100644 index 0000000000..ba6478d66d --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * @author Alexander M. Turek + */ +final class AttributeAutoconfigurationPass implements CompilerPassInterface +{ + private $ignoreAttributesTag; + + public function __construct(string $ignoreAttributesTag = 'container.ignore_attributes') + { + $this->ignoreAttributesTag = $ignoreAttributesTag; + } + + public function process(ContainerBuilder $container): void + { + if (80000 > \PHP_VERSION_ID) { + return; + } + + $autoconfiguredAttributes = $container->getAutoconfiguredAttributes(); + + foreach ($container->getDefinitions() as $id => $definition) { + if (!$definition->isAutoconfigured() + || $definition->isAbstract() + || $definition->hasTag($this->ignoreAttributesTag) + || !($reflector = $container->getReflectionClass($definition->getClass(), false)) + ) { + continue; + } + + $instanceof = $definition->getInstanceofConditionals(); + $conditionals = $instanceof[$reflector->getName()] ?? new ChildDefinition(''); + foreach ($reflector->getAttributes() as $attribute) { + if ($configurator = $autoconfiguredAttributes[$attribute->getName()] ?? null) { + $configurator($conditionals, $attribute->newInstance(), $reflector); + } + } + $instanceof[$reflector->getName()] = $conditionals; + $definition->setInstanceofConditionals($instanceof); + } + } +} diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php index 29e7218bc6..15febf9f70 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php @@ -43,6 +43,7 @@ class PassConfig 100 => [ new ResolveClassPass(), new RegisterAutoconfigureAttributesPass(), + new AttributeAutoconfigurationPass(), new ResolveInstanceofConditionalsPass(), new RegisterEnvVarProcessorsPass(), ], diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 15d6d16adc..a3be650343 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -123,6 +123,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface private $autoconfiguredInstanceof = []; + /** + * @var callable[] + */ + private $autoconfiguredAttributes = []; + private $removedIds = []; private $removedBindingIds = []; @@ -671,6 +676,14 @@ class ContainerBuilder extends Container implements TaggedContainerInterface $this->autoconfiguredInstanceof[$interface] = $childDefinition; } + + foreach ($container->getAutoconfiguredAttributes() as $attribute => $configurator) { + if (isset($this->autoconfiguredAttributes[$attribute])) { + throw new InvalidArgumentException(sprintf('"%s" has already been autoconfigured and merge() does not support merging autoconfiguration for the same attribute.', $attribute)); + } + + $this->autoconfiguredAttributes[$attribute] = $configurator; + } } /** @@ -1309,6 +1322,16 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return $this->autoconfiguredInstanceof[$interface]; } + /** + * Registers an attribute that will be used for autoconfiguring annotated classes. + * + * The configurator will receive a Definition instance and an instance of the attribute, in that order. + */ + public function registerAttributeForAutoconfiguration(string $attributeClass, callable $configurator): void + { + $this->autoconfiguredAttributes[$attributeClass] = $configurator; + } + /** * Registers an autowiring alias that only binds to a specific argument name. * @@ -1338,6 +1361,14 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return $this->autoconfiguredInstanceof; } + /** + * @return callable[] + */ + public function getAutoconfiguredAttributes(): array + { + return $this->autoconfiguredAttributes; + } + /** * Resolves env parameter placeholders in a string or an array. * diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index 9f059a80d9..6ab2cce238 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -16,14 +16,22 @@ use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomAutoconfiguration; use Symfony\Component\DependencyInjection\Tests\Fixtures\BarTagClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedForDefaultPriorityClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooTagClass; +use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService1; +use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService2; +use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3; +use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3Configurator; use Symfony\Contracts\Service\ServiceProviderInterface; use Symfony\Contracts\Service\ServiceSubscriberInterface; @@ -506,6 +514,109 @@ class IntegrationTest extends TestCase ]; $this->assertSame($expected, ['baz' => $serviceLocator->get('baz')]); } + + /** + * @requires PHP 8 + */ + public function testTagsViaAttribute() + { + $container = new ContainerBuilder(); + $container->registerAttributeForAutoconfiguration( + CustomAutoconfiguration::class, + static function (ChildDefinition $definition, CustomAutoconfiguration $attribute, \ReflectionClass $reflector) { + $definition->addTag('app.custom_tag', get_object_vars($attribute) + ['class' => $reflector->getName()]); + } + ); + + $container->register('one', TaggedService1::class) + ->setPublic(true) + ->setAutoconfigured(true); + $container->register('two', TaggedService2::class) + ->addTag('app.custom_tag', ['info' => 'This tag is not autoconfigured']) + ->setPublic(true) + ->setAutoconfigured(true); + + $collector = new TagCollector(); + $container->addCompilerPass($collector); + + $container->compile(); + + self::assertSame([ + 'one' => [ + ['someAttribute' => 'one', 'priority' => 0, 'class' => TaggedService1::class], + ['someAttribute' => 'two', 'priority' => 0, 'class' => TaggedService1::class], + ], + 'two' => [ + ['info' => 'This tag is not autoconfigured'], + ['someAttribute' => 'prio 100', 'priority' => 100, 'class' => TaggedService2::class], + ], + ], $collector->collectedTags); + } + + /** + * @requires PHP 8 + */ + public function testAttributesAreIgnored() + { + $container = new ContainerBuilder(); + $container->registerAttributeForAutoconfiguration( + CustomAutoconfiguration::class, + static function (Definition $definition, CustomAutoconfiguration $attribute) { + $definition->addTag('app.custom_tag', get_object_vars($attribute)); + } + ); + + $container->register('one', TaggedService1::class) + ->setPublic(true) + ->addTag('container.ignore_attributes') + ->setAutoconfigured(true); + $container->register('two', TaggedService2::class) + ->setPublic(true) + ->setAutoconfigured(true); + + $collector = new TagCollector(); + $container->addCompilerPass($collector); + + $container->compile(); + + self::assertSame([ + 'two' => [ + ['someAttribute' => 'prio 100', 'priority' => 100], + ], + ], $collector->collectedTags); + } + + /** + * @requires PHP 8 + */ + public function testAutoconfigureViaAttribute() + { + $container = new ContainerBuilder(); + $container->registerAttributeForAutoconfiguration( + CustomAutoconfiguration::class, + static function (ChildDefinition $definition) { + $definition + ->addMethodCall('doSomething', [1, 2, 3]) + ->setBindings(['string $foo' => 'bar']) + ->setConfigurator(new Reference('my_configurator')) + ; + } + ); + + $container->register('my_configurator', TaggedService3Configurator::class); + $container->register('three', TaggedService3::class) + ->setPublic(true) + ->setAutoconfigured(true); + + $container->compile(); + + /** @var TaggedService3 $service */ + $service = $container->get('three'); + + self::assertSame('bar', $service->foo); + self::assertSame(6, $service->sum); + self::assertTrue($service->hasBeenConfigured); + } } class ServiceSubscriberStub implements ServiceSubscriberInterface @@ -566,3 +677,13 @@ class IntegrationTestStubParent { } } + +final class TagCollector implements CompilerPassInterface +{ + public $collectedTags; + + public function process(ContainerBuilder $container): void + { + $this->collectedTags = $container->findTaggedServiceIds('app.custom_tag'); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomAutoconfiguration.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomAutoconfiguration.php new file mode 100644 index 0000000000..e668834deb --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomAutoconfiguration.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute; + +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +final class CustomAutoconfiguration +{ + public function __construct( + public string $someAttribute, + public int $priority = 0, + ) { + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService1.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService1.php new file mode 100644 index 0000000000..ce05326022 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService1.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomAutoconfiguration; + +#[CustomAutoconfiguration(someAttribute: 'one')] +#[CustomAutoconfiguration(someAttribute: 'two')] +final class TaggedService1 +{ +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService2.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService2.php new file mode 100644 index 0000000000..c282a88541 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService2.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomAutoconfiguration; + +#[CustomAutoconfiguration(someAttribute: 'prio 100', priority: 100)] +final class TaggedService2 +{ +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService3.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService3.php new file mode 100644 index 0000000000..d13341aa9b --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService3.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomAutoconfiguration; + +#[CustomAutoconfiguration(someAttribute: 'three')] +final class TaggedService3 +{ + public int $sum = 0; + public bool $hasBeenConfigured = false; + + public function __construct( + public string $foo, + ) { + } + + public function doSomething(int $a, int $b, int $c): void + { + $this->sum = $a + $b + $c; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService3Configurator.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService3Configurator.php new file mode 100644 index 0000000000..ae5c1307f2 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService3Configurator.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +final class TaggedService3Configurator +{ + public function __invoke(TaggedService3 $service) + { + $service->hasBeenConfigured = true; + } +} diff --git a/src/Symfony/Component/EventDispatcher/Attribute/EventListener.php b/src/Symfony/Component/EventDispatcher/Attribute/EventListener.php new file mode 100644 index 0000000000..e82c69e891 --- /dev/null +++ b/src/Symfony/Component/EventDispatcher/Attribute/EventListener.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Attribute; + +/** + * Service tag to autoconfigure event listeners. + * + * @author Alexander M. Turek + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +class EventListener +{ + public function __construct( + public ?string $event = null, + public ?string $method = null, + public int $priority = 0 + ) { + } +} diff --git a/src/Symfony/Component/EventDispatcher/CHANGELOG.md b/src/Symfony/Component/EventDispatcher/CHANGELOG.md index 92a3b8bfc4..4af07cdc33 100644 --- a/src/Symfony/Component/EventDispatcher/CHANGELOG.md +++ b/src/Symfony/Component/EventDispatcher/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3 +--- + + * Add `EventListener` attribute for declaring listeners on PHP 8. + 5.1.0 ----- diff --git a/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php b/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php index bf2cebf6c0..7ff0850658 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php @@ -13,12 +13,19 @@ namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\AttributeAutoconfigurationPass; +use Symfony\Component\DependencyInjection\Compiler\ResolveInstanceofConditionalsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\Attribute\EventListener; use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass; use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\EventDispatcher\Tests\Fixtures\CustomEvent; +use Symfony\Component\EventDispatcher\Tests\Fixtures\TaggedInvokableListener; +use Symfony\Component\EventDispatcher\Tests\Fixtures\TaggedMultiListener; class RegisterListenersPassTest extends TestCase { @@ -231,6 +238,90 @@ class RegisterListenersPassTest extends TestCase $this->assertEquals($expectedCalls, $definition->getMethodCalls()); } + /** + * @requires PHP 8 + */ + public function testTaggedInvokableEventListener() + { + if (!class_exists(AttributeAutoconfigurationPass::class)) { + self::markTestSkipped('This test requires Symfony DependencyInjection >= 5.3'); + } + + $container = new ContainerBuilder(); + $container->registerAttributeForAutoconfiguration(EventListener::class, static function (ChildDefinition $definition, EventListener $attribute): void { + $definition->addTag('kernel.event_listener', get_object_vars($attribute)); + }); + $container->register('foo', TaggedInvokableListener::class)->setAutoconfigured(true); + $container->register('event_dispatcher', \stdClass::class); + + (new AttributeAutoconfigurationPass())->process($container); + (new ResolveInstanceofConditionalsPass())->process($container); + (new RegisterListenersPass())->process($container); + + $definition = $container->getDefinition('event_dispatcher'); + $expectedCalls = [ + [ + 'addListener', + [ + CustomEvent::class, + [new ServiceClosureArgument(new Reference('foo')), '__invoke'], + 0, + ], + ], + ]; + $this->assertEquals($expectedCalls, $definition->getMethodCalls()); + } + + /** + * @requires PHP 8 + */ + public function testTaggedMultiEventListener() + { + if (!class_exists(AttributeAutoconfigurationPass::class)) { + self::markTestSkipped('This test requires Symfony DependencyInjection >= 5.3'); + } + + $container = new ContainerBuilder(); + $container->registerAttributeForAutoconfiguration(EventListener::class, static function (ChildDefinition $definition, EventListener $attribute): void { + $definition->addTag('kernel.event_listener', get_object_vars($attribute)); + }); + $container->register('foo', TaggedMultiListener::class)->setAutoconfigured(true); + $container->register('event_dispatcher', \stdClass::class); + + (new AttributeAutoconfigurationPass())->process($container); + (new ResolveInstanceofConditionalsPass())->process($container); + (new RegisterListenersPass())->process($container); + + $definition = $container->getDefinition('event_dispatcher'); + $expectedCalls = [ + [ + 'addListener', + [ + CustomEvent::class, + [new ServiceClosureArgument(new Reference('foo')), 'onCustomEvent'], + 0, + ], + ], + [ + 'addListener', + [ + 'foo', + [new ServiceClosureArgument(new Reference('foo')), 'onFoo'], + 42, + ], + ], + [ + 'addListener', + [ + 'bar', + [new ServiceClosureArgument(new Reference('foo')), 'onBarEvent'], + 0, + ], + ], + ]; + $this->assertEquals($expectedCalls, $definition->getMethodCalls()); + } + public function testAliasedEventListener() { $container = new ContainerBuilder(); @@ -416,10 +507,6 @@ final class AliasedEvent { } -final class CustomEvent -{ -} - final class TypedListener { public function __invoke(AliasedEvent $event): void diff --git a/src/Symfony/Component/EventDispatcher/Tests/Fixtures/CustomEvent.php b/src/Symfony/Component/EventDispatcher/Tests/Fixtures/CustomEvent.php new file mode 100644 index 0000000000..41d951c7ab --- /dev/null +++ b/src/Symfony/Component/EventDispatcher/Tests/Fixtures/CustomEvent.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests\Fixtures; + +final class CustomEvent +{ +} diff --git a/src/Symfony/Component/EventDispatcher/Tests/Fixtures/TaggedInvokableListener.php b/src/Symfony/Component/EventDispatcher/Tests/Fixtures/TaggedInvokableListener.php new file mode 100644 index 0000000000..00a5e14d9e --- /dev/null +++ b/src/Symfony/Component/EventDispatcher/Tests/Fixtures/TaggedInvokableListener.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests\Fixtures; + +use Symfony\Component\EventDispatcher\Attribute\EventListener; + +#[EventListener] +final class TaggedInvokableListener +{ + public function __invoke(CustomEvent $event): void + { + } +} diff --git a/src/Symfony/Component/EventDispatcher/Tests/Fixtures/TaggedMultiListener.php b/src/Symfony/Component/EventDispatcher/Tests/Fixtures/TaggedMultiListener.php new file mode 100644 index 0000000000..65a66b8aa2 --- /dev/null +++ b/src/Symfony/Component/EventDispatcher/Tests/Fixtures/TaggedMultiListener.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests\Fixtures; + +use Symfony\Component\EventDispatcher\Attribute\EventListener; + +#[EventListener(event: CustomEvent::class, method: 'onCustomEvent')] +#[EventListener(event: 'foo', priority: 42)] +#[EventListener(event: 'bar', method: 'onBarEvent')] +final class TaggedMultiListener +{ + public function onCustomEvent(CustomEvent $event): void + { + } + + public function onFoo(): void + { + } + + public function onBarEvent(): void + { + } +}