8000 feature #48707 [DependencyInjection] Target Attribute must fail if th… · chalasr/symfony@9f78421 · GitHub < 8000 body class="logged-out env-production page-responsive" style="word-wrap: break-word;">
Skip to content

Commit 9f78421

Browse files
nicolas-grekaschalasr
authored andcommitted
feature symfony#48707 [DependencyInjection] Target Attribute must fail if the target does not exist (rodmen)
This PR was merged into the 6.3 branch. Discussion ---------- [DependencyInjection] Target Attribute must fail if the target does not exist | Q | A | ------------- | --- | Branch? | 6.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Fix symfony#48555 | License | MIT | Doc PR | - This change checks if an attribute is a Target to not auto bind with a new default object if the target name does not exists. The existent behavior should be in charge of suggest possible options for a typo on the Target name. I can't mark `allow edit by maintainers` I think because I'm open the PR from a organization repo. Commits ------- 983cd08 [DependencyInjection] Target Attribute must fail if the target does not exist
2 parents 516e8d9 + 983cd08 commit 9f78421

File tree

12 files changed

+86
-13
lines changed

12 files changed

+86
-13
lines changed

src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class TaggedIteratorArgument extends IteratorArgument
2424
private ?string $defaultPriorityMethod;
2525
private bool $needsIndexes;
2626
private array $exclude;
27+
private bool $excludeSelf = true;
2728

2829
/**
2930
* @param string $tag The name of the tag identifying the target services
@@ -32,8 +33,9 @@ class TaggedIteratorArgument extends IteratorArgument
3233
* @param bool $needsIndexes Whether indexes are required and should be generated when computing the map
3334
* @param string|null $defaultPriorityMethod The static method that should be called to get each service's priority when their tag doesn't define the "priority" attribute
3435
* @param array $exclude Services to exclude from the iterator
36+
* @param array $excludeSelf Whether to automatically exclude the referencing service from the iterator
3537
*/
36-
public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $needsIndexes = false, string $defaultPriorityMethod = null, array $exclude = [])
38+
public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $needsIndexes = false, string $defaultPriorityMethod = null, array $exclude = [], bool $excludeSelf = true)
3739
{
38< 6D40 /code>40
parent::__construct([]);
3941

@@ -47,6 +49,7 @@ public function __construct(string $tag, string $indexAttribute = null, string $
4749
$this->needsIndexes = $needsIndexes;
4850
$this->defaultPriorityMethod = $defaultPriorityMethod ?: ($indexAttribute ? 'getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute))).'Priority' : null);
4951
$this->exclude = $exclude;
52+
$this->excludeSelf = $excludeSelf;
5053
}
5154

5255
public function getTag()
@@ -78,4 +81,9 @@ public function getExclude(): array
7881
{
7982
return $this->exclude;
8083
}
84+
85+
public function excludeSelf(): bool
86+
{
87+
return $this->excludeSelf;
88+
}
8189
}

src/Symfony/Component/DependencyInjection/Attribute/Target.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ public function __construct(string $name)
2828
$this->name = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $name))));
2929
}
3030

31-
public static function parseName(\ReflectionParameter $parameter): string
31+
public static function parseName(\ReflectionParameter $parameter, self &$attribute = null): string
3232
{
33+
$attribute = null;
3334
if (!$target = $parameter->getAttributes(self::class)[0] ?? null) {
3435
return $parameter->name;
3536
}
3637

37-
$name = $target->newInstance()->name;
38+
$attribute = $target->newInstance();
39+
$name = $attribute->name;
3840

3941
if (!preg_match('/^[a-zA-Z_\x7f-\xff]/', $name)) {
4042
if (($function = $parameter->getDeclaringFunction()) instanceof \ReflectionMethod) {

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ CHANGELOG
99
* Add `RemoveBuildParametersPass`, which removes parameters starting with a dot during compilation
1010
* Add support for nesting autowiring-related attributes into `#[Autowire(...)]`
1111
* Deprecate undefined and numeric keys with `service_locator` config
12+
* Fail if Target attribute does not exist during compilation
13+
* Add `excludeSelf` parameter to `TaggedIteratorArgument` with default value to `true`
14+
to control whether the referencing service should be automatically excluded from the iterator if it holds the tag
1215

1316
6.2
1417
---

src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php

Lines changed: 10 additions & 2 deletions
10000
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ private function doProcessValue(mixed $value, bool $isRoot = false): mixed
128128
return $this->processAttribute($attribute, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $value->getInvalidBehavior());
129129
}
130130

131-
$value = new TypedReference($value->getType(), $value->getType(), $value->getInvalidBehavior(), $attribute->name);
131+
$value = new TypedReference($value->getType(), $value->getType(), $value->getInvalidBehavior(), $attribute->name, [$attribute]);
132132
}
133133
if ($ref = $this->getAutowiredReference($value, true)) {
134134
return $ref;
@@ -332,7 +332,8 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
332332
}
333333

334334
$getValue = function () use ($type, $parameter, $class, $method) {
335-
if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, Target::parseName($parameter)), false)) {
335+
$name = Target::parseName($parameter, $target);
336+
if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $name, $target ? [$target] : []), false)) {
336337
$failureMessage = $this->createTypeNotFoundMessageCallback($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method));
337338

338339
if ($parameter->isDefaultValueAvailable()) {
@@ -420,6 +421,10 @@ private function getAutowiredReference(TypedReference $reference, bool $filterTy
420421
}
421422
}
422423
}
424+
425+
if ($reference->getAttributes()) {
426+
return null;
427+
}
423428
}
424429

