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 69dcf41

Browse filesBrowse files
committed
feature #20167 [DependencyInjection] Make method (setter) autowiring configurable (dunglas)
This PR was merged into the 3.3-dev branch. Discussion ---------- [DependencyInjection] Make method (setter) autowiring configurable | Q | A | | --- | --- | | Branch? | master | | Bug fix? | no | | New feature? | yes | | BC breaks? | no | | Deprecations? | maybe? | | Tests pass? | yes | | Fixed tickets | #19631 | | License | MIT | | Doc PR | symfony/symfony-docs#7041 | Follow up of #19631. Implements #19631 (comment): Edit: the last supported format: ``` yaml services: foo: class: Foo autowire: ['__construct', 'set*'] # Autowire constructor and all setters autowire: true # Converted by loaders in `autowire: ['__construct']` for BC autowire: ['foo', 'bar'] # Autowire only `foo` and `bar` methods ``` Outdated: ``` yaml autowire: true # constructor autowiring autowire: [__construct, setFoo, setBar] # autowire whitelisted methods only autowire: '*' # autowire constructor + every setters (following existing rules for setters autowiring) ``` - [x] Allow to specify the list of methods in the XML loader - [x] Add tests for the YAML loader Commits ------- 6dd53c7 [DependencyInjection] Introduce method injection for autowiring
2 parents a3577eb + 6dd53c7 commit 69dcf41
Copy full SHA for 69dcf41

File tree

13 files changed

+375
-33
lines changed
Filter options

13 files changed

