Skip to content

Commit 6857978

Browse files
committed
Merge branch '5.4' into 6.0
* 5.4: [SecurityBundle] Fix compat with symfony/security-core:^6 [DependencyInjection] Fix support for unions/intersections together with `ServiceSubscriberInterface` fixed leftover deprecations PHP 8.1 [Runtime] fix defining APP_DEBUG when Dotenv is not enabled revert using functions provided by polyfill packages [FrameworkBundle] Fix logic in workflow:dump between workflow name and workflow id Bump Symfony version to 5.4.0 Update VERSION for 5.4.0-BETA1 Update CHANGELOG for 5.4.0-BETA1 Add getters and setters for attributes property
2 parents c9c7c7f + 8fe6171 commit 6857978

8 files changed

+232
-5
lines changed

Compiler/AutowirePass.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
100100
private function doProcessValue(mixed $value, bool $isRoot = false): mixed
101101
{
102102
if ($value instanceof TypedReference) {
103-
if ($ref = $this->getAutowiredReference($value)) {
103+
if ($ref = $this->getAutowiredReference($value, true)) {
104104
return $ref;
105105
}
106106
if (ContainerBuilder::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) {
@@ -291,7 +291,7 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
291291
}
292292

293293
$getValue = function () use ($type, $parameter, $class, $method) {
294-
if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, Target::parseName($parameter)))) {
294+
if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, Target::parseName($parameter)), true)) {
295295
$failureMessage = $this->createTypeNotFoundMessageCallback($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method));
296296

