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 0f5a1c8

Browse filesBrowse files
[DI] Add support for "wither" methods - for greater immutable services
1 parent b9b8f9d commit 0f5a1c8
Copy full SHA for 0f5a1c8

16 files changed

+189
-12
lines changed

‎src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,9 @@ private function getContainerDefinitionDocument(Definition $definition, string $
343343
foreach ($calls as $callData) {
344344
$callsXML->appendChild($callXML = $dom->createElement('call'));
345345
$callXML->setAttribute('method', $callData[0]);
346+
if ($callData[2] ?? false) {
347+
$callXML->setAttribute('use-result', 'true');
348+
}
346349
}
347350
}
348351

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php
+11-1Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,21 @@ protected function processValue($value, $isRoot = false)
140140
$this->byConstructor = true;
141141
$this->processValue($value->getFactory());
142142
$this->processValue($value->getArguments());
143+
144+
// "wither" calls are part of the constructor-instantiation graph
145+
$setterCalls = [];
146+
foreach ($value->getMethodCalls() as $call) {
147+
if ($call[2] ?? false) {
148+
$this->processValue($call);
149+
} else {
150+
$setterCalls[] = $call;
151+
}
152+
}
143153
$this->byConstructor = $byConstructor;
144154

145155
if (!$this->onlyConstructorArguments) {
146156
$this->processValue($value->getProperties());
147-
$this->processValue($value->getMethodCalls());
157+
$this->processValue($setterCalls);
148158
$this->processValue($value->getConfigurator());
149159
}
150160
$this->lazy = $lazy;

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredMethodsPass.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ protected function processValue($value, $isRoot = false)
5050
while (true) {
5151
if (false !== $doc = $r->getDocComment()) {
5252
if (false !== stripos($doc, '@required') && preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) {
53-
$value->addMethodCall($reflectionMethod->name);
53+
$value->addMethodCall($reflectionMethod->name, [], preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@return\s++static[\s\*]#i', $doc));
5454
break;
5555
}
5656
if (false === stripos($doc, '@inheritdoc') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+(?:\{@inheritdoc\}|@inheritdoc)(?:\s|\*/$)#i', $doc)) {

‎src/Symfony/Component/DependencyInjection/ContainerBuilder.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/ContainerBuilder.php
+6-4Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,7 +1150,7 @@ private function createService(Definition $definition, array &$inlineServices, $
11501150
}
11511151

11521152
foreach ($definition->getMethodCalls() as $call) {
1153-
$this->callMethod($service, $call, $inlineServices);
1153+
$service = $this->callMethod($service, $call, $inlineServices);
11541154
}
11551155

11561156
if ($callable = $definition->getConfigurator()) {
@@ -1568,16 +1568,18 @@ private function callMethod($service, $call, array &$inlineServices)
15681568
{
15691569
foreach (self::getServiceConditionals($call[1]) as $s) {
15701570
if (!$this->has($s)) {
1571-
return;
1571+
return $service;
15721572
}
15731573
}
15741574
foreach (self::getInitializedConditionals($call[1]) as $s) {
15751575
if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE, $inlineServices)) {
1576-
return;
1576+
return $service;
15771577
}
15781578
}
15791579

1580-
$service->{$call[0]}(...$this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlineServices));
1580+
$result = $service->{$call[0]}(...$this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlineServices));
1581+
1582+
return empty($call[2]) ? $service : $result;
15811583
}
15821584

15831585
/**

‎src/Symfony/Component/DependencyInjection/Definition.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Definition.php
+4-3Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ public function setMethodCalls(array $calls = [])
330330
{
331331
$this->calls = [];
332332
foreach ($calls as $call) {
333-
$this->addMethodCall($call[0], $call[1]);
333+
$this->addMethodCall($call[0], $call[1], $call[2] ?? false);
334334
}
335335

336336
return $this;
@@ -341,17 +341,18 @@ public function setMethodCalls(array $calls = [])
341341
*
342342
* @param string $method The method name to call
343343
* @param array $arguments An array of arguments to pass to the method call
344+
* @param bool $useResult Whether the call returns the service instance or not
344345
*
345346
* @return $this
346347
*
347348
* @throws InvalidArgumentException on empty $method param
348349
*/
349-
public function addMethodCall($method, array $arguments = [])
350+
public function addMethodCall($method, array $arguments = []/*, bool $useResult = false*/)
350351
{
351352
if (empty($method)) {
352353
throw new InvalidArgumentException('Method name cannot be empty.');
353354
}
354-
$this->calls[] = [$method, $arguments];
355+
$this->calls[] = 2 < \func_num_args() && \func_get_arg(2) ? [$method, $arguments, true] : [$method, $arguments];
355356

356357
return $this;
357358
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
+12-1Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ private function addServiceMethodCalls(Definition $definition, string $variableN
572572
$arguments[] = $this->dumpValue($value);
573573
}
574574

575-
$calls .= $this->wrapServiceConditionals($call[1], sprintf(" \$%s->%s(%s);\n", $variableName, $call[0], implode(', ', $arguments)));
575+
$calls .= $this->wrapServiceConditionals($call[1], sprintf(" %s\$%s->%s(%s);\n", empty($call[2]) ? '' : "\${$variableName} = ", $variableName, $call[0], implode(', ', $arguments)));
576576
}
577577

578578
return $calls;
@@ -820,6 +820,17 @@ private function addInlineService(string $id, Definition $definition, Definition
820820

821821
if ($isRootInstance && !$isSimpleInstance) {
822822
$code .= "\n return \$instance;\n";
823+
824+
foreach ($definition->getMethodCalls() as $call) {
825+
if (!($call[2] ?? false)) {
826+
continue;
827+
}
828+
829+
if (!$this->getProxyDumper()->isProxyCandidate($definition) && $definition->isShared() && !isset($this->singleUsePrivateIds[$id])) {
830+
$code = substr_replace($code, sprintf('$this->%s[\'%s\'] = ', $definition->isPublic() ? 'services' : 'privates', $id), -11, 0);
831+
break;
832+
}
833+
}
823834
}
824835

825836
return $code;

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ private function addMethodCalls(array $methodcalls, \DOMElement $parent)
8484
if (\count($methodcall[1])) {
8585
$this->convertParameters($methodcall[1], 'argument', $call);
8686
}
87+
if ($methodcall[2] ?? false) {
88+
$call->setAttribute('use-result', 'true');
89+
}
8790
$parent->appendChild($call);
8891
}
8992
}

