Skip to content

Commit 995bd4f

Browse files
dunglasxabbuh
authored andcommitted
[DependencyInjection] Autowiring doc
1 parent 168072b commit 995bd4f

File tree

3 files changed

+399
-0
lines changed

3 files changed

+399
-0
lines changed

Diff for: components/dependency_injection/autowiring.rst

+397
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,397 @@
1+
.. index::
2+
single: DependencyInjection; Autowiring
3+
4+
Defining Services Dependencies Automatically
5+
============================================
6+
7+
Autowiring allows to register services in the container with minimal configuration.
8+
It is useful in the field of `Rapid Application Development`_, when designing prototypes
9+
in early stages of large projects. It makes it easy to register a service graph
10+
and eases refactoring.
11+
12+
Imagine you're building an API to publish statuses on a Twitter feed, obfuscated
13+
with `ROT13`.. (a special case of the Caesar cipher).
14+
15+
Start by creating a ROT13 transformer class::
16+
17+
// src/AppBundle/Rot13Transformer.php
18+
namespace AppBundle;
19+
20+
class Rot13Transformer
21+
{
22+
public function transform($value)
23+
{
24+
return str_rot13($value);
25+
}
26+
}
27+
28+
And now a Twitter client using this transformer::
29+
30+
// src/AppBundle/TwitterClient.php
31+
namespace AppBundle;
32+
33+
class TwitterClient
34+
{
35+
private $transformer;
36+
37+
public function __construct(Rot13Transformer $transformer)
38+
{
39+
$this->transformer = $transformer;
40+
}
41+
42+
public function tweet($user, $key, $status)
43+
{
44+
$transformedStatus = $this->transformer->transform($status);
45+
46+
// ... connect to Twitter and send the encoded status
47+
}
48+
}
49+
50+
The Dependency Injection Component will be able to automatically register the dependencies
51+
of this ``TwitterClient`` class by marking the ``twitter_client`` service as autowired:
52+
53+
.. configuration-block::
54+
55+
.. code-block:: yaml
56+
57+
# app/config/services.yml
58+
services:
59+
twitter_client:
60+
class: 'AppBundle\TwitterClient'
61+
autowire: true
62+
63+
.. code-block:: xml
64+
65+
<!-- app/config/services.xml -->
66+
<?xml version="1.0" encoding="UTF-8" ?>
67+
<container xmlns="http://symfony.com/schema/dic/services"
68+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
69+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
70+
71+
<services>
72+
<service id="twitter_client" class="AppBundle\TwitterClient" autowire="true" />
73+
</services>
74+
</services>
75+
76+
.. code-block:: php
77+
78+
use Symfony\Component\DependencyInjection\Definition;
79+
80+
// ...
81+
$definition = new Definition('AppBundle\TwitterClient');
82+
$definition->setAutowired(true);
83+
84+
$container->setDefinition('twitter_client', $definition);
85+
86+
The autowiring subsystem will detect the dependencies of the ``TwitterClient``
87+
class by parsing its constructor. For instance it will find here an instance of
88+
a ``Rot13Transformer`` as dependency. If an existing service definition (and only
89+
one – see below) is of the required type, this service will be injected. If it's
90+
not the case (like in this example), the subsystem is smart enough to automatically
91+
register a private service for the ``Rot13Transformer`` class and set it as first
92+
argument of the ``twitter_client`` service. Again, it can work only if there is one
93+
class of the given type. If there are several classes of the same type, you must
94+
use an explicit service definition or register a default implementation.
95+
96+
As you can see, the autowiring feature drastically reduces the amount of configuration
97+
required to define a service. No more arguments section! It also makes it easy
98+
to change the dependencies of the ``TwitterClient`` class: just add or remove typehinted
99+
arguments in the constructor and you are done. There is no need anymore to search
100+
and edit related service definitions.
101+
102+
Here is a typical controller using the ``twitter_client`` service::
103+
104+
// src/AppBundle/Controller/DefaultController.php
105+
namespace AppBundle\Controller;
106+
107+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
108+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
109+
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
110+
use Symfony\Component\HttpFoundation\Request;
111+
use Symfony\Component\HttpFoundation\Response;
112+
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
113+
114+
class DefaultController extends Controller
115+
{
116+
/**
117+
* @Route("/tweet")
118+
* @Method("POST")
119+
*/
120+
public function tweetAction(Request $request)
121+
{
122+
$user = $request->request->get('user');
123+
$key = $request->request->get('key');
124+
$status = $request->request->get('status');
125+
126+
if (!$user || !$key || !$status) {
127+
throw new BadRequestHttpException();
128+
}
129+
130+
$this->get('twitter_client')->tweet($user, $key, $status);
131+
132+
return new Response('OK');
133+
}
134+
}
135+
136+
You can give a try to the API with ``curl``::
137+
138+
curl -d "user=kevin&key=ABCD&status=Hello" http://localhost:8000/tweet
139+
140+
It should return ``OK``.
141+
142+
Working with Interfaces
143+
-----------------------
144+
145+
You might also find yourself using abstractions instead of implementations (especially
146+
in grown applications) as it allows to easily replace some dependencies without
147+
modifying the class depending of them.
148+
149+
To follow this best practice, constructor arguments must be typehinted with interfaces
150+
and not concrete classes. It allows to replace easily the current implementation
151+
if necessary. It also allows to use other transformers.
152+
153+
Let's introduce a ``TransformerInterface``::
154+
155+
// src/AppBundle/TransformerInterface.php
156+
namespace AppBundle;
157+
158+
interface TransformerInterface
159+
{
160+
public function transform($value);
161+
}
162+
163+
Then edit ``Rot13Transformer`` to make it implementing the new interface::
164+
165+
// ...
166+
167+
class Rot13Transformer implements TransformerInterface
168+
169+
// ...
170+
171+
172+
And update ``TwitterClient`` to depend of this new interface::
173+
174+
class TwitterClient
175+
{
176+
// ...
177+
178+
public function __construct(TransformerInterface $transformer)
179+
{
180+
// ...
181+
}
182+
183+
// ...
184+
}
185+
186+
Finally the service definition must be updated because, obviously, the autowiring
187+
subsystem isn't able to find itself the interface implementation to register::
188+
189+
.. configuration-block::
190+
191+
.. code-block:: yaml
192+
193+
# app/config/services.yml
194+
services:
195+
rot13_transformer:
196+
class: 'AppBundle\Rot13Transformer'
197+
198+
twitter_client:
199+
class: 'AppBundle\TwitterClient'
200+
autowire: true
201+
202+
.. code-block:: xml
203+
204+
<!-- app/config/services.xml -->
205+
<?xml version="1.0" encoding="UTF-8" ?>
206+
<container xmlns="http://symfony.com/schema/dic/services"
207+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
208+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
209+
210+
<services>
211+
<service id="rot13_transformer" class="AppBundle\Rot13Transformer" />
212+
<service id="twitter_client" class="AppBundle\TwitterClient" autowire="true" />
213+
</services>
214+
</services>
215+
216+
.. code-block:: php
217+
218+
use Symfony\Component\DependencyInjection\Definition;
219+
220+
// ...
221+
$definition1 = new Definition('AppBundle\Rot13Transformer');
222+
$container->setDefinition('rot13_transformer', $definition1);
223+
224+
$definition2 = new Definition('AppBundle\TwitterClient');
225+
$definition2->setAutowired(true);
226+
$container->setDefinition('twitter_client', $definition2);
227+
228+
The autowiring subsystem detects that the ``rot13_transformer`` service implements
229+
the ``TransformerInterface`` and injects it automatically. Even when using
230+
interfaces (and you should), building the service graph and refactoring the project
231+
is easier than with standard definitions.
232+
233+
Dealing with Multiple Implementations of the Same Type
234+
------------------------------------------------------
235+
236+
Last but not least, the autowiring feature allows to specify the default implementation
237+
of a given type. Let's introduce a new implementation of the ``TransformerInterface``
238+
returning the result of the ROT13 transformation uppercased::
239+
240+
// src/AppBundle/UppercaseRot13Transformer.php
241+
namespace AppBundle;
242+
243+
class UppercaseTransformer implements TransformerInterface
244+
{
245+
private $transformer;
246+
247+
public function __construct(TransformerInterface $transformer)
248+
{
249+
$this->transformer = $transformer;
250+
}
251+
252+
public function transform($value)
253+
{
254+
return strtoupper($this->transformer->transform($value));
255+
}
256+
}
257+
258+
This class is intended to decorate the any transformer and return its value uppercased.
259+
260+
We can now refactor the controller to add another endpoint leveraging this new
261+
transformer::
262+
263+
// src/AppBundle/Controller/DefaultController.php
264+
namespace AppBundle\Controller;
265+
266+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
267+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
268+
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
269+
use Symfony\Component\HttpFoundation\Request;
270+
use Symfony\Component\HttpFoundation\Response;
271+
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
272+
273+
class DefaultController extends Controller
274+
{
275+
/**
276+
* @Route("/tweet")
277+
* @Method("POST")
278+
*/
279+
public function tweetAction(Request $request)
280+
{
281+
return $this->tweet($request, 'twitter_client');
282+
}
283+
284+
/**
285+
* @Route("/tweet-uppercase")
286+
* @Method("POST")
287+
*/
288+
public function tweetUppercaseAction(Request $request)
289+
{
290+
return $this->tweet($request, 'uppercase_twitter_client');
291+
}
292+
293+
private function tweet(Request $request, $service)
294+
{
295+
$user = $request->request->get('user');
296+
$key = $request->request->get('key');
297+
$status = $request->request->get('status');
298+
299+
if (!$user || !$key || !$status) {
300+
throw new BadRequestHttpException();
301+
}
302+
303+
$this->get($service)->tweet($user, $key, $status);
304+
305+
return new Response('OK');
306+
}
307+
}
308+
309+
The last step is to update service definitions to register this new implementation
310+
and a Twitter client using it::
311+
312+
.. configuration-block::
313+
314+
.. code-block:: yaml
315+
316+
# app/config/services.yml
317+
services:
318+
rot13_transformer:
319+
class: 'AppBundle\Rot13Transformer'
320+
autowiring_types: 'AppBundle\TransformerInterface'
321+
322+
twitter_client:
323+
class: 'AppBundle\TwitterClient'
324+
autowire: true
325+
326+
uppercase_rot13_transformer:
327+
class: 'AppBundle\UppercaseRot13Transformer'
328+
autowire: true
329+
330+
uppercase_twitter_client:
331+
class: 'AppBundle\TwitterClient'
332+
arguments: [ '@uppercase_rot13_transformer' ]
333+
334+
.. code-block:: xml
335+
336+
<!-- app/config/services.xml -->
337+
<?xml version="1.0" encoding="UTF-8" ?>
338+
<container xmlns="http://symfony.com/schema/dic/services"
339+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
340+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
341+
342+
<services>
343+
<service id="rot13_transformer" class="AppBundle\Rot13Transformer">
344+
<autowiring-type>AppBundle\TransformerInterface</autowiring-type>
345+
</service>
346+
<service id="twitter_client" class="AppBundle\TwitterClient" autowire="true" />
347+
<service id="uppercase_rot13_transformer" class="AppBundle\UppercaseRot13Transformer" autowire="true" />
348+
<service id="uppercase_twitter_client" class="AppBundle\TwitterClient">
349+
<argument type="service" id="uppercase_rot13_transformer" />
350+
</service>
351+
</services>
352+
</services>
353+
354+
.. code-block:: php
355+
356+
use Symfony\Component\DependencyInjection\Reference;
357+
use Symfony\Component\DependencyInjection\Definition;
358+
359+
// ...
360+
$definition1 = new Definition('AppBundle\Rot13Transformer');
361+
$definition1->setAutowiringTypes(array('AppBundle\TransformerInterface'));
362+
$container->setDefinition('rot13_transformer', $definition1);
363+
364+
$definition2 = new Definition('AppBundle\TwitterClient');
365+
$definition2->setAutowired(true);
366+
$container->setDefinition('twitter_client', $definition2);
367+
368+
$definition3 = new Definition('AppBundle\UppercaseRot13Transformer');
369+
$definition3->setAutowired(true);
370+
$container->setDefinition('uppercase_rot13_transformer', $definition3);
371+
372+
$definition4 = new Definition('AppBundle\TwitterClient');
373+
$definition4->addArgument(new Reference('uppercase_rot13_transformer'));
374+
$container->setDefinition('uppercase_twitter_client', $definition4);
375+
376+
It deserves some explanations. We now have 2 services implementing the ``TransformerInterface``.
377+
The autowiring subsystem cannot guess which one to use, this leads to errors
378+
like::
379+
380+
[Symfony\Component\DependencyInjection\Exception\RuntimeException]
381+
Unable to autowire argument of type "AppBundle\TransformerInterface" for the service "twitter_client".
382+
383+
Fortunately, the ``autowiring_types`` key is here to specify which implementation
384+
to use by default. This key can take a list of types if necessary (using a YAML
385+
array).
386+
387+
Thanks to this setting, the ``rot13_transformer`` service is automatically injected
388+
as an argument of the ``uppercase_rot13_transformer`` and ``twitter_client`` services. For
389+
the ``uppercase_twitter_client``, we use a standard service definition to inject
390+
the specific ``uppercase_rot13_transformer`` service.
391+
392+
As for other RAD features such as the FrameworkBundle controller or annotations,
393+
keep in mind to not use autowiring in public bundles nor in large projects with
394+
complex maintenance needs.
395+
396+
.. _Rapid Application Development: https://en.wikipedia.org/wiki/Rapid_application_development
397+
.. _ROT13: https://en.wikipedia.org/wiki/ROT13

0 commit comments

Comments
 (0)