Skip to content

Commit 962229e

Browse files
[DI] Add autowiring for partial methods
1 parent ac1377c commit 962229e

File tree

11 files changed

+167
-8
lines changed

11 files changed

+167
-8
lines changed

src/Symfony/Component/DependencyInjection/ChildDefinition.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,16 @@ public function setAutowiredCalls(array $autowiredCalls)
169169
return parent::setAutowiredCalls($autowiredCalls);
170170
}
171171

172+
/**
173+
* {@inheritdoc}
174+
*/
175+
public function setAutowiredPartials(array $autowiredPartials)
176+
{
177+
$this->changes['autowired_partials'] = true;
178+
179+
return parent::setAutowiredPartials($autowiredPartials);
180+
}
181+
172182
/**
173183
* Gets an argument to pass to the service constructor/factory method.
174184
*

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

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class AutowirePass extends AbstractRecursivePass
3333
* @internal
3434
*/
3535
const MODE_OPTIONAL = 2;
36+
const MODE_PARTIAL = 3;
3637

3738
private $definedTypes = array();
3839
private $types;
@@ -82,7 +83,7 @@ public static function createResourceForClass(\ReflectionClass $reflectionClass)
8283
*/
8384
protected function processValue($value, $isRoot = false)
8485
{
85-
if (!$value instanceof Definition || !$value->getAutowiredCalls()) {
86+
if (!$value instanceof Definition || !($value->getAutowiredCalls() || $value->getAutowiredPartials())) {
8687
return parent::processValue($value, $isRoot);
8788
}
8889

@@ -118,6 +119,13 @@ protected function processValue($value, $isRoot = false)
118119
$value->setOverriddenGetters($overriddenGetters);
119120
}
120121

122+
$autowiredMethods = $this->getMethodsToAutowire($reflectionClass, $value->getAutowiredPartials());
123+
$partialMethods = $this->autowirePartials($reflectionClass, $value->getPartialMethods(), $autowiredMethods);
124+
125+
if ($partialMethods !== $value->getPartialMethods()) {
126+
$value->setPartialMethods($partialMethods);
127+
}
128+
121129
return parent::processValue($value, $isRoot);
122130
}
123131

