Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 09f38cf

Browse files
committed
[DependencyInjection][HttpKernel] Add support for #[TaggedIterator] & #[TaggedLocator] with controller arguments
1 parent 6d5ac90 commit 09f38cf

File tree

6 files changed

+122
-18
lines changed

6 files changed

+122
-18
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
6666

6767
foreach ($services as $k => $v) {
6868
if ($v instanceof ServiceClosureArgument) {
69-
continue;
69+
$closureValue = $v->getValues()[0];
70+
if (!$closureValue instanceof ServiceLocatorArgument) {
71+
continue;
72+
}
73+
$v = $this->processValue($closureValue);
7074
}
7175

7276
if ($i === $k) {

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,40 @@ public function testBindingsAreProcessed()
226226

227227
$this->assertInstanceOf(Reference::class, $definition->getBindings()['foo']->getValues()[0]);
228228
}
229+
230+
public function testServiceLocatorWithServiceLocatorArgument()
231+
{
232+
$container = new ContainerBuilder();
233+
234+
$container->register('foo', Service::class)->addTag('foobar');
235+
$container->register('bar', Service::class)->addTag('foobar');
236+
237+
$reference = ServiceLocatorTagPass::register($container, [
238+
'with_array' => new ServiceLocatorArgument(['foo' => new Reference('foo'), 'bar' => new Reference('bar')]),
239+
'with_tagged_iterator' => new ServiceLocatorArgument(new TaggedIteratorArgument('foobar', needsIndexes: true)),
240+
]);
241+
$container->getDefinition((string) $reference)->setPublic(true);
242+
243+
$container->compile();
244+
245+
/** @var ServiceLocator $locator */
246+
$locator = $container->get((string) $reference);
247+
248+
$this->assertTrue($locator->has('with_array'));
249+
$this->assertInstanceOf(ServiceLocator::class, $withArrayLocator = $locator->get('with_array'));
250+
$this->assertCount(2, $withArrayLocator);
251+
$this->assertTrue($withArrayLocator->has('foo'));
252+
$this->assertTrue($withArrayLocator->has('bar'));
253+
254+
$this->assertTrue($locator->has('with_tagged_iterator'));
255+
$this->assertInstanceOf(ServiceLocator::class, $withTaggedIteratorLocator = $locator->get('with_tagged_iterator'));
256+
$this->assertCount(2, $withTaggedIteratorLocator);
257+
$this->assertTrue($withTaggedIteratorLocator->has('foo'));
258+
$this->assertTrue($withTaggedIteratorLocator->has('bar'));
259+
260+
$this->assertSame($withArrayLocator->has('foo'), $withTaggedIteratorLocator->has('foo'));
261+
$this->assertSame($withArrayLocator->has('bar'), $withTaggedIteratorLocator->has('bar'));
262+
}
229263
}
230264

231265
class Locator

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ CHANGELOG
1010
* Use an instance of `Psr\Clock\ClockInterface` to generate the current date time in `DateTimeValueResolver`
1111
* Add `#[WithLogLevel]` for defining log levels for exceptions
1212
* Add `skip_response_headers` to the `HttpCache` options
13+
* Add support for `#[TaggedIterator]` and `#[TaggedLocator]` with controller arguments
1314

1415
6.2
1516
---

src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111

1212
namespace Symfony\Component\HttpKernel\DependencyInjection;
1313

14+
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
15+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
1416
use Symfony\Component\DependencyInjection\Attribute\Autowire;
17+
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
18+
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
1519
use Symfony\Component\DependencyInjection\Attribute\Target;
1620
use Symfony\Component\DependencyInjection\ChildDefinition;
1721
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
@@ -52,8 +56,6 @@ public function process(ContainerBuilder $container)
5256
}
5357
}
5458

55-
$emptyAutowireAttributes = class_exists(Autowire::class) ? null : [];
56-
5759
foreach ($container->findTaggedServiceIds('controller.service_arguments', true) as $id => $tags) {
5860
$def = $container->getDefinition($id);
5961
$def->setPublic(true);
@@ -126,7 +128,7 @@ public function process(ContainerBuilder $container)
126128
/** @var \ReflectionParameter $p */
127129
$type = preg_replace('/(^|[(|&])\\\\/', '\1', $target = ltrim(ProxyHelper::exportType($p) ?? '', '?'));
128130
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
129-
$autowireAttributes = $autowire ? $emptyAutowireAttributes : [];
131+
$parameterAttributes = $autowire ? null : [];
130132

131133
if (isset($arguments[$r->name][$p->name])) {
132134
$target = $arguments[$r->name][$p->name];
@@ -146,7 +148,7 @@ public function process(ContainerBuilder $container)
146148
$args[$p->name] = $bindingValue;
147149

148150
continue;
149-
} elseif (!$autowire || (!($autowireAttributes ??= $p->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF)) && (!$type || '\\' !== $target[0]))) {
151+
} elseif (!$autowire || (!($parameterAttributes ??= $this->getParameterAttributes($p)) && (!$type || '\\' !== $target[0]))) {
150152
continue;
151153
} elseif (is_subclass_of($type, \UnitEnum::class)) {
152154
// do not attempt to register enum typed arguments if not already present in bindings
@@ -159,16 +161,24 @@ public function process(ContainerBuilder $container)
159161
continue;
160162
}
161163

