[DependencyInjection] Add #[TaggedItem]
attribute for defining the index and priority of classes found in tagged iterators/locators
This commit is contained in:
parent
59e5ac5dcf
commit
252f2ca1fb
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\DependencyInjection\Attribute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attribute to tell under which index and priority a service class should be found in tagged iterators/locators.
|
||||||
|
*
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||||
|
class TaggedItem
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $index,
|
||||||
|
public ?int $priority = null,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ CHANGELOG
|
|||||||
* Add `ServicesConfigurator::remove()` in the PHP-DSL
|
* Add `ServicesConfigurator::remove()` in the PHP-DSL
|
||||||
* Add `%env(not:...)%` processor to negate boolean values
|
* Add `%env(not:...)%` processor to negate boolean values
|
||||||
* Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8
|
* Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8
|
||||||
|
* Add `#[TaggedItem]` attribute for defining the index and priority of classes found in tagged iterators/locators
|
||||||
* Add autoconfigurable attributes
|
* Add autoconfigurable attributes
|
||||||
* Add support for per-env configuration in loaders
|
* Add support for per-env configuration in loaders
|
||||||
* Add `ContainerBuilder::willBeAvailable()` to help with conditional configuration
|
* Add `ContainerBuilder::willBeAvailable()` to help with conditional configuration
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Component\DependencyInjection\Compiler;
|
namespace Symfony\Component\DependencyInjection\Compiler;
|
||||||
|
|
||||||
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\TaggedItem;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||||
use Symfony\Component\DependencyInjection\Reference;
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
@ -56,8 +57,10 @@ trait PriorityTaggedServiceTrait
|
|||||||
foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) {
|
foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) {
|
||||||
$defaultPriority = null;
|
$defaultPriority = null;
|
||||||
$defaultIndex = null;
|
$defaultIndex = null;
|
||||||
$class = $container->getDefinition($serviceId)->getClass();
|
$definition = $container->getDefinition($serviceId);
|
||||||
|
$class = $definition->getClass();
|
||||||
$class = $container->getParameterBag()->resolveValue($class) ?: null;
|
$class = $container->getParameterBag()->resolveValue($class) ?: null;
|
||||||
|
$checkTaggedItem = !$definition->hasTag(80000 <= \PHP_VERSION_ID && $definition->isAutoconfigured() ? 'container.ignore_attributes' : $tagName);
|
||||||
|
|
||||||
foreach ($attributes as $attribute) {
|
foreach ($attributes as $attribute) {
|
||||||
$index = $priority = null;
|
$index = $priority = null;
|
||||||
@ -65,7 +68,7 @@ trait PriorityTaggedServiceTrait
|
|||||||
if (isset($attribute['priority'])) {
|
if (isset($attribute['priority'])) {
|
||||||
$priority = $attribute['priority'];
|
$priority = $attribute['priority'];
|
||||||
} elseif (null === $defaultPriority && $defaultPriorityMethod && $class) {
|
} elseif (null === $defaultPriority && $defaultPriorityMethod && $class) {
|
||||||
$defaultPriority = PriorityTaggedServiceUtil::getDefaultPriority($container, $serviceId, $class, $defaultPriorityMethod, $tagName);
|
$defaultPriority = PriorityTaggedServiceUtil::getDefault($container, $serviceId, $class, $defaultPriorityMethod, $tagName, 'priority', $checkTaggedItem);
|
||||||
}
|
}
|
||||||
$priority = $priority ?? $defaultPriority ?? $defaultPriority = 0;
|
$priority = $priority ?? $defaultPriority ?? $defaultPriority = 0;
|
||||||
|
|
||||||
@ -77,7 +80,7 @@ trait PriorityTaggedServiceTrait
|
|||||||
if (null !== $indexAttribute && isset($attribute[$indexAttribute])) {
|
if (null !== $indexAttribute && isset($attribute[$indexAttribute])) {
|
||||||
$index = $attribute[$indexAttribute];
|
$index = $attribute[$indexAttribute];
|
||||||
} elseif (null === $defaultIndex && $defaultPriorityMethod && $class) {
|
} elseif (null === $defaultIndex && $defaultPriorityMethod && $class) {
|
||||||
$defaultIndex = PriorityTaggedServiceUtil::getDefaultIndex($container, $serviceId, $class, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute);
|
$defaultIndex = PriorityTaggedServiceUtil::getDefault($container, $serviceId, $class, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute, $checkTaggedItem);
|
||||||
}
|
}
|
||||||
$index = $index ?? $defaultIndex ?? $defaultIndex = $serviceId;
|
$index = $index ?? $defaultIndex ?? $defaultIndex = $serviceId;
|
||||||
|
|
||||||
@ -114,22 +117,30 @@ trait PriorityTaggedServiceTrait
|
|||||||
class PriorityTaggedServiceUtil
|
class PriorityTaggedServiceUtil
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Gets the index defined by the default index method.
|
* @return string|int|null
|
||||||
*/
|
*/
|
||||||
public static function getDefaultIndex(ContainerBuilder $container, string $serviceId, string $class, string $defaultIndexMethod, string $tagName, ?string $indexAttribute): ?string
|
public static function getDefault(ContainerBuilder $container, string $serviceId, string $class, string $defaultMethod, string $tagName, ?string $indexAttribute, bool $checkTaggedItem)
|
||||||
{
|
{
|
||||||
if (!($r = $container->getReflectionClass($class)) || !$r->hasMethod($defaultIndexMethod)) {
|
if (!($r = $container->getReflectionClass($class)) || (!$checkTaggedItem && !$r->hasMethod($defaultMethod))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($checkTaggedItem && !$r->hasMethod($defaultMethod)) {
|
||||||
|
foreach ($r->getAttributes(TaggedItem::class) as $attribute) {
|
||||||
|
return 'priority' === $indexAttribute ? $attribute->newInstance()->priority : $attribute->newInstance()->index;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null !== $indexAttribute) {
|
if (null !== $indexAttribute) {
|
||||||
$service = $class !== $serviceId ? sprintf('service "%s"', $serviceId) : 'on the corresponding service';
|
$service = $class !== $serviceId ? sprintf('service "%s"', $serviceId) : 'on the corresponding service';
|
||||||
$message = [sprintf('Either method "%s::%s()" should ', $class, $defaultIndexMethod), sprintf(' or tag "%s" on %s is missing attribute "%s".', $tagName, $service, $indexAttribute)];
|
$message = [sprintf('Either method "%s::%s()" should ', $class, $defaultMethod), sprintf(' or tag "%s" on %s is missing attribute "%s".', $tagName, $service, $indexAttribute)];
|
||||||
} else {
|
} else {
|
||||||
$message = [sprintf('Method "%s::%s()" should ', $class, $defaultIndexMethod), '.'];
|
$message = [sprintf('Method "%s::%s()" should ', $class, $defaultMethod), '.'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!($rm = $r->getMethod($defaultIndexMethod))->isStatic()) {
|
if (!($rm = $r->getMethod($defaultMethod))->isStatic()) {
|
||||||
throw new InvalidArgumentException(implode('be static', $message));
|
throw new InvalidArgumentException(implode('be static', $message));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,42 +148,24 @@ class PriorityTaggedServiceUtil
|
|||||||
throw new InvalidArgumentException(implode('be public', $message));
|
throw new InvalidArgumentException(implode('be public', $message));
|
||||||
}
|
}
|
||||||
|
|
||||||
$defaultIndex = $rm->invoke(null);
|
$default = $rm->invoke(null);
|
||||||
|
|
||||||
if (\is_int($defaultIndex)) {
|
if ('priority' === $indexAttribute) {
|
||||||
$defaultIndex = (string) $defaultIndex;
|
if (!\is_int($default)) {
|
||||||
|
throw new InvalidArgumentException(implode(sprintf('return int (got "%s")', get_debug_type($default)), $message));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!\is_string($defaultIndex)) {
|
return $default;
|
||||||
throw new InvalidArgumentException(implode(sprintf('return string|int (got "%s")', get_debug_type($defaultIndex)), $message));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $defaultIndex;
|
if (\is_int($default)) {
|
||||||
|
$default = (string) $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if (!\is_string($default)) {
|
||||||
* Gets the priority defined by the default priority method.
|
throw new InvalidArgumentException(implode(sprintf('return string|int (got "%s")', get_debug_type($default)), $message));
|
||||||
*/
|
|
||||||
public static function getDefaultPriority(ContainerBuilder $container, string $serviceId, string $class, string $defaultPriorityMethod, string $tagName): ?int
|
|
||||||
{
|
|
||||||
if (!($r = $container->getReflectionClass($class)) || !$r->hasMethod($defaultPriorityMethod)) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!($rm = $r->getMethod($defaultPriorityMethod))->isStatic()) {
|
return $default;
|
||||||
throw new InvalidArgumentException(sprintf('Either method "%s::%s()" should be static or tag "%s" on service "%s" is missing attribute "priority".', $class, $defaultPriorityMethod, $tagName, $serviceId));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$rm->isPublic()) {
|
|
||||||
throw new InvalidArgumentException(sprintf('Either method "%s::%s()" should be public or tag "%s" on service "%s" is missing attribute "priority".', $class, $defaultPriorityMethod, $tagName, $serviceId));
|
|
||||||
}
|
|
||||||
|
|
||||||
$defaultPriority = $rm->invoke(null);
|
|
||||||
|
|
||||||
if (!\is_int($defaultPriority)) {
|
|
||||||
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return an integer (got "%s") or tag "%s" on service "%s" is missing attribute "priority".', $class, $defaultPriorityMethod, get_debug_type($defaultPriority), $tagName, $serviceId));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $defaultPriority;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,10 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler;
|
|||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\TaggedItem;
|
||||||
|
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||||
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
|
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
|
||||||
|
use Symfony\Component\DependencyInjection\Compiler\ResolveInstanceofConditionalsPass;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||||
use Symfony\Component\DependencyInjection\Reference;
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
@ -188,6 +191,34 @@ class PriorityTaggedServiceTraitTest extends TestCase
|
|||||||
yield ['getMethodShouldBePublicInsteadPrivate', null, sprintf('Method "%s::getMethodShouldBePublicInsteadPrivate()" should be public.', FooTaggedForInvalidDefaultMethodClass::class)];
|
yield ['getMethodShouldBePublicInsteadPrivate', null, sprintf('Method "%s::getMethodShouldBePublicInsteadPrivate()" should be public.', FooTaggedForInvalidDefaultMethodClass::class)];
|
||||||
yield ['getMethodShouldBePublicInsteadPrivate', 'foo', sprintf('Either method "%s::getMethodShouldBePublicInsteadPrivate()" should be public or tag "my_custom_tag" on service "service1" is missing attribute "foo".', FooTaggedForInvalidDefaultMethodClass::class)];
|
yield ['getMethodShouldBePublicInsteadPrivate', 'foo', sprintf('Either method "%s::getMethodShouldBePublicInsteadPrivate()" should be public or tag "my_custom_tag" on service "service1" is missing attribute "foo".', FooTaggedForInvalidDefaultMethodClass::class)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires PHP 8
|
||||||
|
*/
|
||||||
|
public function testTaggedItemAttributes()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->register('service1', FooTagClass::class)->addTag('my_custom_tag');
|
||||||
|
$container->register('service2', HelloNamedService::class)
|
||||||
|
->setAutoconfigured(true)
|
||||||
|
->setInstanceofConditionals([
|
||||||
|
HelloNamedService::class => (new ChildDefinition(''))->addTag('my_custom_tag'),
|
||||||
|
\stdClass::class => (new ChildDefinition(''))->addTag('my_custom_tag2'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
(new ResolveInstanceofConditionalsPass())->process($container);
|
||||||
|
|
||||||
|
$priorityTaggedServiceTraitImplementation = new PriorityTaggedServiceTraitImplementation();
|
||||||
|
|
||||||
|
$tag = new TaggedIteratorArgument('my_custom_tag', 'foo', 'getFooBar');
|
||||||
|
$expected = [
|
||||||
|
'hello' => new TypedReference('service2', HelloNamedService::class),
|
||||||
|
'service1' => new TypedReference('service1', FooTagClass::class),
|
||||||
|
];
|
||||||
|
$services = $priorityTaggedServiceTraitImplementation->test($tag, $container);
|
||||||
|
$this->assertSame(array_keys($expected), array_keys($services));
|
||||||
|
$this->assertEquals($expected, $priorityTaggedServiceTraitImplementation->test($tag, $container));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PriorityTaggedServiceTraitImplementation
|
class PriorityTaggedServiceTraitImplementation
|
||||||
@ -199,3 +230,8 @@ class PriorityTaggedServiceTraitImplementation
|
|||||||
return $this->findAndSortTaggedServices($tagName, $container);
|
return $this->findAndSortTaggedServices($tagName, $container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[TaggedItem(index: 'hello', priority: 1)]
|
||||||
|
class HelloNamedService extends \stdClass
|
||||||
|
{
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user