‎src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ private function parseDefinition(\DOMElement $service, $file, array $defaults)
337337
}
338338

339339
foreach ($this->getChildren($service, 'call') as $call) {
340-
$definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument', $file));
340+
$definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument', $file), $call->getAttribute('use-result'));
341341
}
342342

343343
$tags = $this->getChildren($service, 'tag');

‎src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ private function parseDefinition($id, $service, $file, array $defaults)
471471
if (!\is_array($args)) {
472472
throw new InvalidArgumentException(sprintf('The second parameter for function call "%s" must be an array of its arguments for service "%s" in %s. Check your YAML syntax.', $method, $id, $file));
473473
}
474-
$definition->addMethodCall($method, $args);
474+
$definition->addMethodCall($method, $args, $call['use_result'] ?? false);
475475
}
476476
}
477477

‎src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@
241241
<xsd:element name="argument" type="argument" maxOccurs="unbounded" />
242242
</xsd:choice>
243243
<xsd:attribute name="method" type="xsd:string" />
244+
<xsd:attribute name="use-result" type="boolean" />
244245
</xsd:complexType>
245246

246247
<xsd:simpleType name="parameter_type">

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredMethodsPassTest.php
+17Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,21 @@ public function testExplicitMethodInjection()
7777
);
7878
$this->assertEquals([], $methodCalls[0][1]);
7979
}
80+
81+
public function testWitherInjection()
82+
{
83+
$container = new ContainerBuilder();
84+
$container->register(Foo::class);
85+
86+
$container
87+
->register('wither', Wither::class)
88+
->setAutowired(true);
89+
90+
(new ResolveClassPass())->process($container);
91+
(new AutowireRequiredMethodsPass())->process($container);
92+
93+
$methodCalls = $container->getDefinition('wither')->getMethodCalls();
94+
95+
$this->assertSame([['withFoo', [], true]], $methodCalls);
96+
}
8097
}

