Skip to content

Commit 00bbe9c

Browse files
Merge branch '7.4' into 8.0
* 7.4: [Translation][Lokalise] fix "Project too big for sync export" [DependencyInjection] Fix lazy proxy creation for interfaces aliased to final classes [HttpKernel] Fix StreamedResponse with chunks support in HttpKernelBrowser [HttpFoundation] Fix AcceptHeader overwrites items with different parameters [JsonStreamer] Rebuild cache on class update [Routing] Fix default value not taken if usigng name:entity.attribute [Mime] Remove unused variable in Email::prepareParts [DependencyInjection] Fix merging explicit tags and #[AsTaggeditem]
2 parents 9cfb4ba + b59a54f commit 00bbe9c

File tree

6 files changed

+204
-69
lines changed

6 files changed

+204
-69
lines changed

Attribute/AsTaggedItem.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
class AsTaggedItem
2121
{
2222
/**
23-
* @param string|null $index The property or method to use to index the item in the iterator/locator
24-
* @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
23+
* @param string|null $index The index at which the service will be found when consuming tagged iterators/locators
24+
* @param int|null $priority The priority of the service in iterators/locators; the higher the number, the earlier it will
2525
*/
2626
public function __construct(
2727
public ?string $index = null,

Compiler/AutowirePass.php

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -335,9 +335,7 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
335335
$value = $this->doProcessValue($value);
336336
} elseif ($lazy = $attribute->lazy) {
337337
$value ??= $getValue();
338-
if ($this->container->has($value->getType())) {
339-
$type = $this->container->findDefinition($value->getType())->getClass();
340-
}
338+
$type = $this->resolveProxyType($type, $value->getType());
341339
$definition = (new Definition($type))
342340
->setFactory('current')
343341
->setArguments([[$value]])
@@ -743,4 +741,26 @@ private function getCombinedAlias(string $type, ?string $name = null, ?string $t
743741

744742
return $alias;
745743
}
744+
745+
/**
746+
* Resolves the class name that should be proxied for a lazy service.
747+
*
748+
* @param string $originalType The original parameter type-hint (e.g., the interface)
749+
* @param string $serviceId The service ID the type-hint resolved to (e.g., the alias)
750+
*/
751+
private function resolveProxyType(string $originalType, string $serviceId): string
752+
{
753+
if (!$this->container->has($serviceId)) {
754+
return $originalType;
755+
}
756+
757+
$resolvedType = $this->container->findDefinition($serviceId)->getClass();
758+
$resolvedType = $this->container->getParameterBag()->resolveValue($resolvedType);
759+
760+
if (!$resolvedType || !class_exists($resolvedType, false) && !interface_exists($resolvedType, false)) {
761+
return $originalType;
762+
}
763+
764+
return $resolvedType;
765+
}
746766
}

Compiler/PriorityTaggedServiceTrait.php

Lines changed: 41 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -65,61 +65,69 @@ private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagNam
6565
$class = $definition->getClass();
6666
$class = $container->getParameterBag()->resolveValue($class) ?: null;
6767
$reflector = null !== $class ? $container->getReflectionClass($class) : null;
68-
$checkTaggedItem = !$definition->hasTag($definition->isAutoconfigured() ? 'container.ignore_attributes' : $tagName);
68+
$loadFromDefaultMethods = $reflector && null !== $defaultPriorityMethod;
69+
$phpAttributes = $definition->isAutoconfigured() && !$definition->hasTag('container.ignore_attributes') ? $reflector?->getAttributes(AsTaggedItem::class) : [];
70+
71+
foreach ($phpAttributes ??= [] as $i => $attribute) {
72+
$attribute = $attribute->newInstance();
73+
$phpAttributes[$i] = [
74+
'priority' => $attribute->priority,
75+
$indexAttribute ?? '' => $attribute->index,
76+
];
77+
if (null === $defaultPriority) {
78+
$defaultPriority = $attribute->priority ?? 0;
79+
$defaultIndex = $attribute->index;
80+
}
81+
}
82+
if (1 >= \count($phpAttributes)) {
83+
$phpAttributes = [];
84+
}
85+
86+
for ($i = 0; $i < \count($attributes); ++$i) {
87+
if (!($attribute = $attributes[$i]) && $phpAttributes) {
88+
array_splice($attributes, $i--, 1, $phpAttributes);
89+
continue;
90+
}
6991

70-
foreach ($attributes as $attribute) {
7192
$index = $priority = null;
7293

7394
if (isset($attribute['priority'])) {
7495
$priority = $attribute['priority'];
75-
} elseif (null === $defaultPriority && $defaultPriorityMethod && $reflector) {
76-
$defaultPriority = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultPriorityMethod, $tagName, 'priority', $checkTaggedItem);
96+
} elseif ($loadFromDefaultMethods) {
97+
$defaultPriority = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultPriorityMethod, $tagName, 'priority') ?? $defaultPriority;
98+
$defaultIndex = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute) ?? $defaultIndex;
99+
$loadFromDefaultMethods = false;
77100
}
78101
$priority ??= $defaultPriority ??= 0;
79102

