-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DependencyInjection] Autowiring doc #6032
Changes from 3 commits
867e2af
cbf7448
c818713
9999cc6
c8bc17f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,398 @@ | ||
.. index:: | ||
single: DependencyInjection; Autowiring | ||
|
||
Defining Services Dependencies Automatically | ||
============================================ | ||
|
||
Autowiring allows to register services in the container with minimal configuration. | ||
It is useful in the field of `Rapid Application Development`_, when designing prototypes | ||
in early stages of large projects. It makes it easy to register a service graph | ||
and eases refactoring. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should mention what autowiring is here. E.g. start the paragraph with "Autowiring automatically resolves the service dependencies, based on the typehint of the constructor." |
||
|
||
Imagine you're building an API to publish statuses on a Twitter feed, obfuscated | ||
with `ROT13`.. (a special case of the Caesar cipher). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd remove the part about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The link is already present. Why do you want to remove the part about the Caesar cipher? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
Start by creating a ROT13 transformer class:: | ||
|
||
// src/AppBundle/Rot13Transformer.php | ||
namespace AppBundle; | ||
|
||
class Rot13Transformer | ||
{ | ||
public function transform($value) | ||
{ | ||
return str_rot13($value); | ||
} | ||
} | ||
|
||
And now a Twitter client using this transformer:: | ||
|
||
// src/AppBundle/TwitterClient.php | ||
namespace AppBundle; | ||
|
||
class TwitterClient | ||
{ | ||
private $rot13Transformer; | ||
|
||
public function __construct(Rot13Transformer $rot13Transformer) | ||
{ | ||
$this->rot13Transformer = $rot13Transformer; | ||
} | ||
|
||
public function tweetInRot13($user, $key, $status) | ||
{ | ||
$transformedStatus = $this->rot13Transformer->transform($status); | ||
|
||
// ... connect to Twitter and send the encoded status | ||
} | ||
} | ||
|
||
The Dependency Injection Component will be able to automatically register the dependencies | ||
of this ``TwitterClient`` class by marking the ``twitter_client`` service as autowired: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe use "when" instead of "by" here? |
||
|
||
.. configuration-block:: | ||
|
||
.. code-block:: yaml | ||
|
||
# app/config/services.yml | ||
services: | ||
twitter_client: | ||
class: AppBundle\TwitterClient | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. single quotes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not needed, it's Yaml... 😞 |
||
autowire: true | ||
|
||
.. code-block:: xml | ||
|
||
<!-- app/config/services.xml --> | ||
<?xml version="1.0" encoding="UTF-8" ?> | ||
<container xmlns="http://symfony.com/schema/dic/services" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> | ||
|
||
<services> | ||
<service id="twitter_client" class="AppBundle\TwitterClient" autowire="true" /> | ||
</services> | ||
</services> | ||
|
||
.. code-block:: php | ||
|
||
use Symfony\Component\DependencyInjection\Definition; | ||
|
||
// ... | ||
$definition = new Definition('AppBundle\TwitterClient'); | ||
$definition->setAutowired(true); | ||
|
||
$container->setDefinition('twitter_client', $definition); | ||
|
||
The autowiring subsystem will detect the dependencies of the ``TwitterClient`` | ||
class by parsing its constructor. For instance it will find here an instance of | ||
a ``Rot13Transformer`` as dependency. If an existing service definition (and only | ||
one – see below) is of the required type, this service will be injected. If it's | ||
not the case (like in this example), the subsystem is smart enough to automatically | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
register a private service for the ``Rot13Transformer`` class and set it as first | ||
argument of the `twitter_client`` service. Again, it can work only if there is one | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing a back tick (`) |
||
class of the given type. If there are several classes of the same type, you must | ||
use an explicit service definition or register a default implementation. | ||
|
||
As you can see, the autowiring feature drastically reduces the amount of configuration | ||
required to define a service. No more arguments section! It also makes it easy | ||
to change the dependencies of the ``TwitterClient`` class: just add or remove typehinted | ||
arguments in the constructor and you are done. There is no need anymore to search | ||
and edit related service definitions. | ||
|
||
Here is a typical controller using the ``twitter_client`` service:: | ||
|
||
// src/AppBundle/Controller/DefaultController.php | ||
namespace AppBundle\Controller; | ||
|
||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; | ||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; | ||
use Symfony\Bundle\FrameworkBundle\Controller\Controller; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; | ||
|
||
class DefaultController extends Controller | ||
{ | ||
/** | ||
* @Route("/tweet") | ||
* @Method("POST") | ||
*/ | ||
public function tweetAction(Request $request) | ||
{ | ||
$user = $request->request->get('user'); | ||
$key = $request->request->get('key'); | ||
$status = $request->request->get('status'); | ||
|
||
if (!$user || !$key || !$status) { | ||
throw new BadRequestHttpException(); | ||
} | ||
|
||
$this->get('twitter_client')->tweetInRot13($user, $key, $status); | ||
|
||
return new Response('OK'); | ||
} | ||
} | ||
|
||
You can give a try to the API with ``curl``:: | ||
|
||
curl -d "user=kevin&key=ABCD&status=Hello" http://localhost:8000/tweet | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should use |
||
|
||
It should return ``OK``. | ||
|
||
Working with Interfaces | ||
----------------------- | ||
|
||
You might also find yourself using abstractions instead of implementations (especially | ||
in grown applications) as it allows to easily replace some dependencies without | ||
modifying the class depending of them. | ||
|
||
To follow this best practice, constructor arguments must be typehinted with interfaces | ||
and not concrete classes. It allows to replace easily the current implementation | ||
if necessary. | ||
|
||
Let's introduce a ``Rot13TransformerInterface``:: | ||
|
||
// src/AppBundle/Rot13TransformerInterface.php | ||
namespace AppBundle; | ||
|
||
interface Rot13TransformerInterface | ||
{ | ||
public function transform($value); | ||
} | ||
|
||
Then edit ``Rot13Transformer`` to make it implementing the new interface:: | ||
|
||
// ... | ||
|
||
class Rot13Transformer implements Rot13TransformerInterface | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
// ... | ||
|
||
|
||
And update ``TwitterClient`` to depend of this new interface:: | ||
|
||
class TwitterClient | ||
{ | ||
// ... | ||
|
||
public function __construct(Rot13TransformerInterface $rot13Transformer) | ||
{ | ||
// ... | ||
} | ||
|
||
// ... | ||
} | ||
|
||
Finally the service definition must be updated because, obviously, the autowiring | ||
subsystem isn't able to find itself the interface implementation to register:: | ||
|
||
.. configuration-block:: | ||
|
||
.. code-block:: yaml | ||
|
||
# app/config/services.yml | ||
services: | ||
rot13_transformer: | ||
class: AppBundle\Rot13Transformer | ||
|
||
twitter_client: | ||
class: AppBundle\TwitterClient | ||
autowire: true | ||
|
||
.. code-block:: xml | ||
|
||
<!-- app/config/services.xml --> | ||
<?xml version="1.0" encoding="UTF-8" ?> | ||
<container xmlns="http://symfony.com/schema/dic/services" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> | ||
|
||
<services> | ||
<service id="rot13_transformer" class="AppBundle\Rot13Transformer" /> | ||
<service id="twitter_client" class="AppBundle\TwitterClient" autowire="true" /> | ||
</services> | ||
</services> | ||
|
||
.. code-block:: php | ||
|
||
use Symfony\Component\DependencyInjection\Definition; | ||
|
||
// ... | ||
$definition1 = new Definition('AppBundle\Rot13Transformer'); | ||
$container->setDefinition('rot13_transformer', $definition1); | ||
|
||
$definition2 = new Definition('AppBundle\TwitterClient'); | ||
$definition2->setAutowired(true); | ||
$container->setDefinition('twitter_client', $definition2); | ||
|
||
The autowiring subsystem detects that the ``rot13_transformer`` service implements | ||
the ``Rot13TransformerInterface`` and injects it automatically. Even when using | ||
interfaces (and you should), building the service graph and refactoring the project | ||
is easier than with standard definitions. | ||
|
||
Dealing with Multiple Implementations of the Same Type | ||
------------------------------------------------------ | ||
|
||
Last but not least, the autowiring feature allows to specify the default implementation | ||
of a given type. Let's introduce a new implementation of the ``Rot13TransformerInterface`` | ||
returning the result of the ROT13 transformation uppercased:: | ||
|
||
// src/AppBundle/UppercaseRot13Transformer.php | ||
namespace AppBundle; | ||
|
||
class UppercaseRot13Transformer implements Rot13TransformerInterface | ||
{ | ||
private $rot13transformer; | ||
|
||
public function __construct(Rot13TransformerInterface $rot13transformer) | ||
{ | ||
$this->rot13transformer = $rot13transformer; | ||
} | ||
|
||
public function transform($value) | ||
{ | ||
return strtoupper($this->rot13transformer->transform($value)); | ||
} | ||
} | ||
|
||
This class is intended to decorate the standard ROT13 transformer (or any other | ||
implementation) and return it uppercased. | ||
|
||
We can now refactor the controller to add another endpoint leveraging this new | ||
transformer:: | ||
|
||
// src/AppBundle/Controller/DefaultController.php | ||
namespace AppBundle\Controller; | ||
|
||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; | ||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; | ||
use Symfony\Bundle\FrameworkBundle\Controller\Controller; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; | ||
|
||
class DefaultController extends Controller | ||
{ | ||
/** | ||
* @Route("/tweet") | ||
* @Method("POST") | ||
*/ | ||
public function tweetAction(Request $request) | ||
{ | ||
return $this->tweet($request, 'twitter_client'); | ||
} | ||
|
||
/** | ||
* @Route("/tweet-uppercase") | ||
* @Method("POST") | ||
*/ | ||
public function tweetUppercaseAction(Request $request) | ||
{ | ||
return $this->tweet($request, 'uppercase_twitter_client'); | ||
} | ||
|
||
private function tweet(Request $request, $service) | ||
{ | ||
$user = $request->request->get('user'); | ||
$key = $request->request->get('key'); | ||
$status = $request->request->get('status'); | ||
|
||
if (!$user || !$key || !$status) { | ||
throw new BadRequestHttpException(); | ||
} | ||
|
||
$this->get($service)->tweetInRot13($user, $key, $status); | ||
|
||
return new Response('OK'); | ||
} | ||
} | ||
|
||
The last step is to update service definitions to register this new implementation | ||
and a Twitter client using it:: | ||
|
||
.. configuration-block:: | ||
|
||
.. code-block:: yaml | ||
|
||
# app/config/services.yml | ||
services: | ||
rot13_transformer: | ||
class: AppBundle\Rot13Transformer | ||
autowiring_types: AppBundle\Rot13TransformerInterface | ||
|
||
twitter_client: | ||
class: AppBundle\TwitterClient | ||
autowire: true | ||
|
||
uppercase_rot13_transformer: | ||
class: AppBundle\UppercaseRot13Transformer | ||
autowire: true | ||
|
||
uppercase_twitter_client: | ||
class: AppBundle\TwitterClient | ||
arguments: [ @uppercase_rot13_transformer ] | ||
|
||
.. code-block:: xml | ||
|
||
<!-- app/config/services.xml --> | ||
<?xml version="1.0" encoding="UTF-8" ?> | ||
<container xmlns="http://symfony.com/schema/dic/services" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> | ||
|
||
<services> | ||
<service id="rot13_transformer" class="AppBundle\Rot13Transformer"> | ||
<autowiring-type>AppBundle\Rot13TransformerInterface</autowiring-type> | ||
</service> | ||
<service id="twitter_client" class="AppBundle\TwitterClient" autowire="true" /> | ||
<service id="uppercase_rot13_transformer" class="AppBundle\UppercaseRot13Transformer" autowire="true" /> | ||
<service id="uppercase_twitter_client" class="AppBundle\TwitterClient"> | ||
<argument type="service" id="uppercase_rot13_transformer" /> | ||
</service> | ||
</services> | ||
</services> | ||
|
||
.. code-block:: php | ||
|
||
use Symfony\Component\DependencyInjection\Reference; | ||
use Symfony\Component\DependencyInjection\Definition; | ||
|
||
// ... | ||
$definition1 = new Definition('AppBundle\Rot13Transformer'); | ||
$definition1->setAutowiringTypes(array('AppBundle\Rot13TransformerInterface')); | ||
$container->setDefinition('rot13_transformer', $definition1); | ||
|
||
$definition2 = new Definition('AppBundle\TwitterClient'); | ||
$definition2->setAutowired(true); | ||
$container->setDefinition('twitter_client', $definition2); | ||
|
||
$definition3 = new Definition('AppBundle\UppercaseRot13Transformer'); | ||
$definition3->setAutowired(true); | ||
$container->setDefinition('uppercase_rot13_transformer', $definition3); | ||
|
||
$definition4 = new Definition('AppBundle\TwitterClient'); | ||
$definition4->addArgument(new Reference('uppercase_rot13_transformer')); | ||
$container->setDefinition('uppercase_twitter_client', $definition4); | ||
|
||
It deserves some explanations. We now have 2 services implementing the ``Rot13TransformerInterface``. | ||
The autowiring subsystem cannot guess which one to use, this leads to errors | ||
like:: | ||
|
||
[Symfony\Component\DependencyInjection\Exception\RuntimeException] | ||
Unable to autowire argument of type "AppBundle\Rot13TransformerInterface" for the service "twitter_client". | ||
|
||
Fortunately, the ``autowiring_types`` key is here to specify which implementation | ||
to use by default. This key can take a list of types if necessary (using a YAML | ||
array). | ||
|
||
Thanks to this setting, the ``rot13_transformer`` service is automatically injected | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. an argument of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
as an argument of the ``uppercase_rot13_transformer`` and ``twitter_client`` services. For | ||
the ``uppercase_twitter_client``, we use a standard service definition to inject | ||
the specific ``uppercase_rot13_transformer`` service. | ||
|
||
As for other RAD features such as the FrameworkBundle controller or annotations, | ||
keep in mind to not use autowiring in public bundles nor in large projects with | ||
complex maintenance needs. | ||
|
||
.. _Rapid Application Development: https://en.wikipedia.org/wiki/Rapid_application_development | ||
.. _ROT13: https://en.wikipedia.org/wiki/ROT13 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
versionadded directive should be added here