297297
if ($parameter->isDefaultValueAvailable()) {
@@ -346,7 +346,7 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
346346
/**
347347
* Returns a reference to the service matching the given type, if any.
348348
*/
349-
private function getAutowiredReference(TypedReference $reference): ?TypedReference
349+
private function getAutowiredReference(TypedReference $reference, bool $filterType): ?TypedReference
350350
{
351351
$this->lastFailure = null;
352352
$type = $reference->getType();
@@ -355,6 +355,14 @@ private function getAutowiredReference(TypedReference $reference): ?TypedReferen
355355
return $reference;
356356
}
357357

358+
if ($filterType && false !== $m = strpbrk($type, '&|')) {
359+
$types = array_diff(explode($m[0], $type), ['int', 'string', 'array', 'bool', 'float', 'iterable', 'object', 'callable', 'null']);
360+
361+
sort($types);
362+
363+
$type = implode($m[0], $types);
364+
}
365+
358366
if (null !== $name = $reference->getName()) {
359367
if ($this->container->has($alias = $type.' $'.$name) && !$this->container->findDefinition($alias)->isAbstract()) {
360368
return new TypedReference($alias, $type, $reference->getInvalidBehavior());

Compiler/RegisterServiceSubscribersPass.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
7373
$subscriberMap = [];
7474

7575
foreach ($class::getSubscribedServices() as $key => $type) {
76-
if (!\is_string($type) || !preg_match('/^\??[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+$/', $type)) {
76+
if (!\is_string($type) || !preg_match('/(?(DEFINE)(?<cn>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))(?(DEFINE)(?<fqcn>(?&cn)(?:\\\\(?&cn))*+))^\??(?&fqcn)(?:(?:\|(?&fqcn))*+|(?:&(?&fqcn))*+)$/', $type)) {
7777
throw new InvalidArgumentException(sprintf('"%s::getSubscribedServices()" must return valid PHP types for service "%s" key "%s", "%s" returned.', $class, $this->currentId, $key, \is_string($type) ? $type : get_debug_type($type)));
7878
}
7979
if ($optionalBehavior = '?' === $type[0]) {

Dumper/PhpDumper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1726,7 +1726,7 @@ private function dumpValue(mixed $value, bool $interpolate = true): string
17261726

17271727
$returnedType = '';
17281728
if ($value instanceof TypedReference) {
1729-
$returnedType = sprintf(': %s\%s', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $value->getInvalidBehavior() ? '' : '?', $value->getType());
1729+
$returnedType = sprintf(': %s\%s', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $value->getInvalidBehavior() ? '' : '?', str_replace(['|', '&'], ['|\\', '&\\'], $value->getType()));
17301730
}
17311731

17321732
$code = sprintf('return %s;', $code);

Tests/Compiler/RegisterServiceSubscribersPassTest.php

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@
2929
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition3;
3030
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber;
3131
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriberChild;
32+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriberIntersection;
33+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriberIntersectionWithTrait;
3234
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriberParent;
35+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriberUnion;
36+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriberUnionWithTrait;
3337
use Symfony\Component\DependencyInjection\TypedReference;
3438
use Symfony\Contracts\Service\Attribute\SubscribedService;
3539
use Symfony\Contracts\Service\ServiceSubscriberInterface;
@@ -127,6 +131,78 @@ public function testWithAttributes()
127131
$this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0));
128132
}
129133

134+
/**
135+
* @requires PHP 8
136+
*/
137+
public function testUnionServices()
138+
{
139+
$container = new ContainerBuilder();
140+
141+
$container->register('bar', \stdClass::class);
142+
$container->setAlias(TestDefinition1::class, 'bar');
143+
$container->setAlias(TestDefinition2::class, 'bar');
144+
$container->register('foo', TestServiceSubscriberUnion::class)
145+
->addArgument(new Reference(PsrContainerInterface::class))
146+
->addTag('container.service_subscriber')
147+
;
148+
149+
(new RegisterServiceSubscribersPass())->process($container);
150+
(new ResolveServiceSubscribersPass())->process($container);
151+
152+
$foo = $container->getDefinition('foo');
153+
$locator = $container->getDefinition((string) $foo->getArgument(0));
154+
155+
$this->assertFalse($locator->isPublic());
156+
$this->assertSame(ServiceLocator::class, $locator->getClass());
157+
158+
$expected = [
159+
'string|'.TestDefinition2::class.'|'.TestDefinition1::class => new ServiceClosureArgument(new TypedReference('string|'.TestDefinition2::class.'|'.TestDefinition1::class, 'string|'.TestDefinition2::class.'|'.TestDefinition1::class)),
160+
'bar' => new ServiceClosureArgument(new TypedReference(TestDefinition1::class.'|'.TestDefinition2::class, TestDefinition1::class.'|'.TestDefinition2::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'bar')),
161+
'baz' => new ServiceClosureArgument(new TypedReference(TestDefinition1::class.'|'.TestDefinition2::class, TestDefinition1::class.'|'.TestDefinition2::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE, 'baz')),
162+
];
163+
164+
$this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0));
165+
166+
(new AutowirePass())->process($container);
167+
168+
$expected = [
169+
'string|'.TestDefinition2::class.'|'.TestDefinition1::class => new ServiceClosureArgument(new TypedReference('bar', TestDefinition1::class.'|'.TestDefinition2::class)),
170+
'bar' => new ServiceClosureArgument(new TypedReference('bar', TestDefinition1::class.'|'.TestDefinition2::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'bar')),
171+
'baz' => new ServiceClosureArgument(new TypedReference('bar', TestDefinition1::class.'|'.TestDefinition2::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE, 'baz')),
172+
];
173+
$this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0));
174+
}
175+
176+
/**
177+
* @requires PHP 8.1
178+
*/
179+
public function testIntersectionServices()
180+
{
181+
$container = new ContainerBuilder();
182+
183+
$container->register('foo', TestServiceSubscriberIntersection::class)
184+
->addArgument(new Reference(PsrContainerInterface::class))
185+
->addTag('container.service_subscriber')
186+
;
187+
188+
(new RegisterServiceSubscribersPass())->process($container);
189+
(new ResolveServiceSubscribersPass())->process($container);
190+
191+
$foo = $container->getDefinition('foo');
192+
$locator = $container->getDefinition((string) $foo->getArgument(0));
193+
194+
$this->assertFalse($locator->isPublic());
195+
$this->assertSame(ServiceLocator::class, $locator->getClass());
196+
197+
$expected = [
198+
TestDefinition1::class.'&'.TestDefinition2::class => new ServiceClosureArgument(new TypedReference(TestDefinition1::class.'&'.TestDefinition2::class, TestDefinition1::class.'&'.TestDefinition2::class)),
199+
'bar' => new ServiceClosureArgument(new TypedReference(TestDefinition1::class.'&'.TestDefinition2::class, TestDefinition1::class.'&'.TestDefinition2::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'bar')),
200+
'baz' => new ServiceClosureArgument(new TypedReference(TestDefinition1::class.'&'.TestDefinition2::class, TestDefinition1::class.'&'.TestDefinition2::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE, 'baz')),
201+
];
202+
203+
$this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0));
204+
}
205+
130206
public function testExtraServiceSubscriber()
131207
{
132208
$this->expectException(InvalidArgumentException::class);
@@ -234,6 +310,65 @@ public function method()
234310
$subscriber::getSubscribedServices();
235311
}
236312