80103
if (null === $indexAttribute && !$defaultIndexMethod && !$needsIndexes) {
81-
$services[] = [$priority, ++$i, null, $serviceId, null];
104+
$services[] = [$priority, $i, null, $serviceId, null];
82105
continue 2;
83106
}
84107

85108
if (null !== $indexAttribute && isset($attribute[$indexAttribute])) {
86109
$index = $parameterBag->resolveValue($attribute[$indexAttribute]);
87110
}
88-
if (null === $index && null === $defaultIndex && $defaultPriorityMethod && $reflector) {
89-
$defaultIndex = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute, $checkTaggedItem);
111+
if (null === $index && $loadFromDefaultMethods) {
112+
$defaultPriority = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultPriorityMethod, $tagName, 'priority') ?? $defaultPriority;
113+
$defaultIndex = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute) ?? $defaultIndex;
114+
$loadFromDefaultMethods = false;
90115
}
91116
$index ??= $defaultIndex ??= $definition->getTag('container.decorator')[0]['id'] ?? $serviceId;
92117

93-
$services[] = [$priority, ++$i, $index, $serviceId, $class];
94-
}
95-
96-
if ($reflector) {
97-
$attributes = $reflector->getAttributes(AsTaggedItem::class);
98-
$attributeCount = \count($attributes);
99-
100-
foreach ($attributes as $attribute) {
101-
$instance = $attribute->newInstance();
102-
103-
if (!$instance->index && 1 < $attributeCount) {
104-
throw new InvalidArgumentException(\sprintf('Attribute "%s" on class "%s" cannot have an empty index when repeated.', AsTaggedItem::class, $class));
105-
}
106-
107-
$services[] = [$instance->priority ?? 0, ++$i, $instance->index ?? $serviceId, $serviceId, $class];
108-
}
118+
$services[] = [$priority, $i, $index, $serviceId, $class];
109119
}
110120
}
111121

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

114124
$refs = [];
115125
foreach ($services as [, , $index, $serviceId, $class]) {
116-
if (!$class) {
117-
$reference = new Reference($serviceId);
118-
} elseif ($index === $serviceId) {
119-
$reference = new TypedReference($serviceId, $class);
120-
} else {
121-
$reference = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $index);
122-
}
126+
$reference = match (true) {
127+
!$class => new Reference($serviceId),
128+
$index === $serviceId => new TypedReference($serviceId, $class),
129+
default => new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $index),
130+
};
123131

