Skip to content

Commit 7c31147

Browse files
Merge branch '6.4' into 7.2
* 6.4: [WebProfilerBundle] Fix tests [Cache] Tests for Redis Replication with cache [BrowserKit] Fix submitting forms with empty file fields [WebProfilerBundle] Fix interception for non conventional redirects [DependencyInjection] Do not preload functions [DependencyInjection] Fix cloned lazy services not sharing their dependencies when dumped with PhpDumper [HttpClient] Fix activity tracking leading to negative timeout errors [Security] Return null instead of empty username to fix deprecation notice [DependencyInjection] Fix env default processor with scalar node
2 parents 1d321c4 + e066329 commit 7c31147

14 files changed

+258
-44
lines changed

Compiler/InlineServiceDefinitionsPass.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ private function isInlineableDefinition(string $id, Definition $definition): boo
223223
return false;
224224
}
225225

226-
return $this->container->getDefinition($srcId)->isShared();
226+
$srcDefinition = $this->container->getDefinition($srcId);
227+
228+
return $srcDefinition->isShared() && !$srcDefinition->isLazy();
227229
}
228230
}

Compiler/ValidateEnvPlaceholdersPass.php

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,8 @@ public function process(ContainerBuilder $container): void
4646
$defaultBag = new ParameterBag($resolvingBag->all());
4747
$envTypes = $resolvingBag->getProvidedTypes();
4848
foreach ($resolvingBag->getEnvPlaceholders() + $resolvingBag->getUnusedEnvPlaceholders() as $env => $placeholders) {
49-
$values = [];
50-
if (false === $i = strpos($env, ':')) {
51-
$default = $defaultBag->has("env($env)") ? $defaultBag->get("env($env)") : self::TYPE_FIXTURES['string'];
52-
$defaultType = null !== $default ? get_debug_type($default) : 'string';
53-
$values[$defaultType] = $default;
54-
} else {
55-
$prefix = substr($env, 0, $i);
56-
foreach ($envTypes[$prefix] ?? ['string'] as $type) {
57-
$values[$type] = self::TYPE_FIXTURES[$type] ?? null;
58-
}
59-
}
49+
$values = $this->getPlaceholderValues($env, $defaultBag, $envTypes);
50+
6051
foreach ($placeholders as $placeholder) {
6152
BaseNode::setPlaceholder($placeholder, $values);
6253
}
@@ -97,4 +88,50 @@ public function getExtensionConfig(): array
9788
$this->extensionConfig = [];
9889
}
9990
}
91+
92+
/**
93+
* @param array<string, list<string>> $envTypes
94+
*
95+
* @return array<string, mixed>
96+
*/
97+
private function getPlaceholderValues(string $env, ParameterBag $defaultBag, array $envTypes): array
98+
{
99+
if (false === $i = strpos($env, ':')) {
100+
[$default, $defaultType] = $this->getParameterDefaultAndDefaultType("env($env)", $defaultBag);
101+
102+
return [$defaultType => $default];
103+
}
104+
105+
$prefix = substr($env, 0, $i);
106+
if ('default' === $prefix) {
107+
$parts = explode(':', $env);
108+
array_shift($parts); // Remove 'default' prefix
109+
$parameter = array_shift($parts); // Retrieve and remove parameter
110+
111+
[$defaultParameter, $defaultParameterType] = $this->getParameterDefaultAndDefaultType($parameter, $defaultBag);
112+
113+
return [
114+
$defaultParameterType => $defaultParameter,
115+
...$this->getPlaceholderValues(implode(':', $parts), $defaultBag, $envTypes),
116+
];
117+
}
118+
119+
$values = [];
120+
foreach ($envTypes[$prefix] ?? ['string'] as $type) {
121+
$values[$type] = self::TYPE_FIXTURES[$type] ?? null;
122+
}
123+
124+
return $values;
125+
}
126+
127+
/**
128+
* @return array{0: string, 1: string}
129+
*/
130+
private function getParameterDefaultAndDefaultType(string $name, ParameterBag $defaultBag): array
131+
{
132+
$default = $defaultBag->has($name) ? $defaultBag->get($name) : self::TYPE_FIXTURES['string'];
133+
$defaultType = null !== $default ? get_debug_type($default) : 'string';
134+
135+
return [$default, $defaultType];
136+
}
100137
}

Dumper/PhpDumper.php

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ class %s extends {$options['class']}
338338
EOF;
339339