@@ -223,7 +231,12 @@ private function autowireCalls(\ReflectionClass $reflectionClass, array $methodC
223231
private function autowireMethod(\ReflectionMethod $reflectionMethod, array $arguments, $mode)
224232
{
225233
$didAutowire = false; // Whether any arguments have been autowired or not
226-
foreach ($reflectionMethod->getParameters() as $index => $parameter) {
234+
235+
$parameters = $reflectionMethod->getParameters();
236+
if (self::MODE_PARTIAL === $mode) {
237+
$parameters = array_reverse($parameters, true);
238+
}
239+
foreach ($parameters as $index => $parameter) {
227240
if (array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
228241
continue;
229242
}
@@ -245,6 +258,9 @@ private function autowireMethod(\ReflectionMethod $reflectionMethod, array $argu
245258
if (self::MODE_REQUIRED === $mode) {
246259
throw new RuntimeException(sprintf('Cannot autowire service "%s": argument $%s of method %s::%s() must have a type-hint or be given a value explicitly.', $this->currentId, $parameter->name, $reflectionMethod->class, $reflectionMethod->name));
247260
}
261+
if (self::MODE_PARTIAL === $mode) {
262+
break;
263+
}
248264

249265
return array();
250266
}
@@ -285,6 +301,9 @@ private function autowireMethod(\ReflectionMethod $reflectionMethod, array $argu
285301
if (1 === $e->getCode() || self::MODE_REQUIRED === $mode) {
286302
throw $e;
287303
}
304+
if (self::MODE_PARTIAL === $mode) {
305+
break;
306+
}
288307

289308
return array();
290309
}
@@ -296,6 +315,9 @@ private function autowireMethod(\ReflectionMethod $reflectionMethod, array $argu
296315
if (self::MODE_REQUIRED === $mode) {
297316
throw new RuntimeException(sprintf('Cannot autowire argument $%s of method %s::%s() for service "%s": Class %s does not exist.', $parameter->name, $reflectionMethod->class, $reflectionMethod->name, $this->currentId, $typeName));
298317
}
318+
if (self::MODE_PARTIAL === $mode) {
319+
break;
320+
}
299321

300322
return array();
301323
}
@@ -366,6 +388,38 @@ private function autowireOverridenGetters(array $overridenGetters, array $autowi
366388
return $overridenGetters;
367389
}
368390

391+
/**
392+
* Autowires partial methods.
393+
*
394+
* @return array
395+
*/
396+
private function autowirePartials(\ReflectionClass $reflectionClass, array $partialMethods, array $autowiredMethods)
397+
{
398+
foreach ($partialMethods as $lcMethod => $defaultValues) {
399+
if (isset($autowiredMethods[$lcMethod]) || !$reflectionClass->hasMethod($lcMethod)) {
400+
continue;
401+
}
402+
$autowiredMethods[$lcMethod] = $reflectionClass->getMethod($lcMethod);
403+
}
404+
405+
foreach ($autowiredMethods as $lcMethod => $reflectionMethod) {
406+
if ($reflectionMethod->isConstructor()
407+
|| !$reflectionMethod->getNumberOfParameters()
408+
|| $reflectionMethod->isFinal()
409+
) {
410+
continue;
411+
}
412+
413+
$defaultValues = isset($partialMethods[$lcMethod]) ? $partialMethods[$lcMethod] : array();
414+
415+
if ($defaultValues = $this->autowireMethod($reflectionMethod, $defaultValues, self::MODE_PARTIAL)) {
416+
$partialMethods[$lcMethod] = $defaultValues;
417+
}
418+
}
419+
420+
return $partialMethods;
421+
}
422+
369423
/**
370424
* Populates the list of available types.
371425
*/

src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ private function doResolveDefinition(ChildDefinition $definition)
104104
$def->setPublic($parentDef->isPublic());
105105
$def->setLazy($parentDef->isLazy());
106106
$def->setAutowiredCalls($parentDef->getAutowiredCalls());
107+
$def->setAutowiredPartials($parentDef->getAutowiredPartials());
107108

108109
// overwrite with values specified in the decorator
109110
$changes = $definition->getChanges();
@@ -131,6 +132,9 @@ private function doResolveDefinition(ChildDefinition $definition)
131132
if (isset($changes['autowired_calls'])) {
132133
$def->setAutowiredCalls($definition->getAutowiredCalls());
133134
}
135+
if (isset($changes['autowired_partials'])) {
136+
$def->setAutowiredPartials($definition->getAutowiredPartials());
137+
}
134138
if (isset($changes['decorated_service'])) {
135139
$decoratedService = $definition->getDecoratedService();
136140
if (null === $decoratedService) {

src/Symfony/Component/DependencyInjection/Definition.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class Definition
3939
private $lazy = false;
4040
private $decoratedService;
4141
private $autowiredCalls = array();
42+
private $autowiredPartials = array();
4243
private $autowiringTypes = array();
4344

4445
protected $arguments;
@@ -742,7 +743,7 @@ public function setAutowiringTypes(array $types)
742743
*/
743744
public function isAutowired()
744745
{
745-
return !empty($this->autowiredCalls);
746+
return $this->autowiredCalls || $this->autowiredPartials;
746747
}
747748

748749
/**
@@ -790,6 +791,26 @@ public function setAutowiredCalls(array $autowiredCalls)
790791
return $this;
791792
}
792793

794+
/**
795+
* @experimental in 3.3
796+
*/
797+
public function getAutowiredPartials()
798+
{
799+
return $this->autowiredPartials;
800+
}
801+
802+
/**
803+
* @experimental in 3.3
804+
*
805+
* @return $this
806+
*/
807+
public function setAutowiredPartials(array $autowiredPartials)
808+
{
809+
$this->autowiredPartials = $autowiredPartials;
810+
811+
return $this;
812+
}
813+
793814
/**
794815
* Gets autowiring types that will default to this definition.
795816
*

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -329,15 +329,25 @@ private function parseDefinition(\DOMElement $service, $file, array $defaults =
329329
$definition->addAutowiringType($type->textContent);
330330
}
331331

332-
$autowiredCalls = array();
332+
$autowiredCalls = $autowiredPartials = array();
333333
foreach ($this->getChildren($service, 'autowire') as $tag) {
334-
$autowiredCalls[] = $tag->textContent;
334+
if ('partials' === $tag->getAttribute('type')) {
335+
$autowiredPartials[] = $tag->textContent;
336+
} elseif (!$tag->getAttribute('type') || 'calls' === $tag->getAttribute('type')) {
337+
$autowiredCalls[] = $tag->textContent;
338+
} else {
339+
throw new InvalidArgumentException(sprintf('The "type" attribute of "<autowire>" tags must be set to "partials" or "calls", found "%s" for service "%s" in %s.', $tag->getAttribute('type'), $service->getAttribute('id'), $file));
340+
}
335341
}
336342

337-
if ($autowiredCalls && $service->hasAttribute('autowire')) {
343+
if (($autowiredCalls || $autowiredPartials) && $service->hasAttribute('autowire')) {
338344
throw new InvalidArgumentException(sprintf('The "autowire" attribute cannot be used together with "<autowire>" tags for service "%s" in %s.', $service->getAttribute('id'), $file));
339345
}
340346

347+
if ($autowiredPartials) {
348+
$definition->setAutowiredPartials($autowiredPartials);
349+
}
350+
341351
if ($autowiredCalls) {
342352
$definition->setAutowiredCalls($autowiredCalls);
343353
} elseif (!$service->hasAttribute('autowire') && !empty($defaults['autowire'])) {

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,16 +413,25 @@ private function parseDefinition($id, $service, $file, array $defaults)
413413

414414
$autowire = isset($service['autowire']) ? $service['autowire'] : (isset($defaults['autowire']) ? $defaults['autowire'] : null);
415415
if (is_array($autowire)) {
416-
$autowiredCalls = array();
416+
$autowiredCalls = $autowiredPartials = array();
417417

418418
foreach ($autowire as $v) {
419419
if (is_string($v)) {
420420
$autowiredCalls[] = $v;
421+
} elseif ($v instanceof TaggedValue && is_string($v->getValue())) {
422+
if ('partials' !== $v->getTag()) {
423+
throw new InvalidArgumentException(sprintf('Unknown tag "!%s" for parameter "autowire" of service "%s" in %s. Check your YAML syntax.', $v->getTag(), $id, $file));
424+
}
425+
$autowiredPartials[] = $v->getValue();
421426
} else {
422427
throw new InvalidArgumentException(sprintf('Parameter "autowire" must be boolean or string[] for service "%s" in %s. Check your YAML syntax.', $id, $file));
423428
}
424429
}
425430

431+
if ($autowiredPartials) {
432+
$definition->setAutowiredPartials($autowiredPartials);
433+
}
434+
426435
if ($autowiredCalls) {
427436
$definition->setAutowiredCalls($autowiredCalls);
428437
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@
119119
<xsd:element name="getter" type="getter" minOccurs="0" maxOccurs="unbounded" />
120120
<xsd:element name="partial" type="partial" minOccurs="0" maxOccurs="unbounded" />
121121
<xsd:element name="autowiring-type" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
122-
<xsd:element name="autowire" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
122+
<xsd:element name="autowire" type="autowire" minOccurs="0" maxOccurs="unbounded" />
123123
</xsd:choice>
124124
<xsd:attribute name="id" type="xsd:string" />
125125
<xsd:attribute name="class" type="xsd:string" />
@@ -137,6 +137,10 @@
137137
<xsd:attribute name="inherit-tags" type="boolean" />
138138
</xsd:complexType>
139139

140+
<xsd:complexType name="autowire" mixed="true">
141+
<xsd:attribute name="type" type="xsd:string" />
142+
</xsd:complexType>
143+
140144
<xsd:complexType name="tag">
141145
<xsd:attribute name="name" type="xsd:string" use="required" />
142146
<xsd:anyAttribute namespace="##any" processContents="lax" />

src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,17 @@ public function testLogUnusedPatterns()
622622
$this->assertEquals(array(AutowirePass::class.': Autowiring\'s patterns "not", "exist*" for service "foo" don\'t match any method.'), $container->getCompiler()->getLog());
623623
}
624624

625+
public function testPartialMethodCalls()
626+
{
627+
$container = new ContainerBuilder();
628+
629+
$container->register('a', A::class);
630+
$container->register('foo', Foo::class);
631+
$definition = $container->register('bar', SetterInjection::class);
632+
$definition->setAutowired(true);
633+
$definition->addMethodCall('setDependencies', array(new Reference('foo')));
634+
}
635+
625636
public function testEmptyStringIsKept()
626637
{
627638
$container = new ContainerBuilder();
@@ -637,12 +648,41 @@ public function testEmptyStringIsKept()
637648

638649
$this->assertEquals(array(new Reference('a'), '', new Reference('lille')), $container->getDefinition('foo')->getArguments());
639650
}
651+
652+
public function testPartial()
653+
{
654+
$container = new ContainerBuilder();
655+
656+
$container->register('foo', FooController::class)
657+
->setAutowiredPartials(array('*Action'))
658+
->setPartialMethod('barAction', array(1 => 123));
659+
660+
$pass = new AutowirePass();
661+
$pass->process($container);
662+
663+
$expected = array(
664+
'baraction' => array(1 => 123, 2 => new Reference('autowired.'.Foo::class)),
665+
'fooaction' => array(2 => new Reference('autowired.'.Foo::class)),
666+
);
667+
$this->assertEquals($expected, $container->getDefinition('foo')->getPartialMethods());
668+
}
640669
}
641670

642671
class Foo
643672
{
644673
}
645674

675+
class FooController
676+
{
677+
public function fooAction($a, $b, Foo $foo)
678+
{
679+
}
680+
681+
public function barAction($a, $b, Foo $foo)
682+
{
683+
}
684+
}
685+
646686
class Bar
647687
{
648688
public function __construct(Foo $foo)

src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionTemplatesPassTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,15 +216,18 @@ public function testSetAutowiredOnServiceHasParent()
216216

217217
$container->register('parent', 'stdClass')
218218
->setAutowiredCalls(array('foo'))
219+
->setAutowiredPartials(array('bar'))
219220
;
220221

221222
$container->setDefinition('child1', new ChildDefinition('parent'))
222223
->setAutowiredCalls(array('baz'))
224+
->setAutowiredPartials(array('buz'))
223225
;
224226

225227
$this->process($container);
226228

227229
$this->assertEquals(array('baz'), $container->getDefinition('child1')->getAutowiredCalls());
230+
$this->assertEquals(array('buz'), $container->getDefinition('child1')->getAutowiredPartials());
228231
}
229232

230233
public function testSetAutowiredOnServiceIsParent()
@@ -233,13 +236,15 @@ public function testSetAutowiredOnServiceIsParent()
233236

234237
$container->register('parent', 'stdClass')
235238
->setAutowiredCalls(array('__construct', 'set*'))
239+
->setAutowiredPartials(array('*Action'))
236240
;
237241

238242
$container->setDefinition('child1', new ChildDefinition('parent'));
239243

240244
$this->process($container);
241245

242246
$this->assertEquals(array('__construct', 'set*'), $container->getDefinition('child1')->getAutowiredCalls());
247+
$this->assertEquals(array('*Action'), $container->getDefinition('child1')->getAutowiredPartials());
243248
}
244249

245250
public function testDeepDefinitionsResolving()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
services:
22
foo:
33
class: Foo
4+
autowire: [ !partials '*Action' ]
45
partials:
56
method1: { 1: 'bar', 2: '@bar' }
67

0 commit comments

Comments
 (0)