124132
if (null === $index) {
125133
$refs[] = $reference;
@@ -137,25 +145,16 @@ private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagNam
137145
*/
138146
class PriorityTaggedServiceUtil
139147
{
140-
public static function getDefault(string $serviceId, \ReflectionClass $r, string $defaultMethod, string $tagName, ?string $indexAttribute, bool $checkTaggedItem): string|int|null
148+
public static function getDefault(string $serviceId, \ReflectionClass $r, string $defaultMethod, string $tagName, ?string $indexAttribute): string|int|null
141149
{
142-
$class = $r->getName();
143-
144-
if (!$checkTaggedItem && !$r->hasMethod($defaultMethod)) {
145-
return null;
146-
}
147-
148-
if ($checkTaggedItem && !$r->hasMethod($defaultMethod)) {
149-
foreach ($r->getAttributes(AsTaggedItem::class) as $attribute) {
150-
return 'priority' === $indexAttribute ? $attribute->newInstance()->priority : $attribute->newInstance()->index;
151-
}
152-
150+
if (!$r->hasMethod($defaultMethod)) {
153151
return null;
154152
}
155153

156154
if ($r->isInterface()) {
157155
return null;
158156
}
157+
$class = $r->name;
159158

160159
if (null !== $indexAttribute) {
161160
$service = $class !== $serviceId ? \sprintf('service "%s"', $serviceId) : 'on the corresponding service';

Tests/Compiler/AutowirePassTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,4 +1446,45 @@ public function testAutowireAttributeWithEnvVar()
14461446
$this->assertSame('%env(bool:ENABLED)%', $container->resolveEnvPlaceholders($definition->getArguments()[0]));
14471447
$this->assertSame('%env(default::OPTIONAL)%', $container->resolveEnvPlaceholders($definition->getArguments()[1]));
14481448
}
1449+
1450+
public function testLazyProxyForInterfaceWithFinalImplementation()
1451+
{
1452+
$container = new ContainerBuilder();
1453+
$container->register('final_impl', FinalLazyProxyImplementation::class);
1454+
$container->setAlias(LazyProxyTestInterface::class, 'final_impl');
1455+
1456+
$container->register(LazyProxyInterfaceConsumer::class)
1457+
->setAutoconfigured(true)
1458+
->setAutowired(true)
1459+
->setPublic(true);
1460+
1461+
$container->compile();
1462+
1463+
$service = $container->get(LazyProxyInterfaceConsumer::class);
1464+
$this->assertInstanceOf(LazyProxyInterfaceConsumer::class, $service);
1465+
1466+
// Trigger lazy load
1467+
$dep = $service->getDep()->getSelf();
1468+
$this->assertInstanceOf(FinalLazyProxyImplementation::class, $dep);
1469+
}
1470+
1471+
public function testLazyProxyWithClassInheritance()
1472+
{
1473+
$container = new ContainerBuilder();
1474+
$container->register(BaseLazyProxyClass::class, ExtendedLazyProxyClass::class);
1475+
1476+
$container->register(LazyProxyInheritanceConsumer::class)
1477+
->setAutoconfigured(true)
1478+
->setAutowired(true)
1479+
->setPublic(true);
1480+
1481+
$container->compile();
1482+
1483+
$service = $container->get(LazyProxyInheritanceConsumer::class);
1484+
$this->assertInstanceOf(LazyProxyInheritanceConsumer::class, $service);
1485+
1486+
// Trigger lazy load
1487+
$dep = $service->getDependency()->getSelf();
1488+
$this->assertInstanceOf(ExtendedLazyProxyClass::class, $dep);
1489+
}
14491490
}

Tests/Compiler/PriorityTaggedServiceTraitTest.php

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -232,29 +232,14 @@ public function testTaggedItemAttributes()
232232
'hello' => new TypedReference('service2', HelloNamedService::class),
233233
'multi_hello_1' => new TypedReference('service6', MultiTagHelloNamedService::class),
234234
'service1' => new TypedReference('service1', FooTagClass::class),
235+
'multi_hello_0' => new TypedReference('service6', MultiTagHelloNamedService::class),
235236
];
236237

237238
$services = $priorityTaggedServiceTraitImplementation->test($tag, $container);
238239
$this->assertSame(array_keys($expected), array_keys($services));
239240
$this->assertEquals($expected, $priorityTaggedServiceTraitImplementation->test($tag, $container));
240241
}
241242

242-
public function testTaggedItemAttributesRepeatedWithoutNameThrows()
243-
{
244-
$container = new ContainerBuilder();
245-
$container->register('service1', MultiNoNameTagHelloNamedService::class)
246-
->setAutoconfigured(true)
247-
->addTag('my_custom_tag');
248-
249-
(new ResolveInstanceofConditionalsPass())->process($container);
250-
$tag = new TaggedIteratorArgument('my_custom_tag', 'foo', 'getFooBar', exclude: ['service4', 'service5']);
251-
252-
$this->expectException(InvalidArgumentException::class);
253-
$this->expectExceptionMessage('Attribute "Symfony\Component\DependencyInjection\Attribute\AsTaggedItem" on class "Symfony\Component\DependencyInjection\Tests\Compiler\MultiNoNameTagHelloNamedService" cannot have an empty index when repeated.');
254-
255-
(new PriorityTaggedServiceTraitImplementation())->test($tag, $container);
256-
}
257-
258243
public function testResolveIndexedTags()
259244
{
260245
$container = new ContainerBuilder();
@@ -282,6 +267,48 @@ public function testResolveIndexedTags()
282267
$this->assertSame(array_keys($expected), array_keys($services));
283268
$this->assertEquals($expected, $priorityTaggedServiceTraitImplementation->test($tag, $container));
284269
}
270+
271+
public function testAttributesAreMergedWithTags()
272+
{
273+
$container = new ContainerBuilder();
274+
$definition = $container->register('service_attr_first', MultiTagHelloNamedService::class);
275+
$definition->setAutoconfigured(true);
276+
$definition->addTag('my_custom_tag', ['foo' => 'z']);
277+
$definition->addTag('my_custom_tag', []);
278+
279+
(new ResolveInstanceofConditionalsPass())->process($container);
280+
281+
$priorityTaggedServiceTraitImplementation = new PriorityTaggedServiceTraitImplementation();
282+
283+
$tag = new TaggedIteratorArgument('my_custom_tag', 'foo', 'getFooBar');
284+
$services = $priorityTaggedServiceTraitImplementation->test($tag, $container);
285+
286+
$expected = [
287+
'multi_hello_2' => new TypedReference('service_attr_first', MultiTagHelloNamedService::class),
288+
'multi_hello_1' => new TypedReference('service_attr_first', MultiTagHelloNamedService::class),
289+
'z' => new TypedReference('service_attr_first', MultiTagHelloNamedService::class),
290+
'multi_hello_0' => new TypedReference('service_attr_first', MultiTagHelloNamedService::class),
291+
];
292+
$this->assertSame(array_keys($expected), array_keys($services));
293+
$this->assertEquals($expected, $services);
294+
}
295+
296+
public function testAttributesAreFallbacks()
297+
{
298+
$container = new ContainerBuilder();
299+
$definition = $container->register('service_attr_first', MultiTagHelloNamedService::class);
300+
$definition->setAutoconfigured(true);
301+
$definition->addTag('my_custom_tag', ['foo' => 'z']);
302+
303+
(new ResolveInstanceofConditionalsPass())->process($container);
304+
305+
$priorityTaggedServiceTraitImplementation = new PriorityTaggedServiceTraitImplementation();
306+
307+
$tag = new TaggedIteratorArgument('my_custom_tag', 'foo', 'getFooBar');
308+
$services = $priorityTaggedServiceTraitImplementation->test($tag, $container);
309+
310+
$this->assertEquals(['z' => new TypedReference('service_attr_first', MultiTagHelloNamedService::class)], $services);
311+
}
285312
}
286313