+375
-33
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
+104-29Lines changed: 104 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
*/
2525
class AutowirePass implements CompilerPassInterface
2626
{
27+
/**
28+
* @var ContainerBuilder
29+
*/
2730
private $container;
2831
private $reflectionClasses = array();
2932
private $definedTypes = array();
@@ -41,8 +44,8 @@ public function process(ContainerBuilder $container)
4144
try {
4245
$this->container = $container;
4346
foreach ($container->getDefinitions() as $id => $definition) {
44-
if ($definition->isAutowired()) {
45-
$this->completeDefinition($id, $definition);
47+
if ($autowiredMethods = $definition->getAutowiredMethods()) {
48+
$this->completeDefinition($id, $definition, $autowiredMethods);
4649
}
4750
}
4851
} finally {
@@ -72,8 +75,10 @@ public static function createResourceForClass(\ReflectionClass $reflectionClass)
7275
$metadata['__construct'] = self::getResourceMetadataForMethod($constructor);
7376
}
7477

75-
foreach (self::getSetters($reflectionClass) as $reflectionMethod) {
76-
$metadata[$reflectionMethod->name] = self::getResourceMetadataForMethod($reflectionMethod);
78+
foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
79+
if (!$reflectionMethod->isStatic()) {
80+
$metadata[$reflectionMethod->name] = self::getResourceMetadataForMethod($reflectionMethod);
81+
}
7782
}
7883

7984
return new AutowireServiceResource($reflectionClass->name, $reflectionClass->getFileName(), $metadata);
@@ -84,10 +89,11 @@ public static function createResourceForClass(\ReflectionClass $reflectionClass)
8489
*
8590
* @param string $id
8691
* @param Definition $definition
92+
* @param string[] $autowiredMethods
8793
*
8894
* @throws RuntimeException
8995
*/
90-
private function completeDefinition($id, Definition $definition)
96+
private function completeDefinition($id, Definition $definition, array $autowiredMethods)
9197
{
9298
if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
9399
return;
@@ -97,12 +103,75 @@ private function completeDefinition($id, Definition $definition)
97103
$this->container->addResource(static::createResourceForClass($reflectionClass));
98104
}
99105

100-
if (!$constructor = $reflectionClass->getConstructor()) {
101-
return;
106+
$methodsCalled = array();
107+
foreach ($definition->getMethodCalls() as $methodCall) {
108+
$methodsCalled[$methodCall[0]] = true;
102109
}
103110

104-
$arguments = $definition->getArguments();
105-
foreach ($constructor->getParameters() as $index => $parameter) {
111+
foreach ($this->getMethodsToAutowire($id, $reflectionClass, $autowiredMethods) as $reflectionMethod) {
112+
if (!isset($methodsCalled[$reflectionMethod->name])) {
113+
$this->autowireMethod($id, $definition, $reflectionMethod);
114+
}
115+
}
116+
}
117+
118+
/**
119+
* Gets the list of methods to autowire.
120+
*
121+
* @param string $id
122+
* @param \ReflectionClass $reflectionClass
123+
* @param string[] $autowiredMethods
124+
*
125+
* @return \ReflectionMethod[]
126+
*/
127+
private function getMethodsToAutowire($id, \ReflectionClass $reflectionClass, array $autowiredMethods)
128+
{
129+
$found = array();
130+
$regexList = array();
131+
foreach ($autowiredMethods as $pattern) {
132+
$regexList[] = '/^'.str_replace('\*', '.*', preg_quote($pattern, '/')).'$/i';
133+
}
134+
135+
foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
136+
if ($reflectionMethod->isStatic()) {
137+
continue;
138+
}
139+
140+
foreach ($regexList as $k => $regex) {
141+
if (preg_match($regex, $reflectionMethod->name)) {
142+
$found[] = $autowiredMethods[$k];
143+
yield $reflectionMethod;
144+
145+
continue 2;
146+
}
147+
}
148+
}
149+
150+
if ($notFound = array_diff($autowiredMethods, $found)) {
151+
$compiler = $this->container->getCompiler();
152+
$compiler->addLogMessage($compiler->getLoggingFormatter()->formatUnusedAutowiringPatterns($this, $id, $notFound));
153+
}
154+
}
155+
156+
/**
157+
* Autowires the constructor or a setter.
158+
*
159+
* @param string $id
160+
* @param Definition $definition
161+
* @param \ReflectionMethod $reflectionMethod
162+
*
163+
* @throws RuntimeException
164+
*/
165+
private function autowireMethod($id, Definition $definition, \ReflectionMethod $reflectionMethod)
166+
{
167+
if ($isConstructor = $reflectionMethod->isConstructor()) {
168+
$arguments = $definition->getArguments();
169+
} else {
170+
$arguments = array();
171+
}
172+
173+
$addMethodCall = false; // Whether the method should be added to the definition as a call or as arguments
174+
foreach ($reflectionMethod->getParameters() as $index => $parameter) {
106175
if (array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
107176
continue;
108177
}
@@ -111,7 +180,11 @@ private function completeDefinition($id, Definition $definition)
111180
if (!$typeHint = $parameter->getClass()) {
112181
// no default value? Then fail
113182
if (!$parameter->isOptional()) {
114-
throw new RuntimeException(sprintf('Unable to autowire argument index %d ($%s) for the service "%s". If this is an object, give it a type-hint. Otherwise, specify this argument\'s value explicitly.', $index, $parameter->name, $id));
183+
if ($isConstructor) {
184+
throw new RuntimeException(sprintf('Unable to autowire argument index %d ($%s) for the service "%s". If this is an object, give it a type-hint. Otherwise, specify this argument\'s value explicitly.', $index, $parameter->name, $id));
185+
}
186+
187+
return;
115188
}
116189

117190
// specifically pass the default value
@@ -126,24 +199,35 @@ private function completeDefinition($id, Definition $definition)
126199

127200
if (isset($this->types[$typeHint->name])) {
128201
$value = new Reference($this->types[$typeHint->name]);
202+
$addMethodCall = true;
129203
} else {
130204
try {
131205
$value = $this->createAutowiredDefinition($typeHint, $id);
206+
$addMethodCall = true;
132207
} catch (RuntimeException $e) {
133208
if ($parameter->allowsNull()) {
134209
$value = null;
135210
} elseif ($parameter->isDefaultValueAvailable()) {
136211
$value = $parameter->getDefaultValue();
137212
} else {
138-
throw $e;
213+
// The exception code is set to 1 if the exception must be thrown even if it's a setter
214+
if (1 === $e->getCode() || $isConstructor) {
215+
throw $e;
216+
}
217+
218+
return;
139219
}
140220
}
141221
}
142222
} catch (\ReflectionException $e) {
143223
// Typehint against a non-existing class
144224

145225
if (!$parameter->isDefaultValueAvailable()) {
146-
throw new RuntimeException(sprintf('Cannot autowire argument %s for %s because the type-hinted class does not exist (%s).', $index + 1, $definition->getClass(), $e->getMessage()), 0, $e);
226+
if ($isConstructor) {
227+
throw new RuntimeException(sprintf('Cannot autowire argument %s for %s because the type-hinted class does not exist (%s).', $index + 1, $definition->getClass(), $e->getMessage()), 0, $e);
228+
}
229+
230+
return;
147231
}
148232

149233
$value = $parameter->getDefaultValue();
@@ -155,7 +239,12 @@ private function completeDefinition($id, Definition $definition)
155239
// it's possible index 1 was set, then index 0, then 2, etc
156240
// make sure that we re-order so they're injected as expected
157241
ksort($arguments);
158-
$definition->setArguments($arguments);
242+
243+
if ($isConstructor) {
244+
$definition->setArguments($arguments);
245+
} elseif ($addMethodCall) {
246+
$definition->addMethodCall($reflectionMethod->name, $arguments);
247+
}
159248
}
160249

161250
/**
@@ -253,7 +342,7 @@ private function createAutowiredDefinition(\ReflectionClass $typeHint, $id)
253342
$classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
254343
$matchingServices = implode(', ', $this->ambiguousServiceTypes[$typeHint->name]);
255344

256-
throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Multiple services exist for this %s (%s).', $typeHint->name, $id, $classOrInterface, $matchingServices));
345+
throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Multiple services exist for this %s (%s).', $typeHint->name, $id, $classOrInterface, $matchingServices), 1);
257346
}
258347

259348
if (!$typeHint->isInstantiable()) {
@@ -269,7 +358,7 @@ private function createAutowiredDefinition(\ReflectionClass $typeHint, $id)
269358
$this->populateAvailableType($argumentId, $argumentDefinition);
270359

271360
try {
272-
$this->completeDefinition($argumentId, $argumentDefinition);
361+
$this->completeDefinition($argumentId, $argumentDefinition, array('__construct'));
273362
} catch (RuntimeException $e) {
274363
$classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
275364
$message = sprintf('Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.', $typeHint->name, $id, $classOrInterface);
@@ -320,20 +409,6 @@ private function addServiceToAmbiguousType($id, $type)
320409
$this->ambiguousServiceTypes[$type][] = $id;
321410
}
322411

323-
/**
324-
* @param \ReflectionClass $reflectionClass
325-
*
326-
* @return \ReflectionMethod[]
327-
*/
328-
private static function getSetters(\ReflectionClass $reflectionClass)
329-
{
330-
foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
331-
if (!$reflectionMethod->isStatic() && 1 === $reflectionMethod->getNumberOfParameters() && 0 === strpos($reflectionMethod->name, 'set')) {
332-
yield $reflectionMethod;
333-
}
334-
}
335-
}
336-
337412
private static function getResourceMetadataForMethod(\ReflectionMethod $method)
338413
{
339414
$methodArgumentsMetadata = array();

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/LoggingFormatter.php
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ public function formatResolveInheritance(CompilerPassInterface $pass, $childId,
3838
return $this->format($pass, sprintf('Resolving inheritance for "%s" (parent: %s).', $childId, $parentId));
3939
}
4040

41+
public function formatUnusedAutowiringPatterns(CompilerPassInterface $pass, $id, array $patterns)
42+
{
43+
return $this->format($pass, sprintf('Autowiring\'s patterns "%s" for service "%s" don\'t match any method.', implode('", "', $patterns), $id));
44+
}
45+
4146
public function format(CompilerPassInterface $pass, $message)
4247
{
4348
return sprintf('%s: %s', get_class($pass), $message);

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Definition.php
+34-3Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class Definition
3636
private $abstract = false;
3737
private $lazy = false;
3838
private $decoratedService;
39-
private $autowired = false;
39+
private $autowiredMethods = array();
4040
private $autowiringTypes = array();
4141

4242
protected $arguments;
@@ -662,19 +662,50 @@ public function setAutowiringTypes(array $types)
662662
*/
663663
public function isAutowired()
664664
{
665-
return $this->autowired;
665+
return !empty($this->autowiredMethods);
666+
}
667+
668+
/**
669+
* Gets autowired methods.
670+
*
671+
* @return string[]
672+
*/
673+
public function getAutowiredMethods()
674+
{
675+
return $this->autowiredMethods;
666676
}
667677

668678
/**
669679
* Sets autowired.
670680
*
681+
* Allowed values:
682+
* - true: constructor autowiring, same as $this->setAutowiredMethods(array('__construct'))
683+
* - false: no autowiring, same as $this->setAutowiredMethods(array())
684+
*
671685
* @param bool $autowired
672686
*
673687
* @return Definition The current instance
674688
*/
675689
public function setAutowired($autowired)
676690
{
677-
$this->autowired = $autowired;
691+
$this->autowiredMethods = $autowired ? array('__construct') : array();
692+
693+
return $this;
694+
}
695+
696+
/**
697+
* Sets autowired methods.
698+
*
699+
* Example of allowed value:
700+
* - array('__construct', 'set*', 'initialize'): autowire whitelisted methods only
701+
*
702+
* @param string[] $autowiredMethods
703+
*
704+
* @return Definition The current instance
705+
*/
706+
public function setAutowiredMethods(array $autowiredMethods)
707+
{
708+
$this->autowiredMethods = $autowiredMethods;
678709

679710
return $this;
680711
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,19 @@ private function parseDefinition(\DOMElement $service, $file)
239239
$definition->addAutowiringType($type->textContent);
240240
}
241241

242+
$autowireTags = array();
243+
foreach ($this->getChildren($service, 'autowire') as $type) {
244+
$autowireTags[] = $type->textContent;
245+
}
246+
247+
if ($autowireTags) {
248+
if ($service->hasAttribute('autowire')) {
249+
throw new InvalidArgumentException(sprintf('The "autowire" attribute cannot be used together with "<autowire>" tags for service "%s" in %s.', (string) $service->getAttribute('id'), $file));
250+
}
251+
252+
$definition->setAutowiredMethods($autowireTags);
253+
}
254+
242255
if ($value = $service->getAttribute('decorates')) {
243256
$renameId = $service->hasAttribute('decoration-inner-name') ? $service->getAttribute('decoration-inner-name') : null;
244257
$priority = $service->hasAttribute('decoration-priority') ? $service->getAttribute('decoration-priority') : 0;

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
+5-1Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,11 @@ private function parseDefinition($id, $service, $file)
302302
}
303303

304304
if (isset($service['autowire'])) {
305-
$definition->setAutowired($service['autowire']);
305+
if (is_array($service['autowire'])) {
306+
$definition->setAutowiredMethods($service['autowire']);
307+
} else {
308+
$definition->setAutowired($service['autowire']);
309+
}
306310
}
307311

308312
if (isset($service['autowiring_types'])) {

‎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
@@ -100,6 +100,7 @@
100100
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
101101
<xsd:element name="property" type="property" minOccurs="0" maxOccurs="unbounded" />
102102
<xsd:element name="autowiring-type" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
103+
<xsd:element name="autowire" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
103104
</xsd:choice>
104105
<xsd:attribute name="id" type="xsd:string" />
105106
<xsd:attribute name="class" type="xsd:string" />

0 commit comments

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