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" } } } 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