Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
class AsTaggedItem
{
/**
* @param string|null $index The property or method to use to index the item in the iterator/locator
* @param int|null $priority The priority of the item; the higher the number, the earlier the tagged service will be located in the iterator/locator
* @param string|null $index The index at which the service will be found when consuming tagged iterators/locators
* @param int|null $priority The priority of the service in iterators/locators; the higher the number, the earlier it will
*/
public function __construct(
public ?string $index = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,61 +65,69 @@
$class = $definition->getClass();
$class = $container->getParameterBag()->resolveValue($class) ?: null;
$reflector = null !== $class ? $container->getReflectionClass($class) : null;
$checkTaggedItem = !$definition->hasTag($definition->isAutoconfigured() ? 'container.ignore_attributes' : $tagName);
$loadFromDefaultMethods = $reflector && null !== $defaultPriorityMethod;
$phpAttributes = $definition->isAutoconfigured() && !$definition->hasTag('container.ignore_attributes') ? $reflector?->getAttributes(AsTaggedItem::class) : [];

foreach ($phpAttributes ??= [] as $i => $attribute) {
$attribute = $attribute->newInstance();
$phpAttributes[$i] = [
'priority' => $attribute->priority,
$indexAttribute ?? '' => $attribute->index,
];
if (null === $defaultPriority) {
$defaultPriority = $attribute->priority ?? 0;
$defaultIndex = $attribute->index;
}
}
if (1 >= \count($phpAttributes)) {
$phpAttributes = [];
}

for ($i = 0; $i < \count($attributes); ++$i) {
if (!($attribute = $attributes[$i]) && $phpAttributes) {
array_splice($attributes, $i--, 1, $phpAttributes);

Check failure on line 88 in src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php

View workflow job for this annotation

GitHub Actions / Psalm

NoValue

src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php:88:56: NoValue: All possible types for this argument were invalidated - This may be dead code (see https://psalm.dev/179)

Check failure on line 88 in src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php

View workflow job for this annotation

GitHub Actions / Psalm

NoValue

src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php:88:56: NoValue: All possible types for this argument were invalidated - This may be dead code (see https://psalm.dev/179)
continue;
}

foreach ($attributes as $attribute) {
$index = $priority = null;

if (isset($attribute['priority'])) {
$priority = $attribute['priority'];
} elseif (null === $defaultPriority && $defaultPriorityMethod && $reflector) {
$defaultPriority = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultPriorityMethod, $tagName, 'priority', $checkTaggedItem);
} elseif ($loadFromDefaultMethods) {
$defaultPriority = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultPriorityMethod, $tagName, 'priority') ?? $defaultPriority;
$defaultIndex = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute) ?? $defaultIndex;
$loadFromDefaultMethods = false;
}
$priority ??= $defaultPriority ??= 0;

if (null === $indexAttribute && !$defaultIndexMethod && !$needsIndexes) {
$services[] = [$priority, ++$i, null, $serviceId, null];
$services[] = [$priority, $i, null, $serviceId, null];
continue 2;
}

if (null !== $indexAttribute && isset($attribute[$indexAttribute])) {
$index = $parameterBag->resolveValue($attribute[$indexAttribute]);
}
if (null === $index && null === $defaultIndex && $defaultPriorityMethod && $reflector) {
$defaultIndex = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute, $checkTaggedItem);
if (null === $index && $loadFromDefaultMethods) {
$defaultPriority = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultPriorityMethod, $tagName, 'priority') ?? $defaultPriority;
$defaultIndex = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute) ?? $defaultIndex;
$loadFromDefaultMethods = false;
}
$index ??= $defaultIndex ??= $definition->getTag('container.decorator')[0]['id'] ?? $serviceId;

$services[] = [$priority, ++$i, $index, $serviceId, $class];
}

if ($reflector) {
$attributes = $reflector->getAttributes(AsTaggedItem::class);
$attributeCount = \count($attributes);

foreach ($attributes as $attribute) {
$instance = $attribute->newInstance();

if (!$instance->index && 1 < $attributeCount) {
throw new InvalidArgumentException(\sprintf('Attribute "%s" on class "%s" cannot have an empty index when repeated.', AsTaggedItem::class, $class));
}

$services[] = [$instance->priority ?? 0, ++$i, $instance->index ?? $serviceId, $serviceId, $class];
}
$services[] = [$priority, $i, $index, $serviceId, $class];
}
}

uasort($services, static fn ($a, $b) => $b[0] <=> $a[0] ?: $a[1] <=> $b[1]);

$refs = [];
foreach ($services as [, , $index, $serviceId, $class]) {
if (!$class) {
$reference = new Reference($serviceId);
} elseif ($index === $serviceId) {
$reference = new TypedReference($serviceId, $class);
} else {
$reference = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $index);
}
$reference = match (true) {
!$class => new Reference($serviceId),
$index === $serviceId => new TypedReference($serviceId, $class),
default => new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $index),
};

