From 3ff85f8f8f0cbd1f007e3b1271a59dc2364c6da1 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sun, 10 Jan 2021 02:19:46 +0100 Subject: [PATCH] [DependencyInjection] Configure service tags via attributes. --- .../AttributeAutoconfigurationPass.php | 47 ++++++++++ .../Compiler/PassConfig.php | 1 + .../Tests/Compiler/IntegrationTest.php | 54 ++++++++++++ .../Tests/Fixtures/Attribute/CustomTag.php | 28 ++++++ .../Tests/Fixtures/TaggedService1.php | 20 +++++ .../Tests/Fixtures/TaggedService2.php | 19 +++++ .../Tests/Fixtures/TaggedService3.php | 19 +++++ .../Attribute/EventListener.php | 44 ++++++++++ .../Component/EventDispatcher/CHANGELOG.md | 5 ++ .../RegisterListenersPassTest.php | 85 ++++++++++++++++++- .../Tests/Fixtures/CustomEvent.php | 16 ++++ .../Fixtures/TaggedInvokableListener.php | 22 +++++ .../Tests/Fixtures/TaggedMultiListener.php | 23 +++++ .../Component/EventDispatcher/composer.json | 4 +- .../Component/HttpKernel/Attribute/Reset.php | 33 +++++++ src/Symfony/Component/HttpKernel/CHANGELOG.md | 1 + .../ResettableServicePassTest.php | 42 +++++++++ .../Tests/Fixtures/ClearableService.php | 3 + .../Tests/Fixtures/MultiResettableService.php | 4 + .../Tests/Fixtures/ResettableService.php | 3 + .../Component/HttpKernel/composer.json | 5 +- src/Symfony/Contracts/CHANGELOG.md | 5 ++ src/Symfony/Contracts/Cache/composer.json | 4 +- .../Contracts/Deprecation/composer.json | 4 +- .../Contracts/EventDispatcher/composer.json | 4 +- .../Contracts/HttpClient/composer.json | 4 +- .../Contracts/Service/Attribute/Tag.php | 55 ++++++++++++ .../Service/Attribute/TagInterface.php | 37 ++++++++ src/Symfony/Contracts/Service/composer.json | 4 +- .../Contracts/Translation/composer.json | 4 +- src/Symfony/Contracts/composer.json | 4 +- 31 files changed, 581 insertions(+), 22 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomTag.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/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 create mode 100644 src/Symfony/Component/HttpKernel/Attribute/Reset.php create mode 100644 src/Symfony/Contracts/Service/Attribute/Tag.php create mode 100644 src/Symfony/Contracts/Service/Attribute/TagInterface.php diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php new file mode 100644 index 0000000000000..dd4404e1c484f --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php @@ -0,0 +1,47 @@ + + * + * 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\ContainerBuilder; +use Symfony\Contracts\Service\Attribute\TagInterface; + +final class AttributeAutoconfigurationPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (80000 > \PHP_VERSION_ID || !interface_exists(TagInterface::class)) { + return; + } + + foreach ($container->getDefinitions() as $definition) { + if (!$definition->isAutoconfigured()) { + continue; + } + + if (!$class = $container->getParameterBag()->resolveValue($definition->getClass())) { + continue; + } + + try { + $reflector = new \ReflectionClass($class); + } catch (\ReflectionException $e) { + continue; + } + + foreach ($reflector->getAttributes(TagInterface::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + /** @var TagInterface $tag */ + $tag = $attribute->newInstance(); + $definition->addTag($tag->getName(), $tag->getAttributes()); + } + } + } +} diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php index 961711fd28cad..6bdb1fa1f0b6c 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php @@ -42,6 +42,7 @@ public function __construct() $this->beforeOptimizationPasses = [ 100 => [ new ResolveClassPass(), + new AttributeAutoconfigurationPass(), new ResolveInstanceofConditionalsPass(), new RegisterEnvVarProcessorsPass(), ], diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index 9f059a80d9891..be104881f4cf3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -16,6 +16,7 @@ use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\DependencyInjection\Reference; @@ -24,6 +25,10 @@ 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\Contracts\Service\Attribute\TagInterface; use Symfony\Contracts\Service\ServiceProviderInterface; use Symfony\Contracts\Service\ServiceSubscriberInterface; @@ -506,6 +511,45 @@ public function testTaggedServiceLocatorWithDefaultIndex() ]; $this->assertSame($expected, ['baz' => $serviceLocator->get('baz')]); } + + /** + * @requires PHP 8 + */ + public function testTagsViaAttribute() + { + if (!\interface_exists(TagInterface::class)) { + self::markTestSkipped('This test requires symfony/service-contracts 2.4 or newer.'); + } + + $container = new ContainerBuilder(); + $container->register('one', TaggedService1::class) + ->setPublic(true) + ->setAutoconfigured(true); + $container->register('two', TaggedService2::class) + ->setPublic(true) + ->setAutoconfigured(true); + $container->register('three', TaggedService3::class) + ->setPublic(true) + ->setAutoconfigured(true); + + $collector = new TagCollector(); + $container->addCompilerPass($collector); + + $container->compile(); + + self::assertSame([ + 'one' => [ + ['foo' => 'bar', 'priority' => 0], + ['bar' => 'baz', 'priority' => 0], + ], + 'two' => [ + ['someAttribute' => 'prio 100', 'priority' => 100], + ], + 'three' => [ + ['someAttribute' => 'custom_tag_class'], + ], + ], $collector->collectedTags); + } } class ServiceSubscriberStub implements ServiceSubscriberInterface @@ -566,3 +610,13 @@ public function setSunshine($type) { } } + +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/CustomTag.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomTag.php new file mode 100644 index 0000000000000..9edd29215d9ae --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomTag.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\DependencyInjection\Tests\Fixtures\Attribute; + +use Symfony\Contracts\Service\Attribute\TagInterface; + +#[\Attribute(\Attribute::TARGET_CLASS)] +final class CustomTag implements TagInterface +{ + public function getName(): string + { + return 'app.custom_tag'; + } + + public function getAttributes(): array + { + return ['someAttribute' => 'custom_tag_class']; + } +} 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 0000000000000..1649ee5f3eca5 --- /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\Contracts\Service\Attribute\Tag; + +#[Tag(name: 'app.custom_tag', attributes: ['foo' => 'bar'])] +#[Tag(name: 'app.custom_tag', attributes: ['bar' => 'baz'])] +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 0000000000000..96fb53ed25fb0 --- /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\Contracts\Service\Attribute\Tag; + +#[Tag(name: 'app.custom_tag', priority: 100, attributes: ['someAttribute' => 'prio 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 0000000000000..c3e68f35c13cf --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService3.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\CustomTag; + +#[CustomTag] +final class TaggedService3 +{ +} diff --git a/src/Symfony/Component/EventDispatcher/Attribute/EventListener.php b/src/Symfony/Component/EventDispatcher/Attribute/EventListener.php new file mode 100644 index 0000000000000..274f6fb778620 --- /dev/null +++ b/src/Symfony/Component/EventDispatcher/Attribute/EventListener.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Attribute; + +use Symfony\Contracts\Service\Attribute\TagInterface; + +/** + * Service tag to autoconfigure event listeners. + * + * @author Alexander M. Turek + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +class EventListener implements TagInterface +{ + public function __construct( + private ?string $event = null, + private ?string $method = null, + private int $priority = 0 + ) { + } + + public function getName(): string + { + return 'kernel.event_listener'; + } + + public function getAttributes(): array + { + return [ + 'event' => $this->event, + 'method' => $this->method, + 'priority' => $this->priority, + ]; + } +} diff --git a/src/Symfony/Component/EventDispatcher/CHANGELOG.md b/src/Symfony/Component/EventDispatcher/CHANGELOG.md index 92a3b8bfc4d9e..c37ad1d325a17 100644 --- a/src/Symfony/Component/EventDispatcher/CHANGELOG.md +++ b/src/Symfony/Component/EventDispatcher/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + + * Added the `EventListener` service tag attribute for 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 bf2cebf6c0660..70ce867422f38 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php @@ -13,12 +13,17 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Compiler\AttributeAutoconfigurationPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; 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; +use Symfony\Contracts\Service\Attribute\TagInterface; class RegisterListenersPassTest extends TestCase { @@ -231,6 +236,82 @@ public function testInvokableEventListener() $this->assertEquals($expectedCalls, $definition->getMethodCalls()); } + /** + * @requires PHP 8 + */ + public function testTaggedInvokableEventListener() + { + if (!\interface_exists(TagInterface::class)) { + self::markTestSkipped('This test requires symfony/service-contracts 2.4 or newer.'); + } + + $container = new ContainerBuilder(); + $container->register('foo', TaggedInvokableListener::class)->setAutoconfigured(true); + $container->register('event_dispatcher', \stdClass::class); + + (new AttributeAutoconfigurationPass())->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 (!\interface_exists(TagInterface::class)) { + self::markTestSkipped('This test requires symfony/service-contracts 2.4 or newer.'); + } + + $container = new ContainerBuilder(); + $container->register('foo', TaggedMultiListener::class)->setAutoconfigured(true); + $container->register('event_dispatcher', \stdClass::class); + + (new AttributeAutoconfigurationPass())->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 +497,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 0000000000000..41d951c7abd04 --- /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 0000000000000..00a5e14d9e120 --- /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 0000000000000..6a71eff928943 --- /dev/null +++ b/src/Symfony/Component/EventDispatcher/Tests/Fixtures/TaggedMultiListener.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Symfony\Contracts\Service\Attribute\TagInterface; + +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +final class Reset implements TagInterface +{ + public function __construct( + private string $method + ) { + } + + public function getName(): string + { + return 'kernel.reset'; + } + + public function getAttributes(): array + { + return ['method' => $this->method]; + } +} diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index e20b6c881d2d1..f780fd80a2104 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * marked the class `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` as internal + * added the `Reset` service tag attribute for PHP 8. 5.2.0 ----- diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ResettableServicePassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ResettableServicePassTest.php index b28f90d3628c0..89488d31cd998 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ResettableServicePassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ResettableServicePassTest.php @@ -55,6 +55,48 @@ public function testCompilerPass() ); } + /** + * @requires PHP 8 + */ + public function testCompilerPassWithAutoconfiguration() + { + $container = new ContainerBuilder(); + $container->register('one', ResettableService::class) + ->setPublic(true) + ->setAutoconfigured(true); + $container->register('two', ClearableService::class) + ->setPublic(true) + ->setAutoconfigured(true); + $container->register('three', MultiResettableService::class) + ->setPublic(true) + ->setAutoconfigured(true); + + $container->register('services_resetter', ServicesResetter::class) + ->setPublic(true) + ->setArguments([null, []]); + $container->addCompilerPass(new ResettableServicePass()); + + $container->compile(); + + $definition = $container->getDefinition('services_resetter'); + + $this->assertEquals( + [ + new IteratorArgument([ + 'one' => new Reference('one', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE), + 'two' => new Reference('two', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE), + 'three' => new Reference('three', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE), + ]), + [ + 'one' => ['reset'], + 'two' => ['clear'], + 'three' => ['resetFirst', 'resetSecond'], + ], + ], + $definition->getArguments() + ); + } + public function testMissingMethod() { $this->expectException(\Symfony\Component\DependencyInjection\Exception\RuntimeException::class); diff --git a/src/Symfony/Component/HttpKernel/Tests/Fixtures/ClearableService.php b/src/Symfony/Component/HttpKernel/Tests/Fixtures/ClearableService.php index 35acb419ce3e5..b7838841ef867 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Fixtures/ClearableService.php +++ b/src/Symfony/Component/HttpKernel/Tests/Fixtures/ClearableService.php @@ -2,6 +2,9 @@ namespace Symfony\Component\HttpKernel\Tests\Fixtures; +use Symfony\Component\HttpKernel\Attribute\Reset; + +#[Reset(method: 'clear')] class ClearableService { public static $counter = 0; diff --git a/src/Symfony/Component/HttpKernel/Tests/Fixtures/MultiResettableService.php b/src/Symfony/Component/HttpKernel/Tests/Fixtures/MultiResettableService.php index 4930fd6a30c19..f5035c548c54e 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Fixtures/MultiResettableService.php +++ b/src/Symfony/Component/HttpKernel/Tests/Fixtures/MultiResettableService.php @@ -2,6 +2,10 @@ namespace Symfony\Component\HttpKernel\Tests\Fixtures; +use Symfony\Component\HttpKernel\Attribute\Reset; + +#[Reset(method: 'resetFirst')] +#[Reset(method: 'resetSecond')] class MultiResettableService { public static $resetFirstCounter = 0; diff --git a/src/Symfony/Component/HttpKernel/Tests/Fixtures/ResettableService.php b/src/Symfony/Component/HttpKernel/Tests/Fixtures/ResettableService.php index ffb72a35a7c77..cebf1bc226264 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Fixtures/ResettableService.php +++ b/src/Symfony/Component/HttpKernel/Tests/Fixtures/ResettableService.php @@ -2,6 +2,9 @@ namespace Symfony\Component\HttpKernel\Tests\Fixtures; +use Symfony\Component\HttpKernel\Attribute\Reset; + +#[Reset(method: 'reset')] class ResettableService { public static $counter = 0; diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index f68f60f0531d5..ec624281e9ada 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -32,12 +32,13 @@ "symfony/config": "^5.0", "symfony/console": "^4.4|^5.0", "symfony/css-selector": "^4.4|^5.0", - "symfony/dependency-injection": "^5.1.8", + "symfony/dependency-injection": "^5.3", "symfony/dom-crawler": "^4.4|^5.0", "symfony/expression-language": "^4.4|^5.0", "symfony/finder": "^4.4|^5.0", "symfony/process": "^4.4|^5.0", "symfony/routing": "^4.4|^5.0", + "symfony/service-contracts": "^2.4", "symfony/stopwatch": "^4.4|^5.0", "symfony/translation": "^4.4|^5.0", "symfony/translation-contracts": "^1.1|^2", @@ -53,7 +54,7 @@ "symfony/config": "<5.0", "symfony/console": "<4.4", "symfony/form": "<5.0", - "symfony/dependency-injection": "<5.1.8", + "symfony/dependency-injection": "<5.3", "symfony/doctrine-bridge": "<5.0", "symfony/http-client": "<5.0", "symfony/mailer": "<5.0", diff --git a/src/Symfony/Contracts/CHANGELOG.md b/src/Symfony/Contracts/CHANGELOG.md index b62029adb59d7..a5fc46def0144 100644 --- a/src/Symfony/Contracts/CHANGELOG.md +++ b/src/Symfony/Contracts/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +2.4.0 +----- + +* added `Service\Attribute\Tag` attribute for PHP 8 + 2.3.0 ----- diff --git a/src/Symfony/Contracts/Cache/composer.json b/src/Symfony/Contracts/Cache/composer.json index f95df70f6496c..b9350c099f0d6 100644 --- a/src/Symfony/Contracts/Cache/composer.json +++ b/src/Symfony/Contracts/Cache/composer.json @@ -27,9 +27,9 @@ }, "minimum-stability": "dev", "extra": { - "branch-version": "2.3", + "branch-version": "2.4", "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/Deprecation/composer.json b/src/Symfony/Contracts/Deprecation/composer.json index 27aa491e11d99..182b4633bc92b 100644 --- a/src/Symfony/Contracts/Deprecation/composer.json +++ b/src/Symfony/Contracts/Deprecation/composer.json @@ -24,9 +24,9 @@ }, "minimum-stability": "dev", "extra": { - "branch-version": "2.3", + "branch-version": "2.4", "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/EventDispatcher/composer.json b/src/Symfony/Contracts/EventDispatcher/composer.json index 36aeed02d3767..ff8a4812337c7 100644 --- a/src/Symfony/Contracts/EventDispatcher/composer.json +++ b/src/Symfony/Contracts/EventDispatcher/composer.json @@ -27,9 +27,9 @@ }, "minimum-stability": "dev", "extra": { - "branch-version": "2.3", + "branch-version": "2.4", "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/HttpClient/composer.json b/src/Symfony/Contracts/HttpClient/composer.json index 2dc9990e1e886..f3f7342dd6272 100644 --- a/src/Symfony/Contracts/HttpClient/composer.json +++ b/src/Symfony/Contracts/HttpClient/composer.json @@ -26,9 +26,9 @@ }, "minimum-stability": "dev", "extra": { - "branch-version": "2.3", + "branch-version": "2.4", "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/Service/Attribute/Tag.php b/src/Symfony/Contracts/Service/Attribute/Tag.php new file mode 100644 index 0000000000000..76d3585a362a8 --- /dev/null +++ b/src/Symfony/Contracts/Service/Attribute/Tag.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +/** + * A generic tag implementation. + * + * This attribute holds meta information on the annotated class that can be processed by a service container. + * + * @author Alexander M. Turek + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +final class Tag implements TagInterface +{ + /** + * @param mixed[] $attributes + */ + public function __construct( + private string $name, + private array $attributes = [], + int $priority = 0, + ) { + $this->attributes['priority'] = $priority; + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return $this->name; + } + + public function getPriority(): int + { + return $this->attributes['priority']; + } + + /** + * {@inheritdoc} + */ + public function getAttributes(): array + { + return $this->attributes; + } +} diff --git a/src/Symfony/Contracts/Service/Attribute/TagInterface.php b/src/Symfony/Contracts/Service/Attribute/TagInterface.php new file mode 100644 index 0000000000000..2a05a627750fe --- /dev/null +++ b/src/Symfony/Contracts/Service/Attribute/TagInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +/** + * A service tag. + * + * This attribute holds meta information on the annotated class that can be processed by a service container. + * + * @author Alexander M. Turek + */ +interface TagInterface +{ + /** + * The name of the tag. + * + * If the service container implementation offers a way to query services or service definitions by tag, + * this name shall be used as search input. + */ + public function getName(): string; + + /** + * Additional attributes of this tag. + * + * @return mixed[] + */ + public function getAttributes(): array; +} diff --git a/src/Symfony/Contracts/Service/composer.json b/src/Symfony/Contracts/Service/composer.json index efbf399d1c900..951ce0465469a 100644 --- a/src/Symfony/Contracts/Service/composer.json +++ b/src/Symfony/Contracts/Service/composer.json @@ -27,9 +27,9 @@ }, "minimum-stability": "dev", "extra": { - "branch-version": "2.3", + "branch-version": "2.4", "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/Translation/composer.json b/src/Symfony/Contracts/Translation/composer.json index 311c38b28c2a9..656b06b05edb5 100644 --- a/src/Symfony/Contracts/Translation/composer.json +++ b/src/Symfony/Contracts/Translation/composer.json @@ -26,9 +26,9 @@ }, "minimum-stability": "dev", "extra": { - "branch-version": "2.3", + "branch-version": "2.4", "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/composer.json b/src/Symfony/Contracts/composer.json index eecf32761fb35..40ea720d877a8 100644 --- a/src/Symfony/Contracts/composer.json +++ b/src/Symfony/Contracts/composer.json @@ -48,9 +48,9 @@ }, "minimum-stability": "dev", "extra": { - "branch-version": "2.3", + "branch-version": "2.4", "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" } } }