Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 36080f3

Browse filesBrowse files
[DependencyInjection] fix support for "new" in initializers on PHP 8.1
1 parent e6e641c commit 36080f3
Copy full SHA for 36080f3

File tree

8 files changed

+175
-17
lines changed
Filter options

8 files changed

+175
-17
lines changed

‎src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
+46-8Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class AutowirePass extends AbstractRecursivePass
3434
private $decoratedClass;
3535
private $decoratedId;
3636
private $methodCalls;
37+
private $defaultArgument;
3738
private $getPreviousValue;
3839
private $decoratedMethodIndex;
3940
private $decoratedMethodArgumentIndex;
@@ -42,6 +43,10 @@ class AutowirePass extends AbstractRecursivePass
4243
public function __construct(bool $throwOnAutowireException = true)
4344
{
4445
$this->throwOnAutowiringException = $throwOnAutowireException;
46+
$this->defaultArgument = new class() {
47+
public $value;
48+
public $names;
49+
};
4550
}
4651

4752
/**
@@ -56,6 +61,7 @@ public function process(ContainerBuilder $container)
5661
$this->decoratedClass = null;
5762
$this->decoratedId = null;
5863
$this->methodCalls = null;
64+
$this->defaultArgument->names = null;
5965
$this->getPreviousValue = null;
6066
$this->decoratedMethodIndex = null;
6167
$this->decoratedMethodArgumentIndex = null;
@@ -150,8 +156,9 @@ private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot):
150156
$this->decoratedClass = $this->container->findDefinition($this->decoratedId)->getClass();
151157
}
152158

159+
$patchedIndexes = [];
160+
153161
foreach ($this->methodCalls as $i => $call) {
154-
$this->decoratedMethodIndex = $i;
155162
[$method, $arguments] = $call;
156163

157164
if ($method instanceof \ReflectionFunctionAbstract) {
@@ -168,11 +175,36 @@ private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot):
168175
}
169176
}
170177

171-
$arguments = $this->autowireMethod($reflectionMethod, $arguments);
178+
$arguments = $this->autowireMethod($reflectionMethod, $arguments, $i);
172179

173180
if ($arguments !== $call[1]) {
174181
$this->methodCalls[$i][1] = $arguments;
182+
$patchedIndexes[] = $i;
183+
}
184+
}
185+
186+
foreach ($patchedIndexes as $i) {
187+
$namedArguments = null;
188+
$arguments = $this->methodCalls[$i][1];
189+
190+
foreach ($arguments as $j => $value) {
191+
if ($namedArguments && !$value instanceof $this->defaultArgument) {
192+
unset($arguments[$j]);
193+
$arguments[$namedArguments[$j]] = $value;
194+
}
195+
if ($namedArguments || !$value instanceof $this->defaultArgument) {
196+
continue;
197+
}
198+
199+
if (\PHP_VERSION_ID >= 80100 && (\is_array($value->value) ? $value->value : \is_object($value->value))) {
200+
unset($arguments[$j]);
201+
$namedArguments = $value->names;
202+
} else {
203+
$arguments[$j] = $value->value;
204+
}
175205
}
206+
207+
$this->methodCalls[$i][1] = $arguments;
176208
}
177209

178210
return $this->methodCalls;
@@ -185,16 +217,19 @@ private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot):
185217
*
186218
* @throws AutowiringFailedException
187219
*/
188-
private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments): array
220+
private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments, int $methodIndex): array
189221
{
190222
$class = $reflectionMethod instanceof \ReflectionMethod ? $reflectionMethod->class : $this->currentId;
191223
$method = $reflectionMethod->name;
192224
$parameters = $reflectionMethod->getParameters();
193225
if ($reflectionMethod->isVariadic()) {
194226
array_pop($parameters);
195227
}
228+
$this->defaultArgument->names = new \ArrayObject();
196229

197230
foreach ($parameters as $index => $parameter) {
231+
$this->defaultArgument->names[$index] = $parameter->name;
232+
198233
if (\array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
199234
continue;
200235
}
@@ -212,7 +247,8 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
212247
// be false when isOptional() returns true. If the
213248
// argument *is* optional, allow it to be missing
214249
if ($parameter->isOptional()) {
215-
continue;
250+
--$index;
251+
break;
216252
}
217253
$type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, false);
218254
$type = $type ? sprintf('is type-hinted "%s"', ltrim($type, '\\')) : 'has no type-hint';
@@ -221,7 +257,8 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
221257
}
222258

223259
// specifically pass the default value
224-
$arguments[$index] = $parameter->getDefaultValue();
260+
$arguments[$index] = clone $this->defaultArgument;
261+
$arguments[$index]->value = $parameter->getDefaultValue();
225262