287314
class PriorityTaggedServiceTraitImplementation
@@ -304,18 +331,13 @@ class HelloNamedService2
304331
{
305332
}
306333

334+
#[AsTaggedItem(index: 'multi_hello_0', priority: 0)]
307335
#[AsTaggedItem(index: 'multi_hello_1', priority: 1)]
308336
#[AsTaggedItem(index: 'multi_hello_2', priority: 2)]
309337
class MultiTagHelloNamedService
310338
{
311339
}
312340

313-
#[AsTaggedItem(priority: 1)]
314-
#[AsTaggedItem(priority: 2)]
315-
class MultiNoNameTagHelloNamedService
316-
{
317-
}
318-
319341
interface HelloInterface
320342
{
321343
public static function getFooBar(): string;

Tests/Fixtures/includes/autowiring_classes.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,3 +524,56 @@ public static function staticCreateFooWithParam(mixed $someParam): MyInlineServi
524524
return new MyInlineService($someParam);
525525
}
526526
}
527+
528+
interface LazyProxyTestInterface
529+
{
530+
public function getSelf(): self;
531+
}
532+
533+
final class FinalLazyProxyImplementation implements LazyProxyTestInterface
534+
{
535+
public function getSelf(): self
536+
{
537+
return $this;
538+
}
539+
}
540+
541+
class BaseLazyProxyClass
542+
{
543+
public function getSelf(): self
544+
{
545+
return $this;
546+
}
547+
}
548+
549+
class ExtendedLazyProxyClass extends BaseLazyProxyClass
550+
{
551+
public function getSelf(): self
552+
{
553+
return $this;
554+
}
555+
}
556+
557+
class LazyProxyInterfaceConsumer
558+
{
559+
public function __construct(#[Autowire(lazy: true)] private readonly LazyProxyTestInterface $dep)
560+
{
561+
}
562+
563+
public function getDep(): LazyProxyTestInterface
564+
{
565+
return $this->dep;
566+
}
567+
}
568+
569+
class LazyProxyInheritanceConsumer
570+
{
571+
public function __construct(#[Autowire(lazy: true)] private readonly BaseLazyProxyClass $dep)
572+
{
573+
}
574+
575+
public function getDependency(): BaseLazyProxyClass
576+
{
577+
return $this->dep;
578+
}
579+
}

0 commit comments

Comments
 (0)