162-
if ($autowireAttributes) {
163-
$value = $autowireAttributes[0]->newInstance()->value;
164+
if ($parameterAttributes) {
165+
$attributeInstance = $parameterAttributes[0]->newInstance();
164166

165-
if ($value instanceof Reference) {
166-
$args[$p->name] = $type ? new TypedReference($value, $type, $invalidBehavior, $p->name) : new Reference($value, $invalidBehavior);
167+
if ($attributeInstance instanceof TaggedIterator) {
168+
$args[$p->name] = new TaggedIteratorArgument($attributeInstance->tag, $attributeInstance->indexAttribute, $attributeInstance->defaultIndexMethod, false, $attributeInstance->defaultPriorityMethod, (array) $attributeInstance->exclude);
169+
} elseif ($attributeInstance instanceof TaggedLocator) {
170+
$args[$p->name] = new ServiceLocatorArgument(new TaggedIteratorArgument($attributeInstance->tag, $attributeInstance->indexAttribute, $attributeInstance->defaultIndexMethod, true, $attributeInstance->defaultPriorityMethod, (array) $attributeInstance->exclude));
167171
} else {
168-
$args[$p->name] = new Reference('.value.'.$container->hash($value));
169-
$container->register((string) $args[$p->name], 'mixed')
170-
->setFactory('current')
171-
->addArgument([$value]);
172+
$value = $attributeInstance->value;
173+
174+
if ($value instanceof Reference) {
175+
$args[$p->name] = $type ? new TypedReference($value, $type, $invalidBehavior, $p->name) : new Reference($value, $invalidBehavior);
176+
} else {
177+
$args[$p->name] = new Reference('.value.'.$container->hash($value));
178+
$container->register((string) $args[$p->name], 'mixed')
179+
->setFactory('current')
180+
->addArgument([$value]);
181+
}
172182
}
173183

174184
continue;
@@ -216,4 +226,15 @@ public function process(ContainerBuilder $container)
216226

217227
$container->setAlias('argument_resolver.controller_locator', (string) $controllerLocatorRef);
218228
}
229+
230+
private function getParameterAttributes(\ReflectionParameter $parameter): array
231+
{
232+
foreach ([TaggedIterator::class, TaggedLocator::class, Autowire::class] as $attributeClass) {
233+
if ($attributes = $parameter->getAttributes($attributeClass, Autowire::class === $attributeClass ? \ReflectionAttribute::IS_INSTANCEOF : 0)) {
234+
return $attributes;
235+
}
236+
}
237+
238+
return [];
239+
}
219240
}

src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
namespace Symfony\Component\HttpKernel\Tests\DependencyInjection;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
1516
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1617
use Symfony\Component\DependencyInjection\Attribute\Autowire;
18+
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
19+
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
1720
use Symfony\Component\DependencyInjection\Attribute\Target;
1821
use Symfony\Component\DependencyInjection\ChildDefinition;
1922
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
@@ -460,10 +463,6 @@ public function testResponseArgumentIsIgnored()
460463

461464
public function testAutowireAttribute()
462465
{
463-
if (!class_exists(Autowire::class)) {
464-
$this->markTestSkipped('#[Autowire] attribute not available.');
465-
}
466-
467466
$container = new ContainerBuilder();
468467
$resolver = $container->register('argument_resolver.service', 'stdClass')->addArgument([]);
469468

@@ -493,6 +492,42 @@ public function testAutowireAttribute()
493492
$this->assertSame('foo', $locator->get('customAutowire'));
494493
$this->assertFalse($locator->has('service2'));
495494
}
495+
496+
public function testTaggedIteratorAndTaggedLocatorAttributes()
497+
{
498+
$container = new ContainerBuilder();
499+
$resolver = $container->register('argument_resolver.service', \stdClass::class)->addArgument([]);
500+
501+
$container->register('bar', \stdClass::class)->addTag('foobar');
502+
$container->register('baz', \stdClass::class)->addTag('foobar');
503+
504+
$container->register('foo', WithTaggedIteratorAndTaggedLocator::class)
505+
->addTag('controller.service_arguments');
506+
507+
(new RegisterControllerArgumentLocatorsPass())->process($container);
508+
509+
$locatorId = (string) $resolver->getArgument(0);
510+
$container->getDefinition($locatorId)->setPublic(true);
511+
512+
$container->compile();
513+
514+
/** @var ServiceLocator $locator */
515+
$locator = $container->get($locatorId)->get('foo::fooAction');
516+
517+
$this->assertCount(2, $locator->getProvidedServices());
518+
519+
$this->assertTrue($locator->has('iterator'));
520+
$this->assertInstanceOf(RewindableGenerator::class, $argIterator = $locator->get('iterator'));
521+
$this->assertCount(2, $argIterator);
522+
523+
$this->assertTrue($locator->has('locator'));
524+
$this->assertInstanceOf(ServiceLocator::class, $argLocator = $locator->get('locator'));
525+
$this->assertCount(2, $argLocator);
526+
$this->assertTrue($argLocator->has('bar'));
527+
$this->assertTrue($argLocator->has('baz'));
528+
529+
$this->assertSame(iterator_to_array($argIterator), [$argLocator->get('bar'), $argLocator->get('baz')]);
530+
}
496531
}
497532

498533
class RegisterTestController
@@ -614,3 +649,12 @@ public function fooAction(
614649
) {
615650
}
616651
}
652+
653+
class WithTaggedIteratorAndTaggedLocator
654+
{
655+
public function fooAction(
656+
#[TaggedIterator('foobar')] iterable $iterator,
657+
#[TaggedLocator('foobar')] ServiceLocator $locator,
658+
) {
659+
}
660+
}

src/Symfony/Component/HttpKernel/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"symfony/config": "^6.1",
3131
"symfony/console": "^5.4|^6.0",
3232
"symfony/css-selector": "^5.4|^6.0",
33-
"symfony/dependency-injection": "^6.2",
33+
"symfony/dependency-injection": "^6.3",
3434
"symfony/dom-crawler": "^5.4|^6.0",
3535
"symfony/expression-language": "^5.4|^6.0",
3636
"symfony/finder": "^5.4|^6.0",

0 commit comments

Comments
 (0)