Skip to content

Commit 1faaac6

Browse files
Merge branch '7.4' into 8.0
* 7.4: [WebProfilerBundle] Cleanup whitespace [Validator] Regex bypass when match is false with too big input gracefully handle the kernel.runtime_mode.web parameter missing [JsonStreamer] Fix missing generator for shared types in self-referencing objects [Mailer] Rewrite "rebanded" to "re-branded" [DependencyInjection] Handle Stringable for string-typed arguments in CheckTypeDeclarationsPass [DependencyInjection] Fix TypeError when using a custom container base class with typed $parameterBag [Dotenv] Defer variable and command expansion to account for overrides from subsequent .env files Bump Symfony version to 7.4.7 Update VERSION for 7.4.6 Update CHANGELOG for 7.4.6 Bump Symfony version to 6.4.35 Update VERSION for 6.4.34 Update CONTRIBUTORS for 6.4.34 Update CHANGELOG for 6.4.34
2 parents edd9886 + 0f651e5 commit 1faaac6

File tree

7 files changed

+183
-14
lines changed

7 files changed

+183
-14
lines changed

Compiler/CheckTypeDeclarationsPass.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ private function checkType(Definition $checkedDefinition, mixed $value, \Reflect
275275
return;
276276
}
277277

278-
if ('string' === $type && $class instanceof \Stringable) {
278+
if ('string' === $type && is_a($class, \Stringable::class, true)) {
279279
return;
280280
}
281281

Dumper/PhpDumper.php

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,16 +1280,9 @@ public function __construct()
12801280
}
12811281
}
12821282

1283-
if (Container::class !== $this->baseClass) {
1284-
$r = $this->container->getReflectionClass($this->baseClass, false);
1285-
if (null !== $r
1286-
&& (null !== $constructor = $r->getConstructor())
1287-
&& 0 === $constructor->getNumberOfRequiredParameters()
1288-
&& Container::class !== $constructor->getDeclaringClass()->name
1289-
) {
1290-
$code .= " parent::__construct();\n";
1291-
$code .= " \$this->parameterBag = null;\n\n";
1292-
}
1283+
if ($this->needsUnsetParameterBag()) {
1284+
$code .= " parent::__construct();\n";
1285+
$code .= " unset(\$this->parameterBag);\n\n";
12931286
}
12941287

12951288
if ($this->container->getParameterBag()->all()) {
@@ -1585,11 +1578,24 @@ private function addInlineRequires(bool $hasProxyClasses): string
15851578
return $code ? \sprintf("\n \$this->privates['service_container'] = static function (\$container) {%s\n };\n", $code) : '';
15861579
}
15871580

1581+
private function needsUnsetParameterBag(): bool
1582+
{
1583+
if (Container::class === $this->baseClass) {
1584+
return false;
1585+
}
1586+
$r = $this->container->getReflectionClass($this->baseClass, false);
1587+
1588+
return null !== $r
1589+
&& (null !== $constructor = $r->getConstructor())
1590+
&& 0 === $constructor->getNumberOfRequiredParameters()
1591+
&& Container::class !== $constructor->getDeclaringClass()->name;
1592+
}
1593+
15881594
private function addDefaultParametersMethod(): string
15891595
{
15901596
$bag = $this->container->getParameterBag();
15911597

1592-
if (!$bag->all() && (!$bag instanceof ParameterBag || !$bag->allNonEmpty())) {
1598+
if (!$bag->all() && (!$bag instanceof ParameterBag || !$bag->allNonEmpty()) && !$this->needsUnsetParameterBag()) {
15931599
return '';
15941600
}
15951601

Tests/Compiler/CheckTypeDeclarationsPassTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,18 @@ public function testCheckTypeDeclarationsSkipsSubsequentNamedArguments()
10321032

10331033
(new CheckTypeDeclarationsPass(true))->process($container);
10341034
}
1035+
1036+
public function testStringableCanBePassedToStringTypeParameter()
1037+
{
1038+
$container = new ContainerBuilder();
1039+
$container->register('stringable', StringableClass::class);
1040+
$container->register('consumer', StringConsumer::class)
1041+
->setArguments([new Reference('stringable')]);
1042+
1043+
(new CheckTypeDeclarationsPass(true))->process($container);
1044+
1045+
$this->addToAssertionCount(1);
1046+
}
10351047
}
10361048

10371049
class CallableClass
@@ -1054,3 +1066,18 @@ public function __construct(int $a, int $b)
10541066
{
10551067
}
10561068
}
1069+
1070+
class StringableClass implements \Stringable
1071+
{
1072+
public function __toString(): string
1073+
{
1074+
return 'stringable';
1075+
}
1076+
}
1077+
1078+
class StringConsumer
1079+
{
1080+
public function __construct(string $value)
1081+
{
1082+
}
1083+
}

Tests/Dumper/PhpDumperTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
4747
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper;
4848
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
49+
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
4950
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
5051
use Symfony\Component\DependencyInjection\Reference;
5152
use Symfony\Component\DependencyInjection\ServiceLocator;
@@ -183,6 +184,26 @@ public function testDumpCustomContainerClassWithOptionalArgumentLessConstructor(
183184
$this->assertStringEqualsGeneratedFile('custom_container_class_with_optional_constructor_arguments.php', $dumper->dump(['base_class' => 'ConstructorWithOptionalArgumentsContainer', 'namespace' => 'Symfony\Component\DependencyInjection\Tests\Fixtures\Container']));
184185
}
185186