‎src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Tests;
1313

14+
require_once __DIR__.'/Fixtures/includes/autowiring_classes.php';
1415
require_once __DIR__.'/Fixtures/includes/classes.php';
1516
require_once __DIR__.'/Fixtures/includes/ProjectExtension.php';
1617

@@ -36,6 +37,8 @@
3637
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
3738
use Symfony\Component\DependencyInjection\Reference;
3839
use Symfony\Component\DependencyInjection\ServiceLocator;
40+
use Symfony\Component\DependencyInjection\Tests\Compiler\Foo;
41+
use Symfony\Component\DependencyInjection\Tests\Compiler\Wither;
3942
use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
4043
use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition;
4144
use Symfony\Component\DependencyInjection\Tests\Fixtures\SimilarArgumentsDummy;
@@ -1565,6 +1568,22 @@ public function testDecoratedSelfReferenceInvolvingPrivateServices()
15651568

15661569
$this->assertSame(['service_container'], array_keys($container->getDefinitions()));
15671570
}
1571+
1572+
public function testWither()
1573+
{
1574+
$container = new ContainerBuilder();
1575+
$container->register(Foo::class);
1576+
1577+
$container
1578+
->register('wither', Wither::class)
1579+
->setPublic(true)
1580+
->setAutowired(true);
1581+
1582+
$container->compile();
1583+
1584+
$wither = $container->get('wither');
1585+
$this->assertInstanceOf(Foo::class, $wither->foo);
1586+
}
15681587
}
15691588

15701589
class FooClass

‎src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,16 @@ public function testMethodCalls()
9595
$this->assertEquals([['foo', ['foo']]], $def->getMethodCalls(), '->getMethodCalls() returns the methods to call');
9696
$this->assertSame($def, $def->addMethodCall('bar', ['bar']), '->addMethodCall() implements a fluent interface');
9797
$this->assertEquals([['foo', ['foo']], ['bar', ['bar']]], $def->getMethodCalls(), '->addMethodCall() adds a method to call');
98+
$this->assertSame($def, $def->addMethodCall('foobar', ['foobar'], true), '->addMethodCall() implements a fluent interface with third parameter');
99+
$this->assertEquals([['foo', ['foo']], ['bar', ['bar']], ['foobar', ['foobar'], true]], $def->getMethodCalls(), '->addMethodCall() adds a method to call');
98100
$this->assertTrue($def->hasMethodCall('bar'), '->hasMethodCall() returns true if first argument is a method to call registered');
99101
$this->assertFalse($def->hasMethodCall('no_registered'), '->hasMethodCall() returns false if first argument is not a method to call registered');
100102
$this->assertSame($def, $def->removeMethodCall('bar'), '->removeMethodCall() implements a fluent interface');
103+
$this->assertTrue($def->hasMethodCall('foobar'), '->hasMethodCall() returns true if first argument is a method to call registered');
104+
$this->assertSame($def, $def->removeMethodCall('foobar'), '->removeMethodCall() implements a fluent interface');
101105
$this->assertEquals([['foo', ['foo']]], $def->getMethodCalls(), '->removeMethodCall() removes a method to call');
106+
$this->assertSame($def, $def->setMethodCalls([['foobar', ['foobar'], true]]), '->setMethodCalls() implements a fluent interface with third parameter');
107+
$this->assertEquals([['foobar', ['foobar'], true]], $def->getMethodCalls(), '->addMethodCall() adds a method to call');
102108
}
103109