if (null === $index) {
$refs[] = $reference;
Expand All @@ -137,25 +145,16 @@
*/
class PriorityTaggedServiceUtil
{
public static function getDefault(string $serviceId, \ReflectionClass $r, string $defaultMethod, string $tagName, ?string $indexAttribute, bool $checkTaggedItem): string|int|null
public static function getDefault(string $serviceId, \ReflectionClass $r, string $defaultMethod, string $tagName, ?string $indexAttribute): string|int|null
{
$class = $r->getName();

if (!$checkTaggedItem && !$r->hasMethod($defaultMethod)) {
return null;
}

if ($checkTaggedItem && !$r->hasMethod($defaultMethod)) {
foreach ($r->getAttributes(AsTaggedItem::class) as $attribute) {
return 'priority' === $indexAttribute ? $attribute->newInstance()->priority : $attribute->newInstance()->index;
}

if (!$r->hasMethod($defaultMethod)) {
return null;
}

if ($r->isInterface()) {
return null;
}
$class = $r->name;

if (null !== $indexAttribute) {
$service = $class !== $serviceId ? \sprintf('service "%s"', $serviceId) : 'on the corresponding service';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,29 +233,14 @@ public function testTaggedItemAttributes()
'hello' => new TypedReference('service2', HelloNamedService::class),
'multi_hello_1' => new TypedReference('service6', MultiTagHelloNamedService::class),
'service1' => new TypedReference('service1', FooTagClass::class),
'multi_hello_0' => new TypedReference('service6', MultiTagHelloNamedService::class),
];

$services = $priorityTaggedServiceTraitImplementation->test($tag, $container);
$this->assertSame(array_keys($expected), array_keys($services));
$this->assertEquals($expected, $priorityTaggedServiceTraitImplementation->test($tag, $container));
}

public function testTaggedItemAttributesRepeatedWithoutNameThrows()
{
$container = new ContainerBuilder();
$container->register('service1', MultiNoNameTagHelloNamedService::class)
->setAutoconfigured(true)
->addTag('my_custom_tag');

(new ResolveInstanceofConditionalsPass())->process($container);
$tag = new TaggedIteratorArgument('my_custom_tag', 'foo', 'getFooBar', exclude: ['service4', 'service5']);

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Attribute "Symfony\Component\DependencyInjection\Attribute\AsTaggedItem" on class "Symfony\Component\DependencyInjection\Tests\Compiler\MultiNoNameTagHelloNamedService" cannot have an empty index when repeated.');

(new PriorityTaggedServiceTraitImplementation())->test($tag, $container);
}

public function testResolveIndexedTags()
{
$container = new ContainerBuilder();
Expand Down Expand Up @@ -283,6 +268,48 @@ public function testResolveIndexedTags()
$this->assertSame(array_keys($expected), array_keys($services));
$this->assertEquals($expected, $priorityTaggedServiceTraitImplementation->test($tag, $container));
}

public function testAttributesAreMergedWithTags()
{
$container = new ContainerBuilder();
$definition = $container->register('service_attr_first', MultiTagHelloNamedService::class);
$definition->setAutoconfigured(true);
$definition->addTag('my_custom_tag', ['foo' => 'z']);
$definition->addTag('my_custom_tag', []);

(new ResolveInstanceofConditionalsPass())->process($container);

$priorityTaggedServiceTraitImplementation = new PriorityTaggedServiceTraitImplementation();

$tag = new TaggedIteratorArgument('my_custom_tag', 'foo', 'getFooBar');
$services = $priorityTaggedServiceTraitImplementation->test($tag, $container);

$expected = [
'multi_hello_2' => new TypedReference('service_attr_first', MultiTagHelloNamedService::class),
'multi_hello_1' => new TypedReference('service_attr_first', MultiTagHelloNamedService::class),
'z' => new TypedReference('service_attr_first', MultiTagHelloNamedService::class),
'multi_hello_0' => new TypedReference('service_attr_first', MultiTagHelloNamedService::class),
];
$this->assertSame(array_keys($expected), array_keys($services));
$this->assertEquals($expected, $services);
}

public function testAttributesAreFallbacks()
{
$container = new ContainerBuilder();
$definition = $container->register('service_attr_first', MultiTagHelloNamedService::class);
$definition->setAutoconfigured(true);
$definition->addTag('my_custom_tag', ['foo' => 'z']);

(new ResolveInstanceofConditionalsPass())->process($container);

$priorityTaggedServiceTraitImplementation = new PriorityTaggedServiceTraitImplementation();

$tag = new TaggedIteratorArgument('my_custom_tag', 'foo', 'getFooBar');
$services = $priorityTaggedServiceTraitImplementation->test($tag, $container);

$this->assertEquals(['z' => new TypedReference('service_attr_first', MultiTagHelloNamedService::class)], $services);
}
}

class PriorityTaggedServiceTraitImplementation
Expand All @@ -305,18 +332,13 @@ class HelloNamedService2
{
}

#[AsTaggedItem(index: 'multi_hello_0', priority: 0)]
#[AsTaggedItem(index: 'multi_hello_1', priority: 1)]
#[AsTaggedItem(index: 'multi_hello_2', priority: 2)]
class MultiTagHelloNamedService
{
}

#[AsTaggedItem(priority: 1)]
#[AsTaggedItem(priority: 2)]
class MultiNoNameTagHelloNamedService
{
}

interface HelloInterface
{
public static function getFooBar(): string;
Expand Down
Loading