226263
continue;
227264
}
@@ -231,7 +268,8 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
231268
$failureMessage = $this->createTypeNotFoundMessageCallback($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method));
232269

233270
if ($parameter->isDefaultValueAvailable()) {
234-
$value = $parameter->getDefaultValue();
271+
$value = clone $this->defaultArgument;
272+
$value->value = $parameter->getDefaultValue();
235273
} elseif (!$parameter->allowsNull()) {
236274
throw new AutowiringFailedException($this->currentId, $failureMessage);
237275
}
@@ -252,6 +290,7 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
252290
} else {
253291
$arguments[$index] = new TypedReference($this->decoratedId, $this->decoratedClass);
254292
$this->getPreviousValue = $getValue;
293+
$this->decoratedMethodIndex = $methodIndex;
255294
$this->decoratedMethodArgumentIndex = $index;
256295

257296
continue;
@@ -263,8 +302,7 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
263302

264303
if ($parameters && !isset($arguments[++$index])) {
265304
while (0 <= --$index) {
266-
$parameter = $parameters[$index];
267-
if (!$parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== $arguments[$index]) {
305+
if (!$arguments[$index] instanceof $this->defaultArgument) {
268306
break;
269307
}
270308
unset($arguments[$index]);

‎src/Symfony/Component/DependencyInjection/Compiler/CheckArgumentsValidityPass.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/CheckArgumentsValidityPass.php
+32Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,13 @@ protected function processValue($value, $isRoot = false)
3939
}
4040

4141
$i = 0;
42+
$hasNamedArgs = false;
4243
foreach ($value->getArguments() as $k => $v) {
44+
if (\PHP_VERSION_ID >= 80000 && preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $k)) {
45+
$hasNamedArgs = true;
46+
continue;
47+
}
48+
4349
if ($k !== $i++) {
4450
if (!\is_int($k)) {
4551
$msg = sprintf('Invalid constructor argument for service "%s": integer expected but found string "%s". Check your service definition.', $this->currentId, $k);
@@ -57,11 +63,27 @@ protected function processValue($value, $isRoot = false)
5763
throw new RuntimeException($msg);
5864
}
5965
}
66+
67+
if ($hasNamedArgs) {
68+
$msg = sprintf('Invalid constructor argument for service "%s": cannot use positional argument after named argument. Check your service definition.', $this->currentId);
69+
$value->addError($msg);
70+
if ($this->throwExceptions) {
71+
throw new RuntimeException($msg);
72+
}
73+
74+
break;
75+
}
6076
}
6177

6278
foreach ($value->getMethodCalls() as $methodCall) {
6379
$i = 0;
80+
$hasNamedArgs = false;
6481
foreach ($methodCall[1] as $k => $v) {
82+
if (\PHP_VERSION_ID >= 80000 && preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $k)) {
83+
$hasNamedArgs = true;
84+
continue;
85+
}
86+
6587
if ($k !== $i++) {
6688
if (!\is_int($k)) {
6789
$msg = sprintf('Invalid argument for method call "%s" of service "%s": integer expected but found string "%s". Check your service definition.', $methodCall[0], $this->currentId, $k);
@@ -79,6 +101,16 @@ protected function processValue($value, $isRoot = false)
79101
throw new RuntimeException($msg);
80102
}
81103
}
104+
105+
if ($hasNamedArgs) {
106+
$msg = sprintf('Invalid argument for method call "%s" of service "%s": cannot use positional argument after named argument. Check your service definition.', $methodCall[0], $this->currentId);
107+
$value->addError($msg);
108+
if ($this->throwExceptions) {
109+
throw new RuntimeException($msg);
110+
}
111+
112+
break;
113+
}
82114
}
83115
}
84116

‎src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public function process(ContainerBuilder $container)
124124
protected function processValue($value, $isRoot = false)
125125
{
126126
if ($value instanceof ArgumentInterface) {
127-
// Reference found in ArgumentInterface::getValues() are not inlineable
127+
// References found in ArgumentInterface::getValues() are not inlineable
128128
return $value;
129129
}
130130

‎src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
+4-4Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -714,8 +714,8 @@ private function addServiceMethodCalls(Definition $definition, string $variableN
714714
$calls = '';
715715
foreach ($definition->getMethodCalls() as $k => $call) {
716716
$arguments = [];
717-
foreach ($call[1] as $value) {
718-
$arguments[] = $this->dumpValue($value);
717+
foreach ($call[1] as $i => $value) {
718+
$arguments[] = (\is_string($i) ? $i.': ' : '').$this->dumpValue($value);
719719
}
720720

721721
$witherAssignation = '';
@@ -1080,8 +1080,8 @@ private function addNewInstance(Definition $definition, string $return = '', str
10801080
}
10811081

10821082
$arguments = [];
1083-
foreach ($definition->getArguments() as $value) {
1084-
$arguments[] = $this->dumpValue($value);
1083+
foreach ($definition->getArguments() as $i => $value) {
1084+
$arguments[] = (\is_string($i) ? $i.': ' : '').$this->dumpValue($value);
10851085
}
10861086

10871087
if (null !== $definition->getFactory()) {

‎src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckArgumentsValidityPassTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckArgumentsValidityPassTest.php
+4-4Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,22 @@ public function testProcess()
4646
*/
4747
public function testException(array $arguments, array $methodCalls)
4848
{
49-
$this->expectException(RuntimeException::class);
5049
$container = new ContainerBuilder();
5150
$definition = $container->register('foo');
5251
$definition->setArguments($arguments);
5352
$definition->setMethodCalls($methodCalls);
5453

5554
$pass = new CheckArgumentsValidityPass();
55+
$this->expectException(RuntimeException::class);
5656
$pass->process($container);
5757
}
5858

5959
public function definitionProvider()
6060
{
6161
return [
62-
[[null, 'a' => 'a'], []],
62+
[['a' => 'a', null], []],
6363
[[1 => 1], []],
64-
[[], [['baz', [null, 'a' => 'a']]]],
64+
[[], [['baz', ['a' => 'a', null]]]],
6565
[[], [['baz', [1 => 1]]]],
6666
];
6767
}
@@ -70,7 +70,7 @@ public function testNoException()
7070
{
7171
$container = new ContainerBuilder();
7272
$definition = $container->register('foo');
73-
$definition->setArguments([null, 'a' => 'a']);
73+
$definition->setArguments(['a' => 'a', null]);
7474

7575
$pass = new CheckArgumentsValidityPass(false);
7676
$pass->process($container);

‎src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum;
4545
use Symfony\Component\DependencyInjection\Tests\Fixtures\ScalarFactory;
4646
use Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator;
47+
use Symfony\Component\DependencyInjection\Tests\Fixtures\NewInInitializer;
4748
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition1;
4849
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber;
4950
use Symfony\Component\DependencyInjection\TypedReference;
@@ -1187,6 +1188,24 @@ public function testDumpHandlesObjectClassNames()
11871188
$this->assertInstanceOf(\stdClass::class, $container->get('bar'));
11881189
}
11891190

1191+
/**
1192+
* @requires PHP 8.1
1193+
*/
1194+
public function testNewInInitializer()
1195+
{
1196+
$container = new ContainerBuilder();
1197+
$container
1198+
->register('foo', NewInInitializer::class)
1199+
->setPublic(true)
1200+
->setAutowired(true)
1201+
->setArguments(['$bar' => 234]);
1202+
1203+
$container->compile();
1204+
1205+
$dumper = new PhpDumper($container);
1206+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_new_in_initializer.php', $dumper->dump());
1207+
}
1208+
11901209
/**
11911210
* @requires PHP 8.1
11921211
*/
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
4+
5+
class NewInInitializer
6+
{
7+
public function __construct($foo = new \stdClass(), $bar = 123)
8+
{
9+
}
10+
}
+59Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
4+
use Symfony\Component\DependencyInjection\ContainerInterface;
5+
use Symfony\Component\DependencyInjection\Container;
6+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
7+
use Symfony\Component\DependencyInjection\Exception\LogicException;
8+
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
9+
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
10+
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
11+
12+
/**
13+
* This class has been auto-generated
14+
* by the Symfony Dependency Injection Component.
15+
*
16+
* @final
17+
*/
18+
class ProjectServiceContainer extends Container
19+
{
20+
private $parameters = [];
21+
22+
public function __construct()
23+
{
24+
$this->services = $this->privates = [];
25+
$this->methodMap = [
26+
'foo' => 'getFooService',
27+
];
28+
29+
$this->aliases = [];
30+
}
31+
32+
public function compile(): void
33+
{
34+
throw new LogicException('You cannot compile a dumped container that was already compiled.');
35+
}
36+
37+
public function isCompiled(): bool
38+
{
39+
return true;
40+
}
41+
42+
public function getRemovedIds(): array
43+
{
44+
return [
45+
'Psr\\Container\\ContainerInterface' => true,
46+
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
47+
];
48+
}
49+
50+
/**
51+
* Gets the public 'foo' shared autowired service.
52+
*
53+
* @return \Symfony\Component\DependencyInjection\Tests\Fixtures\NewInInitializer
54+
*/
55+
protected function getFooService()
56+
{
57+
return $this->services['foo'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\NewInInitializer(bar: 234);
58+
}
59+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.