187+
public function testDumpCustomContainerClassWithTypedParameterBagCanBeInstantiated()
188+
{
189+
$container = new ContainerBuilder();
190+
$container->compile();
191+
192+
$dumper = new PhpDumper($container);
193+
$code = $dumper->dump(['base_class' => 'ContainerWithTypedParameterBag', 'class' => 'CompiledContainerWithTypedParameterBag', 'namespace' => 'Symfony\Component\DependencyInjection\Tests\Fixtures\Container']);
194+
195+
// The compiled container must not assign null to a potentially typed $parameterBag property
196+
$this->assertStringNotContainsString('$this->parameterBag = null', $code);
197+
$this->assertStringContainsString('unset($this->parameterBag)', $code);
198+
199+
// Verify the compiled container can actually be instantiated without TypeError
200+
eval('?>'.$code);
201+
$compiledContainer = new \Symfony\Component\DependencyInjection\Tests\Fixtures\Container\CompiledContainerWithTypedParameterBag();
202+
$this->assertTrue($compiledContainer->isCompiled());
203+
204+
$this->assertInstanceOf(FrozenParameterBag::class, $compiledContainer->getParameterBag());
205+
}
206+
186207
public function testDumpCustomContainerClassWithMandatoryArgumentLessConstructor()
187208
{
188209
$container = new ContainerBuilder();
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Container;
4+
5+
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
6+
7+
class ContainerWithTypedParameterBag extends \Symfony\Component\DependencyInjection\Container
8+
{
9+
public function __construct(?ParameterBagInterface $parameterBag = null)
10+
{
11+
parent::__construct($parameterBag);
12+
}
13+
}

Tests/Fixtures/php/custom_container_class_constructor_without_arguments.php

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class ProjectServiceContainer extends \Symfony\Component\DependencyInjection\Tes
2121
public function __construct()
2222
{
2323
parent::__construct();
24-
$this->parameterBag = null;
24+
unset($this->parameterBag);
2525

2626
$this->services = $this->privates = [];
2727

@@ -37,4 +37,55 @@ public function isCompiled(): bool
3737
{
3838
return true;
3939
}
40+
41+
public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null
42+
{
43+
if (isset($this->loadedDynamicParameters[$name])) {
44+
$value = $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
45+
} elseif (\array_key_exists($name, $this->parameters) && '.' !== ($name[0] ?? '')) {
46+
$value = $this->parameters[$name];
47+
} else {
48+
throw new ParameterNotFoundException($name);
49+
}
50+
51+
return $value;
52+
}
53+
54+
public function hasParameter(string $name): bool
55+
{
56+
return \array_key_exists($name, $this->parameters) || isset($this->loadedDynamicParameters[$name]);
57+
}
58+
59+
public function setParameter(string $name, $value): void
60+
{
61+
throw new LogicException('Impossible to call set() on a frozen ParameterBag.');
62+
}
63+
64+
public function getParameterBag(): ParameterBagInterface
65+
{
66+
if (!isset($this->parameterBag)) {
67+
$parameters = $this->parameters;
68+
foreach ($this->loadedDynamicParameters as $name => $loaded) {
69+
$parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
70+
}
71+
$this->parameterBag = new FrozenParameterBag($parameters, []);
72+
}
73+
74+
return $this->parameterBag;
75+
}
76+
77+
private $loadedDynamicParameters = [];
78+
private $dynamicParameters = [];
79+
80+
private function getDynamicParameter(string $name)
81+
{
82+
throw new ParameterNotFoundException($name);
83+
}
84+
85+
protected function getDefaultParameters(): array
86+
{
87+
return [
88+
89+
];
90+
}
4091
}

Tests/Fixtures/php/custom_container_class_with_optional_constructor_arguments.php

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class ProjectServiceContainer extends \Symfony\Component\DependencyInjection\Tes
2121
public function __construct()
2222
{
2323
parent::__construct();
24-
$this->parameterBag = null;
24+
unset($this->parameterBag);
2525

2626
$this->services = $this->privates = [];
2727

@@ -37,4 +37,55 @@ public function isCompiled(): bool
3737
{
3838
return true;
3939
}
40+
41+
public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null
42+
{
43+
if (isset($this->loadedDynamicParameters[$name])) {
44+
$value = $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
45+
} elseif (\array_key_exists($name, $this->parameters) && '.' !== ($name[0] ?? '')) {
46+
$value = $this->parameters[$name];
47+
} else {
48+
throw new ParameterNotFoundException($name);
49+
}
50+
51+
return $value;
52+
}
53+
54+
public function hasParameter(string $name): bool
55+
{
56+
return \array_key_exists($name, $this->parameters) || isset($this->loadedDynamicParameters[$name]);
57+
}
58+
59+
public function setParameter(string $name, $value): void
60+
{
61+
throw new LogicException('Impossible to call set() on a frozen ParameterBag.');
62+
}
63+
64+
public function getParameterBag(): ParameterBagInterface
65+
{
66+
if (!isset($this->parameterBag)) {
67+
$parameters = $this->parameters;
68+
foreach ($this->loadedDynamicParameters as $name => $loaded) {
69+
$parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
70+
}
71+
$this->parameterBag = new FrozenParameterBag($parameters, []);
72+
}
73+
74+
return $this->parameterBag;
75+
}
76+
77+
private $loadedDynamicParameters = [];
78+
private $dynamicParameters = [];
79+
80+
private function getDynamicParameter(string $name)
81+
{
82+
throw new ParameterNotFoundException($name);
83+
}
84+
85+
protected function getDefaultParameters(): array
86+
{
87+
return [
88+
89+
];
90+
}
4091
}

0 commit comments

Comments
 (0)