425430
if ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract()) {
@@ -544,6 +549,9 @@ private function createTypeNotFoundMessage(TypedReference $reference, string $la
544549
}
545550

546551
$message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ? sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found');
552+
} elseif ($reference->getAttributes()) {
553+
$message = $label;
554+
$label = sprintf('"#[Target(\'%s\')" on', $reference->getName());
547555
} else {
548556
$alternatives = $this->createTypeAlternatives($this->container, $reference);
549557
$message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists';

src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,16 @@ trait PriorityTaggedServiceTrait
3737
*
3838
* @return Reference[]
3939
*/
40-
private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagName, ContainerBuilder $container): array
40+
private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagName, ContainerBuilder $container, array $exclude = []): array
4141
{
42-
$exclude = [];
4342
$indexAttribute = $defaultIndexMethod = $needsIndexes = $defaultPriorityMethod = null;
4443

4544
if ($tagName instanceof TaggedIteratorArgument) {
4645
$indexAttribute = $tagName->getIndexAttribute();
4746
$defaultIndexMethod = $tagName->getDefaultIndexMethod();
4847
$needsIndexes = $tagName->needsIndexes();
4948
$defaultPriorityMethod = $tagName->getDefaultPriorityMethod() ?? 'getDefaultPriority';
50-
$exclude = $tagName->getExclude();
49+
$exclude = array_merge($exclude, $tagName->getExclude());
5150
$tagName = $tagName->getTag();
5251
}
5352

src/Symfony/Component/DependencyInjection/Compiler/ResolveTaggedIteratorArgumentPass.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
2828
return parent::processValue($value, $isRoot);
2929
}
3030

31-
$value->setValues($this->findAndSortTaggedServices($value, $this->container));
31+
$exclude = $value->getExclude();
32+
if ($value->excludeSelf()) {
33+
$exclude[] = $this->currentId;
34+
}
35+
36+
$value->setValues($this->findAndSortTaggedServices($value, $this->container, $exclude));
3237

3338
return $value;
3439
}

src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ private function getArgumentsAsPhp(\DOMElement $node, string $name, string $file
550550
$excludes = [$arg->getAttribute('exclude')];
551551
}
552552

553-
$arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null, $forLocator, $arg->getAttribute('default-priority-method') ?: null, $excludes);
553+
$arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null, $forLocator, $arg->getAttribute('default-priority-method') ?: null, $excludes, $arg->getAttribute('exclude-self') ?: true);
554554

555555
if ($forLocator) {
556556
$arguments[$key] = new ServiceLocatorArgument($arguments[$key]);

src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -824,11 +824,11 @@ private function resolveServices(mixed $value, string $file, bool $isParameter =
824824
$forLocator = 'tagged_locator' === $value->getTag();
825825

826826
if (\is_array($argument) && isset($argument['tag']) && $argument['tag']) {
827-
if ($diff = array_diff(array_keys($argument), $supportedKeys = ['tag', 'index_by', 'default_index_method', 'default_priority_method', 'exclude'])) {
827+
if ($diff = array_diff(array_keys($argument), $supportedKeys = ['tag', 'index_by', 'default_index_method', 'default_priority_method', 'exclude', 'exclude_self'])) {
828828
throw new InvalidArgumentException(sprintf('"!%s" tag contains unsupported key "%s"; supported ones are "%s".', $value->getTag(), implode('", "', $diff), implode('", "', $supportedKeys)));
829829
}
830830

831-
$argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null, $forLocator, $argument['default_priority_method'] ?? null, (array) ($argument['exclude'] ?? null));
831+
$argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null, $forLocator, $argument['default_priority_method'] ?? null, (array) ($argument['exclude'] ?? null), $argument['exclude_self'] ?? true);
832832
} elseif (\is_string($argument) && $argument) {
833833
$argument = new TaggedIteratorArgument($argument, null, null, $forLocator);
834834
} else {

src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@
302302
<xsd:attribute name="default-index-method" type="xsd:string" />
303303
<xsd:attribute name="default-priority-method" type="xsd:string" />
304304
<xsd:attribute name="exclude" type="xsd:string" />
305+
<xsd:attribute name="exclude-self" type="xsd:boolean" />
305306
</xsd:complexType>
306307

307308
<xsd:complexType name="call">

src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,6 +1142,21 @@ public function testArgumentWithTarget()
11421142
$this->assertSame(BarInterface::class.' $imageStorage', (string) $container->getDefinition('with_target')->getArgument(0));
11431143
}
11441144

1145+
public function testArgumentWithTypoTarget()
1146+
{
1147+
$container = new ContainerBuilder();
1148+
1149+
$container->register(BarInterface::class, BarInterface::class);
1150+
$container->register(BarInterface::class.' $iamgeStorage', BarInterface::class);
1151+
$container->register('with_target', WithTarget::class)
1152+
->setAutowired(true);
1153+
1154+
$this->expectException(AutowiringFailedException::class);
1155+
$this->expectExceptionMessage('Cannot autowire service "with_target": "#[Target(\'imageStorage\')" on argument "$bar" of method "Symfony\Component\DependencyInjection\Tests\Fixtures\WithTarget::__construct()"');
1156+
1157+
(new AutowirePass())->process($container);
1158+
}
1159+
11451160
public function testDecorationWithServiceAndAliasedInterface()
11461161
{
11471162
$container = new ContainerBuilder();

0 commit comments

Comments
 (0)
0