313+
/**
314+
* @requires PHP 8
315+
*/
316+
public function testServiceSubscriberTraitWithUnionReturnType()
317+
{
318+
if (!class_exists(SubscribedService::class)) {
319+
$this->markTestSkipped('SubscribedService attribute not available.');
320+
}
321+
322+
$container = new ContainerBuilder();
323+
324+
$container->register('foo', TestServiceSubscriberUnionWithTrait::class)
325+
->addMethodCall('setContainer', [new Reference(PsrContainerInterface::class)])
326+
->addTag('container.service_subscriber')
327+
;
328+
329+
(new RegisterServiceSubscribersPass())->process($container);
330+
(new ResolveServiceSubscribersPass())->process($container);
331+
332+
$foo = $container->getDefinition('foo');
333+
$locator = $container->getDefinition((string) $foo->getMethodCalls()[0][1][0]);
334+
335+
$expected = [
336+
TestServiceSubscriberUnionWithTrait::class.'::method1' => new ServiceClosureArgument(new TypedReference(TestDefinition1::class.'|'.TestDefinition2::class.'|null', TestDefinition1::class.'|'.TestDefinition2::class.'|null', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
337+
TestServiceSubscriberUnionWithTrait::class.'::method2' => new ServiceClosureArgument(new TypedReference(TestDefinition1::class.'|'.TestDefinition2::class, TestDefinition1::class.'|'.TestDefinition2::class)),
338+
];
339+
340+
$this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0));
341+
}
342+
343+
/**
344+
* @requires PHP 8.1
345+
*/
346+
public function testServiceSubscriberTraitWithIntersectionReturnType()
347+
{
348+
if (!class_exists(SubscribedService::class)) {
349+
$this->markTestSkipped('SubscribedService attribute not available.');
350+
}
351+
352+
$container = new ContainerBuilder();
353+
354+
$container->register('foo', TestServiceSubscriberIntersectionWithTrait::class)
355+
->addMethodCall('setContainer', [new Reference(PsrContainerInterface::class)])
356+
->addTag('container.service_subscriber')
357+
;
358+
359+
(new RegisterServiceSubscribersPass())->process($container);
360+
(new ResolveServiceSubscribersPass())->process($container);
361+
362+
$foo = $container->getDefinition('foo');
363+
$locator = $container->getDefinition((string) $foo->getMethodCalls()[0][1][0]);
364+
365+
$expected = [
366+
TestServiceSubscriberIntersectionWithTrait::class.'::method1' => new ServiceClosureArgument(new TypedReference(TestDefinition1::class.'&'.TestDefinition2::class, TestDefinition1::class.'&'.TestDefinition2::class)),
367+
];
368+
369+
$this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0));
370+
}
371+
237372
public function testServiceSubscriberWithSemanticId()
238373
{
239374
$container = new ContainerBuilder();
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
4+
5+
use Symfony\Contracts\Service\ServiceSubscriberInterface;
6+
7+
class TestServiceSubscriberIntersection implements ServiceSubscriberInterface
8+
{
9+
public function __construct($container)
10+
{
11+
}
12+
13+
public static function getSubscribedServices(): array
14+
{
15+
return [
16+
TestDefinition1::class.'&'.TestDefinition2::class,
17+
'bar' => TestDefinition1::class.'&'.TestDefinition2::class,
18+
'baz' => '?'.TestDefinition1::class.'&'.TestDefinition2::class,
19+
];
20+
}
21+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
4+
5+
use Symfony\Contracts\Service\Attribute\SubscribedService;
6+
use Symfony\Contracts\Service\ServiceSubscriberInterface;
7+
use Symfony\Contracts\Service\ServiceSubscriberTrait;
8+
9+
class TestServiceSubscriberIntersectionWithTrait implements ServiceSubscriberInterface
10+
{
11+
use ServiceSubscriberTrait;
12+
13+
#[SubscribedService]
14+
private function method1(): TestDefinition1&TestDefinition2
15+
{
16+
return $this->container->get(__METHOD__);
17+
}
18+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
4+
5+
use Symfony\Contracts\Service\ServiceSubscriberInterface;
6+
7+
class TestServiceSubscriberUnion implements ServiceSubscriberInterface
8+
{
9+
public function __construct($container)
10+
{
11+
}
12+
13+
public static function getSubscribedServices(): array
14+
{
15+
return [
16+
'string|'.TestDefinition2::class.'|'.TestDefinition1::class,
17+
'bar' => TestDefinition1::class.'|'.TestDefinition2::class,
18+
'baz' => '?'.TestDefinition1::class.'|'.TestDefinition2::class,
19+
];
20+
}
21+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
4+
5+
use Symfony\Contracts\Service\Attribute\SubscribedService;
6+
use Symfony\Contracts\Service\ServiceSubscriberInterface;
7+
use Symfony\Contracts\Service\ServiceSubscriberTrait;
8+
9+
class TestServiceSubscriberUnionWithTrait implements ServiceSubscriberInterface
10+
{
11+
use ServiceSubscriberTrait;
12+
13+
#[SubscribedService]
14+
private function method1(): TestDefinition1|TestDefinition2|null
15+
{
16+
return $this->container->get(__METHOD__);
17+
}
18+
19+
#[SubscribedService]
20+
private function method2(): TestDefinition1|TestDefinition2
21+
{
22+
return $this->container->get(__METHOD__);
23+
}
24+
}

0 commit comments

Comments
 (0)