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

Skip to content

Commit 478ad54

Browse files
committed
feature #15613 [DependencyInjection] Add autowiring capabilities (dunglas)
This PR was squashed before being merged into the 2.8 branch (closes #15613). Discussion ---------- [DependencyInjection] Add autowiring capabilities | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | n/a | License | MIT | Doc PR | not yet This PR adds autowiring capabilities to the Dependency Injection component. It eases service registration by letting the component guessing dependencies to inject and even (under certain conditions) registering them using typehints of the constructor parameters. The following usages are supported: # Automatic dependency registration ```php class Foo { } class Bar { public function __construct(Foo $f) { } } ``` ```yaml services: bar: class: Bar autowire: true ``` It will register `Foo` as a private service (`autowired.foo`) and injects it as the first argument of the `bar` constructor. This method works only for typehints corresponding to instantiable classes (interfaces and abstract classes are not supported). # Autocompletion of definition arguments ```php interface A { } interface B extends A { } class Foo implements B { } class Bar { } class Baz extends Bar { } class LesTilleuls { public function __construct(A $a, Bar $bar) { } } ``` ```yaml services: foo: class: Foo baz: class: Baz les_tilleuls: class: LesTilleuls autowire: true ``` The autowiring system will find types of all services and completes constructor arguments of the `les_tilleuls` service definition using typehints. It works only if there is one service registered for a given type (if there are several services available for the same type and no explicit type definition, a `RuntimeException` is thrown). # Explicit type definition ```php interface A { } class A1 implements A { } class A2 implements A { } class B { public function __construct(A $a) { } } ``` ```yaml services: a1: class: A1 types: [ A ] a2: class: A2 # Will be autowired with A1 class b: class: B autowire: true # Not autowired class another_b: class: B arguments: [ @a2 ] autowire: true ``` When a service is explicitly associated with a type, it is always used to fill a definition depending of this type, even if several services have this type. If several services are associated with the same type, the last definition takes the priority. Of course explicit definitions are still supported. YAML, XML and PHP loaders have been updated to supports the new `type` parameter. Commits ------- aee5731 [DependencyInjection] Add autowiring capabilities
2 parents da92c10 + aee5731 commit 478ad54

17 files changed

+827
-0
lines changed
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\ContainerBuilder;
15+
use Symfony\Component\DependencyInjection\Definition;
16+
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
17+
use Symfony\Component\DependencyInjection\Reference;
18+
19+
/**
20+
* Guesses constructor arguments of services definitions and try to instantiate services if necessary.
21+
*
22+
* @author Kévin Dunglas <[email protected]>
23+
*/
24+
class AutowirePass implements CompilerPassInterface
25+
{
26+
private $container;
27+
private $reflectionClasses = array();
28+
private $definedTypes = array();
29+
private $types;
30+
private $notGuessableTypes = array();
31+
32+
/**
33+
* {@inheritdoc}
34+
*/
35+
public function process(ContainerBuilder $container)
36+
{
37+
$this->container = $container;
38+
foreach ($container->getDefinitions() as $id => $definition) {
39+
if ($definition->isAutowired()) {
40+
$this->completeDefinition($id, $definition);
41+
}
42+
}
43+
44+
// Free memory and remove circular reference to container
45+
$this->container = null;
46+
$this->reflectionClasses = array();
47+
$this->definedTypes = array();
48+
$this->types = null;
49+
$this->notGuessableTypes = array();
50+
}
51+
52+
/**
53+
* Wires the given definition.
54+
*
55+
* @param string $id
56+
* @param Definition $definition
57+
*
58+
* @throws RuntimeException
59+
*/
60+
private function completeDefinition($id, Definition $definition)
61+
{
62+
if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
63+
return;
64+
}
65+
66+
$this->container->addClassResource($reflectionClass);
67+
68+
if (!$constructor = $reflectionClass->getConstructor()) {
69+
return;
70+
}
71+
72+
$arguments = $definition->getArguments();
73+
foreach ($constructor->getParameters() as $index => $parameter) {
74+
$argumentExists = array_key_exists($index, $arguments);
75+
if ($argumentExists && '' !== $arguments[$index]) {
76+
continue;
77+
}
78+
79+
try {
80+
if (!$typeHint = $parameter->getClass()) {
81+
continue;
82+
}
83+
84+
if (null === $this->types) {
85+
$this->populateAvailableTypes();
86+
}
87+
88+
if (isset($this->types[$typeHint->name])) {
89+
$value = new Reference($this->types[$typeHint->name]);
90+
} else {
91+
try {
92+
$value = $this->createAutowiredDefinition($typeHint, $id);
93+
} catch (RuntimeException $e) {
94+
if (!$parameter->isDefaultValueAvailable()) {
95+
throw $e;
96+
}
97+
98+
$value = $parameter->getDefaultValue();
99+
}
100+
}
101+
} catch (\ReflectionException $reflectionException) {
102+
// Typehint against a non-existing class
103+
104+
if (!$parameter->isDefaultValueAvailable()) {
105+
continue;
106+
}
107+
108+
$value = $parameter->getDefaultValue();
109+
}
110+
111+
if ($argumentExists) {
112+
$definition->replaceArgument($index, $value);
113+
} else {
114+
$definition->addArgument($value);
115+
}
116+
}
117+
}
118+
119+
/**
120+
* Populates the list of available types.
121+
*/
122+
private function populateAvailableTypes()
123+
{
124+
$this->types = array();
125+
126+
foreach ($this->container->getDefinitions() as $id => $definition) {
127+
$this->populateAvailableType($id, $definition);
128+
}
129+
}
130+
131+
/**
132+
* Populates the list of available types for a given definition.
133+
*
134+
* @param string $id
135+
* @param Definition $definition
136+
*/
137+
private function populateAvailableType($id, Definition $definition)
138+
{
139+
if (!$definition->getClass()) {
140+
return;
141+
}
142+
143+
foreach ($definition->getAutowiringTypes() as $type) {
144+
$this->definedTypes[$type] = true;
145+
$this->types[$type] = $id;
146+
}
147+
148+
if ($reflectionClass = $this->getReflectionClass($id, $definition)) {
149+
$this->extractInterfaces($id, $reflectionClass);
150+
$this->extractAncestors($id, $reflectionClass);
151+
}
152+
}
153+
154+
/**
155+
* Extracts the list of all interfaces implemented by a class.
156+
*
157+
* @param string $id
158+
* @param \ReflectionClass $reflectionClass
159+
*/
160+
private function extractInterfaces($id, \ReflectionClass $reflectionClass)
161+
{
162+
foreach ($reflectionClass->getInterfaces() as $interfaceName => $reflectionInterface) {
163+
$this->set($interfaceName, $id);
164+
165+
$this->extractInterfaces($id, $reflectionInterface);
166+
}
167+
}
168+
169+
/**
170+
* Extracts all inherited types of a class.
171+
*
172+
* @param string $id
173+
* @param \ReflectionClass $reflectionClass
174+
*/
175+
private function extractAncestors($id, \ReflectionClass $reflectionClass)
176+
{
177+
$this->set($reflectionClass->name, $id);
178+
179+
if ($reflectionParentClass = $reflectionClass->getParentClass()) {
180+
$this->extractAncestors($id, $reflectionParentClass);
181+
}
182+
}
183+
184+
/**
185+
* Associates a type and a service id if applicable.
186+
*
187+
* @param string $type
188+
* @param string $id
189+
*/
190+
private function set($type, $id)
191+
{
192+
if (isset($this->definedTypes[$type]) || isset($this->notGuessableTypes[$type])) {
193+
return;
194+
}
195+
196+
if (isset($this->types[$type])) {
197+
if ($this->types[$type] === $id) {
198+
return;
199+
}
200+
201+
unset($this->types[$type]);
202+
$this->notGuessableTypes[$type] = true;
203+
204+
return;
205+
}
206+
207+
$this->types[$type] = $id;
208+
}
209+
210+
/**
211+
* Registers a definition for the type if possible or throws an exception.
212+
*
213+
* @param \ReflectionClass $typeHint
214+
* @param string $id
215+
*
216+
* @return Reference A reference to the registered definition
217+
*
218+
* @throws RuntimeException
219+
*/
220+
private function createAutowiredDefinition(\ReflectionClass $typeHint, $id)
221+
{
222+
if (!$typeHint->isInstantiable()) {
223+
throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s".', $typeHint->name, $id));
224+
}
225+
226+
$argumentId = sprintf('autowired.%s', $typeHint->name);
227+
228+
$argumentDefinition = $this->container->register($argumentId, $typeHint->name);
229+
$argumentDefinition->setPublic(false);
230+
231+
$this->populateAvailableType($argumentId, $argumentDefinition);
232+
$this->completeDefinition($argumentId, $argumentDefinition);
233+
234+
return new Reference($argumentId);
235+
}
236+
237+
/**
238+
* Retrieves the reflection class associated with the given service.
239+
*
240+
* @param string $id
241+
* @param Definition $definition
242+
*
243+
* @return \ReflectionClass|null
244+
*/
245+
private function getReflectionClass($id, Definition $definition)
246+
{
247+
if (isset($this->reflectionClasses[$id])) {
248+
return $this->reflectionClasses[$id];
249+
}
250+
251+
if (!$class = $definition->getClass()) {
252+
return;
253+
}
254+
255+
$class = $this->container->getParameterBag()->resolveValue($class);
256+
257+
try {
258+
return $this->reflectionClasses[$id] = new \ReflectionClass($class);
259+
} catch (\ReflectionException $reflectionException) {
260+
// return null
261+
}
262+
}
263+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public function __construct()
5050
new CheckDefinitionValidityPass(),
5151
new ResolveReferencesToAliasesPass(),
5252
new ResolveInvalidReferencesPass(),
53+
new AutowirePass(),
5354
new AnalyzeServiceReferencesPass(true),
5455
new CheckCircularReferencesPass(),
5556
new CheckReferenceValidityPass(),

src/Symfony/Component/DependencyInjection/Definition.php

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ class Definition
4141
private $synchronized = false;
4242
private $lazy = false;
4343
private $decoratedService;
44+
private $autowired = false;
45+
private $autowiringTypes = array();
4446

4547
protected $arguments;
4648

@@ -818,4 +820,96 @@ public function getConfigurator()
818820
{
819821
return $this->configurator;
820822
}
823+
824+
/**
825+
* Sets types that will default to this definition.
826+
*
827+
* @param string[] $types
828+
*
829+
* @return Definition The current instance
830+
*/
831+
public function setAutowiringTypes(array $types)
832+
{
833+
$this->autowiringTypes = array();
834+
835+
foreach ($types as $type) {
836+
$this->autowiringTypes[$type] = true;
837+
}
838+
839+
return $this;
840+
}
841+
842+
/**
843+
* Is the definition autowired?
844+
*
845+
* @return bool
846+
*/
847+
public function isAutowired()
848+
{
849+
return $this->autowired;
850+
}
851+
852+
/**
853+
* Sets autowired.
854+
*
855+
* @param $autowired
856+
*
857+
* @return Definition The current instance
858+
*/
859+
public function setAutowired($autowired)
860+
{
861+
$this->autowired = $autowired;
862+
863+
return $this;
864+
}
865+
866+
/**
867+
* Gets autowiring types that will default to this definition.
868+
*
869+
* @return string[]
870+
*/
871+
public function getAutowiringTypes()
872+
{
873+
return array_keys($this->autowiringTypes);
874+
}
875+
876+
/**
877+
* Adds a type that will default to this definition.
878+
*
879+
* @param string $type
880+
*
881+
* @return Definition The current instance
882+
*/
883+
public function addAutowiringType($type)
884+
{
885+
$this->autowiringTypes[$type] = true;
886+
887+
return $this;
888+
}
889+
890+
/**
891+
* Removes a type.
892+
*
893+
* @param string $type
894+
*
895+
* @return Definition The current instance
896+
*/
897+
public function removeAutowiringType($type)
898+
{
899+
unset($this->autowiringTypes[$type]);
900+
901+
return $this;
902+
}
903+
904+
/**
905+
* Will this definition default for the given type?
906+
*
907+
* @param string $type
908+
*
909+
* @return bool
910+
*/
911+
public function hasAutowiringType($type)
912+
{
913+
return isset($this->autowiringTypes[$type]);
914+
}
821915
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ private function parseDefinition(\DOMElement $service, $file)
157157
}
158158
}
159159

160+
if ($value = $service->getAttribute('autowire')) {
161+
$definition->setAutowired(XmlUtils::phpize($value));
162+
}
163+
160164
if ($value = $service->getAttribute('scope')) {
161165
$triggerDeprecation = 'request' !== (string) $service->getAttribute('id');
162166

@@ -247,6 +251,10 @@ private function parseDefinition(\DOMElement $service, $file)
247251
$definition->addTag($tag->getAttribute('name'), $parameters);
248252
}
249253

254+
foreach ($this->getChildren($service, 'autowiring-type') as $type) {
255+
$definition->addAutowiringType($type->textContent);
256+
}
257+
250258
if ($value = $service->getAttribute('decorates')) {
251259
$renameId = $service->hasAttribute('decoration-inner-name') ? $service->getAttribute('decoration-inner-name') : null;
252260
$priority = $service->hasAttribute('decoration-priority') ? $service->getAttribute('decoration-priority') : 0;

0 commit comments

Comments
 (0)