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 995bd4f

Browse filesBrowse files
dunglasxabbuh
authored andcommitted
[DependencyInjection] Autowiring doc
1 parent 168072b commit 995bd4f
Copy full SHA for 995bd4f

File tree

Expand file treeCollapse file tree

3 files changed

+399
-0
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+399
-0
lines changed
+397Lines changed: 397 additions & 0 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.