Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 33bd906

Browse files
[DI] Add support for "wither" methods - for greater immutable services
1 parent b9b8f9d commit 33bd906

16 files changed

+190
-12
lines changed

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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines changed: 26 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,29 @@ 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+
}
1198+
11731199
}
11741200

11751201
class Rot13EnvVarProcessor implements EnvVarProcessorInterface

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

Lines 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*/
Lines 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)