Skip to content

Commit a5504e7

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 81357bf + 3b71d1a commit a5504e7

File tree

15 files changed

+273
-19
lines changed

15 files changed

+273
-19
lines changed

src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Symfony\Component\Console\Input\InputInterface;
2121
use Symfony\Component\Console\Input\InputOption;
2222
use Symfony\Component\Console\Output\OutputInterface;
23+
use Symfony\Component\Workflow\Definition;
2324
use Symfony\Component\Workflow\Dumper\GraphvizDumper;
2425
use Symfony\Component\Workflow\Dumper\MermaidDumper;
2526
use Symfony\Component\Workflow\Dumper\PlantUmlDumper;
@@ -34,6 +35,10 @@
3435
#[AsCommand(name: 'workflow:dump', description: 'Dump a workflow')]
3536
class WorkflowDumpCommand extends Command
3637
{
38+
/**
39+
* string is the service id
40+
* @var array<string, Definition>
41+
*/
3742
private array $workflows = [];
3843

3944
private const DUMP_FORMAT_OPTIONS = [
@@ -78,13 +83,21 @@ protected function configure()
7883
*/
7984
protected function execute(InputInterface $input, OutputInterface $output): int
8085
{
81-
$workflowId = $input->getArgument('name');
86+
$workflowName = $input->getArgument('name');
87+
88+
$workflow = null;
8289

83-
if (!\in_array($workflowId, array_keys($this->workflows), true)) {
84-
throw new InvalidArgumentException(sprintf('No service found for "workflow.%1$s" nor "state_machine.%1$s".', $workflowId));
90+
if (isset($this->workflows['workflow.'.$workflowName])) {
91+
$workflow = $this->workflows['workflow.'.$workflowName];
92+
$type = 'workflow';
93+
} elseif (isset($this->workflows['state_machine.'.$workflowName])) {
94+
$workflow = $this->workflows['state_machine.'.$workflowName];
95+
$type = 'state_machine';
8596
}
8697

87-
$type = explode('.', $workflowId)[0];
98+
if (null === $workflow) {
99+
throw new InvalidArgumentException(sprintf('No service found for "workflow.%1$s" nor "state_machine.%1$s".', $workflowName));
100+
}
88101

89102
switch ($input->getOption('dump-format')) {
90103
case 'puml':
@@ -108,10 +121,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
108121
$marking->mark($place);
109122
}
110123

111-
$workflow = $this->workflows[$workflowId];
112-
113124
$options = [
114-
'name' => $workflowId,
125+
'name' => $workflowName,
115126
'nofooter' => true,
116127
'graph' => [
117128
'label' => $input->getOption('label'),

src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public function applyVersion(string $path): string
6969
private function getManifestPath(string $path): ?string
7070
{
7171
if (!isset($this->manifestData)) {
72-
if (null !== $this->httpClient && 0 === strpos(parse_url($this->manifestPath, \PHP_URL_SCHEME), 'http')) {
72+
if (null !== $this->httpClient && ($scheme = parse_url($this->manifestPath, \PHP_URL_SCHEME)) && 0 === strpos($scheme, 'http')) {
7373
try {
7474
$this->manifestData = $this->httpClient->request('GET', $this->manifestPath, [
7575
'headers' => ['accept' => 'application/json'],

src/Symfony/Component/DependencyInjection/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());

src/Symfony/Component/DependencyInjection/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]) {

src/Symfony/Component/DependencyInjection/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);

src/Symfony/Component/DependencyInjection/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+
}

src/Symfony/Component/Runtime/SymfonyRuntime.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ public function __construct(array $options = [])
108108
$options['debug'] ?? $options['debug'] = '1' === $_SERVER[$debugKey];
109109
$options['disable_dotenv'] = true;
110110
} else {
111-
$_SERVER[$envKey] ?? $_SERVER[$envKey] = 'dev';
111+
$_SERVER[$envKey] ?? $_SERVER[$envKey] = $_ENV[$envKey] ?? 'dev';
112+
$_SERVER[$debugKey] ?? $_SERVER[$debugKey] = $_ENV[$debugKey] ?? !\in_array($_SERVER[$envKey], (array) ($options['prod_envs'] ?? ['prod']), true);
112113
}
113114

114115
$options['error_handler'] ?? $options['error_handler'] = SymfonyErrorHandler::class;

src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public function __construct(HttpUtils $httpUtils, UserProviderInterface $userPro
6565

6666
public function supports(Request $request): ?bool
6767
{
68-
if (false === strpos($request->getRequestFormat(), 'json') && false === strpos($request->getContentType(), 'json')) {
68+
if (false === strpos($request->getRequestFormat() ?? '', 'json') && false === strpos($request->getContentType() ?? '', 'json')) {
6969
return false;
7070
}
7171

src/Symfony/Component/Security/Http/Authenticator/Passport/Passport.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,9 @@ public function getAttribute(string $name, mixed $default = null): mixed
8989
{
9090
return $this->attributes[$name] ?? $default;
9191
}
92+
93+
public function getAttributes(): array
94+
{
95+
return $this->attributes;
96+
}
9297
}

0 commit comments

Comments
 (0)