340340
foreach ($this->preload as $class) {
341-
if (!$class || str_contains($class, '$') || \in_array($class, ['int', 'float', 'string', 'bool', 'resource', 'object', 'array', 'null', 'callable', 'iterable', 'mixed', 'void'], true)) {
341+
if (!$class || str_contains($class, '$') || \in_array($class, ['int', 'float', 'string', 'bool', 'resource', 'object', 'array', 'null', 'callable', 'iterable', 'mixed', 'void', 'never'], true)) {
342342
continue;
343343
}
344344
if (!(class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) || (new \ReflectionClass($class))->isUserDefined()) {
@@ -831,8 +831,7 @@ private function addService(string $id, Definition $definition): array
831831
if ($class = $definition->getClass()) {
832832
$class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class);
833833
$return[] = \sprintf(str_starts_with($class, '%') ? '@return object A %1$s instance' : '@return \%s', ltrim($class, '\\'));
834-
} elseif ($definition->getFactory()) {
835-
$factory = $definition->getFactory();
834+
} elseif ($factory = $definition->getFactory()) {
836835
if (\is_string($factory) && !str_starts_with($factory, '@=')) {
837836
$return[] = \sprintf('@return object An instance returned by %s()', $factory);
838837
} elseif (\is_array($factory) && (\is_string($factory[0]) || $factory[0] instanceof Definition || $factory[0] instanceof Reference)) {
@@ -1152,9 +1151,7 @@ private function addNewInstance(Definition $definition, string $return = '', ?st
11521151
$arguments[] = (\is_string($i) ? $i.': ' : '').$this->dumpValue($value);
11531152
}
11541153

1155-
if (null !== $definition->getFactory()) {
1156-
$callable = $definition->getFactory();
1157-
1154+
if ($callable = $definition->getFactory()) {
11581155
if ('current' === $callable && [0] === array_keys($definition->getArguments()) && \is_array($value) && [0] === array_keys($value)) {
11591156
return $return.$this->dumpValue($value[0]).$tail;
11601157
}
@@ -2197,6 +2194,12 @@ private function isSingleUsePrivateNode(ServiceReferenceGraphNode $node): bool
21972194
if ($edge->isLazy() || !$value instanceof Definition || !$value->isShared()) {
21982195
return false;
21992196
}
2197+
2198+
// When the source node is a proxy or ghost, it will construct its references only when the node itself is initialized.
2199+
// Since the node can be cloned before being fully initialized, we do not know how often its references are used.
2200+
if ($this->getProxyDumper()->isProxyCandidate($value)) {
2201+
return false;
2202+
}
22002203
$ids[$edge->getSourceNode()->getId()] = true;
22012204
}
22022205

@@ -2305,7 +2308,6 @@ private function getAutoloadFile(): ?string
23052308
private function getClasses(Definition $definition, string $id): array
23062309
{
23072310
$classes = [];
2308-
$resolve = $this->container->getParameterBag()->resolveValue(...);
23092311

23102312
while ($definition instanceof Definition) {
23112313
foreach ($definition->getTag($this->preloadTags[0]) as $tag) {
@@ -2317,24 +2319,24 @@ private function getClasses(Definition $definition, string $id): array
23172319
}
23182320

23192321
if ($class = $definition->getClass()) {
2320-
$classes[] = trim($resolve($class), '\\');
2322+
$classes[] = trim($class, '\\');
23212323
}
23222324
$factory = $definition->getFactory();
23232325

2326+
if (\is_string($factory) && !str_starts_with($factory, '@=') && str_contains($factory, '::')) {
2327+
$factory = explode('::', $factory);
2328+
}
2329+
23242330
if (!\is_array($factory)) {
2325-
$factory = [$factory];
2331+
$definition = $factory;
2332+
continue;
23262333
}
23272334

2328-
if (\is_string($factory[0])) {
2329-
$factory[0] = $resolve($factory[0]);
2335+
$definition = $factory[0] ?? null;
23302336

2331-
if (false !== $i = strrpos($factory[0], '::')) {
2332-
$factory[0] = substr($factory[0], 0, $i);
2333-
}
2337+
if (\is_string($definition)) {
23342338
$classes[] = trim($factory[0], '\\');
23352339
}
2336-
2337-
$definition = $factory[0];
23382340
}
23392341

23402342
return $classes;

Tests/Compiler/ValidateEnvPlaceholdersPassTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,36 @@ public function testDefaultEnvWithoutPrefixIsValidatedInConfig()
7373
$this->doProcess($container);
7474
}
7575

76+
public function testDefaultProcessorWithScalarNode()
77+
{
78+
$container = new ContainerBuilder();
79+
$container->setParameter('parameter_int', 12134);
80+
$container->setParameter('env(FLOATISH)', 4.2);
81+
$container->registerExtension($ext = new EnvExtension());
82+
$container->prependExtensionConfig('env_extension', $expected = [
83+
'scalar_node' => '%env(default:parameter_int:FLOATISH)%',
84+
]);
85+
86+
$this->doProcess($container);
87+
$this->assertSame($expected, $container->resolveEnvPlaceholders($ext->getConfig()));
88+
}
89+
90+
public function testDefaultProcessorAndAnotherProcessorWithScalarNode()
91+
{
92+
$this->expectException(InvalidTypeException::class);
93+
$this->expectExceptionMessageMatches('/^Invalid type for path "env_extension\.scalar_node"\. Expected one of "bool", "int", "float", "string", but got one of "int", "array"\.$/');
94+
95+
$container = new ContainerBuilder();
96+
$container->setParameter('parameter_int', 12134);
97+
$container->setParameter('env(JSON)', '{ "foo": "bar" }');
98+
$container->registerExtension($ext = new EnvExtension());
99+
$container->prependExtensionConfig('env_extension', [
100+
'scalar_node' => '%env(default:parameter_int:json:JSON)%',
101+
]);
102+
103+
$this->doProcess($container);
104+
}
105+
76106
public function testEnvsAreValidatedInConfigWithInvalidPlaceholder()
77107
{
78108
$this->expectException(InvalidTypeException::class);

Tests/Dumper/PhpDumperTest.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
use Symfony\Component\DependencyInjection\Tests\Compiler\SingleMethodInterface;
5757
use Symfony\Component\DependencyInjection\Tests\Compiler\Wither;
5858
use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition;
59+
use Symfony\Component\DependencyInjection\Tests\Fixtures\DependencyContainer;
60+
use Symfony\Component\DependencyInjection\Tests\Fixtures\DependencyContainerInterface;
5961
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithEnumAttribute;
6062
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum;
6163
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooWithAbstractArgument;
@@ -1668,6 +1670,59 @@ public function testWitherWithStaticReturnType()
16681670
$this->assertInstanceOf(Foo::class, $wither->foo);
16691671
}
16701672

1673+
public function testCloningLazyGhostWithDependency()
1674+
{
1675+
$container = new ContainerBuilder();
1676+
$container->register('dependency', \stdClass::class);
1677+
$container->register(DependencyContainer::class)
1678+
->addArgument(new Reference('dependency'))
1679+
->setLazy(true)
1680+
->setPublic(true);
1681+
1682+
$container->compile();
1683+
$dumper = new PhpDumper($container);
1684+
$dump = $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Service_CloningLazyGhostWithDependency']);
1685+
eval('?>'.$dump);
1686+
1687+
$container = new \Symfony_DI_PhpDumper_Service_CloningLazyGhostWithDependency();
1688+
1689+
$bar = $container->get(DependencyContainer::class);
1690+
$this->assertInstanceOf(DependencyContainer::class, $bar);
1691+
1692+
$first_clone = clone $bar;
1693+
$second_clone = clone $bar;
1694+
1695+
$this->assertSame($first_clone->dependency, $second_clone->dependency);
1696+
}
1697+
1698+
public function testCloningProxyWithDependency()
1699+
{
1700+
$container = new ContainerBuilder();
1701+
$container->register('dependency', \stdClass::class);
1702+
$container->register(DependencyContainer::class)
1703+
->addArgument(new Reference('dependency'))
1704+
->setLazy(true)
1705+
->addTag('proxy', [
1706+
'interface' => DependencyContainerInterface::class,
1707+
])
1708+
->setPublic(true);
1709+
1710+
$container->compile();
1711+
$dumper = new PhpDumper($container);
1712+
$dump = $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Service_CloningProxyWithDependency']);
1713+
eval('?>'.$dump);
1714+
1715+
$container = new \Symfony_DI_PhpDumper_Service_CloningProxyWithDependency();
1716+
1717+
$bar = $container->get(DependencyContainer::class);
1718+
$this->assertInstanceOf(DependencyContainerInterface::class, $bar);
1719+
1720+
$first_clone = clone $bar;
1721+
$second_clone = clone $bar;
1722+
1723+
$this->assertSame($first_clone->getDependency(), $second_clone->getDependency());
1724+
}
1725+
16711726
public function testCurrentFactoryInlining()
16721727
{
16731728
$container = new ContainerBuilder();
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
13+
14+
class DependencyContainer implements DependencyContainerInterface
15+
{
16+
public function __construct(
17+
public mixed $dependency,
18+
) {
19+
}
20+
21+
public function getDependency(): mixed
22+
{
23+
return $this->dependency;
24+
}
25+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
13+
14+
interface DependencyContainerInterface
15+
{
16+
public function getDependency(): mixed;
17+
}

Tests/Fixtures/config/child.expected.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ services:
1111
- container.decorator: { id: bar, inner: b }
1212
file: file.php
1313
lazy: true
14-
arguments: [!service { class: Class1 }]
14+
arguments: ['@b']
15+
b:
16+
class: Class1
1517
bar:
1618
alias: foo
1719
public: true

Tests/Fixtures/config/from_callable.expected.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@ services:
88
class: stdClass
99
public: true
1010
lazy: true
11-
arguments: [[!service { class: stdClass }, do]]
11+
arguments: [['@bar', do]]
1212
factory: [Closure, fromCallable]
13+
bar:
14+
class: stdClass

Tests/Fixtures/php/closure_proxy.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,6 @@ protected function createProxy($class, \Closure $factory)
5555
*/
5656
protected static function getClosureProxyService($container, $lazyLoad = true)
5757
{
58-
return $container->services['closure_proxy'] = new class(fn () => new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo()) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure implements \Symfony\Component\DependencyInjection\Tests\Compiler\SingleMethodInterface { public function theMethod() { return $this->service->cloneFoo(...\func_get_args()); } };
58+
return $container->services['closure_proxy'] = new class(fn () => ($container->privates['foo'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo())) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure implements \Symfony\Component\DependencyInjection\Tests\Compiler\SingleMethodInterface { public function theMethod() { return $this->service->cloneFoo(...\func_get_args()); } };
5959
}
6060
}

0 commit comments

Comments
 (0)