104110
/**

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
+25Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,16 @@
3030
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
3131
use Symfony\Component\DependencyInjection\Reference;
3232
use Symfony\Component\DependencyInjection\ServiceLocator;
33+
use Symfony\Component\DependencyInjection\Tests\Compiler\Foo;
34+
use Symfony\Component\DependencyInjection\Tests\Compiler\Wither;
3335
use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition;
3436
use Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator;
3537
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber;
3638
use Symfony\Component\DependencyInjection\TypedReference;
3739
use Symfony\Component\DependencyInjection\Variable;
3840
use Symfony\Component\ExpressionLanguage\Expression;
3941

42+
require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php';
4043
require_once __DIR__.'/../Fixtures/includes/classes.php';
4144

4245
class PhpDumperTest extends TestCase
@@ -1170,6 +1173,28 @@ public function testServiceLocatorArgument()
11701173
$container->set('foo5', $foo5 = new \stdClass());
11711174
$this->assertSame($foo5, $locator->get('foo5'));
11721175
}
1176+
1177+
public function testWither()
1178+
{
1179+
$container = new ContainerBuilder();
1180+
$container->register(Foo::class);
1181+
1182+
$container
1183+
->register('wither', Wither::class)
1184+
->setPublic(true)
1185+
->setAutowired(true);
1186+
1187+
$container->compile();
1188+
$dumper = new PhpDumper($container);
1189+
$dump = $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Service_Wither']);
1190+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_wither.php', $dump);
1191+
eval('?>'.$dump);
1192+
1193+
$container = new \Symfony_DI_PhpDumper_Service_Wither();
1194+
1195+
$wither = $container->get('wither');
1196+
$this->assertInstanceOf(Foo::class, $wither->foo);
1197+
}
11731198
}
11741199

11751200
class Rot13EnvVarProcessor implements EnvVarProcessorInterface

‎src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php
+15Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,21 @@ public function setChildMethodWithoutDocBlock(A $a)
278278
}
279279
}
280280

281+
class Wither
282+
{
283+
/**
284+
* @required
285+
* @return static
286+
*/
287+
public function withFoo(Foo $foo)
288+
{
289+
$new = clone $this;
290+
$new->foo = $foo;
291+
292+
return $new;
293+
}
294+
}
295+
281296
class SetterInjectionParent
282297
{
283298
/** @required*/
+64Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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+
11+
/**
12+
* This class has been auto-generated
13+
* by the Symfony Dependency Injection Component.
14+
*
15+
* @final since Symfony 3.3
16+
*/
17+
class Symfony_DI_PhpDumper_Service_Wither extends Container
18+
{
19+
private $parameters;
20+
private $targetDirs = [];
21+
22+
public function __construct()
23+
{
24+
$this->services = $this->privates = [];
25+
$this->methodMap = [
26+
'wither' => 'getWitherService',
27+
];
28+
29+
$this->aliases = [];
30+
}
31+
32+
public function compile()
33+
{
34+
throw new LogicException('You cannot compile a dumped container that was already compiled.');
35+
}
36+
37+
public function isCompiled()
38+
{
39+
return true;
40+
}
41+
42+
public function getRemovedIds()
43+
{
44+
return [
45+
'Psr\\Container\\ContainerInterface' => true,
46+
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
47+
'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo' => true,
48+
];
49+
}
50+
51+
/**
52+
* Gets the public 'wither' shared autowired service.
53+
*
54+
* @return \Symfony\Component\DependencyInjection\Tests\Compiler\Wither
55+
*/
56+
protected function getWitherService()
57+
{
58+
$this->services['wither'] = $instance = new \Symfony\Component\DependencyInjection\Tests\Compiler\Wither();
59+
60+
$instance = $instance->withFoo(new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo());
61+
62+
return $this->services['wither'] = $instance;
63+
}
64+
}

0 commit comments

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