diff --git a/best_practices/security.rst b/best_practices/security.rst index 2e4efccf3cb..5252686d7f7 100644 --- a/best_practices/security.rst +++ b/best_practices/security.rst @@ -328,18 +328,9 @@ the same ``getAuthorEmail()`` logic you used above: } } -To enable the security voter in the application, define a new service: - -.. code-block:: yaml - - # app/config/services.yml - services: - # ... - app.post_voter: - class: AppBundle\Security\PostVoter - arguments: ['@security.access.decision_manager'] - public: false - tags: [security.voter] +Your application will :ref:`autoconfigure ` your security +voter and inject a ``AccessDecisionManagerInterface`` instance in it thanks to +:doc:`autowiring `. Now, you can use the voter with the ``@Security`` annotation: diff --git a/form/form_dependencies.rst b/form/form_dependencies.rst index 8389e224f21..cee0dbdf3e0 100644 --- a/form/form_dependencies.rst +++ b/form/form_dependencies.rst @@ -8,9 +8,9 @@ configuration from inside of your form class. To do this, you have 2 options: ---------------------------- The simplest way to pass services or configuration to your form is via form *options*. -Suppose you need to access the ``doctrine.orm.entity_manager`` service so that you -can make a query. First, allow (in fact, require) a new ``entity_manager`` option -to be passed to your form:: +Suppose you need to access the Doctrine entity manager so that you can make a +query. First, allow (in fact, require) a new ``entity_manager`` option to be +passed to your form:: // src/AppBundle/Form/TaskType.php // ... @@ -32,13 +32,17 @@ create your form:: // src/AppBundle/Controller/DefaultController.php use AppBundle\Form\TaskType; + use Doctrine\ORM\EntityManagerInterface; // ... - public function newAction() + public function newAction(EntityManagerInterface $em) { + // or fetch the em via the container + // $em = $this->get('doctrine')->getManager(); + $task = ...; $form = $this->createForm(TaskType::class, $task, array( - 'entity_manager' => $this->get('doctrine.orm.entity_manager') + 'entity_manager' => $em, )); // ... diff --git a/service_container/autowiring.rst b/service_container/autowiring.rst index 92c30a44afe..bcf75fc2268 100644 --- a/service_container/autowiring.rst +++ b/service_container/autowiring.rst @@ -15,7 +15,7 @@ with `ROT13`_ (a special case of the Caesar cipher). Start by creating a ROT13 transformer class:: - namespace Acme; + namespace AppBundle; class Rot13Transformer { @@ -27,7 +27,7 @@ Start by creating a ROT13 transformer class:: And now a Twitter client using this transformer:: - namespace Acme; + namespace AppBundle; class TwitterClient { @@ -47,16 +47,15 @@ And now a Twitter client using this transformer:: } The DependencyInjection component will be able to automatically register -the dependencies of this ``TwitterClient`` class when the ``twitter_client`` -service is marked as autowired: +the dependencies of this ``TwitterClient`` class when its service is marked as +autowired: .. configuration-block:: .. code-block:: yaml services: - twitter_client: - class: Acme\TwitterClient + AppBundle\TwitterClient: autowire: true .. code-block:: xml @@ -67,17 +66,17 @@ service is marked as autowired: xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - + .. code-block:: php - use Acme\TwitterClient; + use AppBundle\TwitterClient; // ... - $container->autowire('twitter_client', TwitterClient::class); + $container->autowire(TwitterClient::class); .. versionadded:: 3.3 @@ -87,13 +86,27 @@ service is marked as autowired: 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 -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 -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. +a ``Rot13Transformer`` as dependency. + +The subsystem will first try to find a service whose id is the required type, so +here it'll search for a service named ``AppBundle\Rot13Transformer``. +It will be considered as the default implementation and will win over any other +services implementing the required type. + +*In case this service does not exist*, the subsystem will detect the types of +all services and check if one - and only one - implements the required type, and +inject it if it's the case. If there are several services of the same type, an +exception will be thrown. You'll have to use an explicit service definition or +register a default implementation by creating a service or an alias whose id is +the required type (as seen above). +Note that this step is deprecated and will no longer be done in 4.0. The +subsystem will directly pass to the third check. + +At last, if no service implements the required type, as it's the case here, the +subsystem is, as long as it's a concrete class, smart enough to automatically +register a private service for it. +Here it'll register a private service for the ``Rot13Transformer`` class and set +it as first argument of the ``twitter_client`` service. 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 @@ -101,10 +114,11 @@ to change the dependencies of the ``TwitterClient`` class: just add or remove ty 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:: +Here is a typical controller using the ``TwitterClient``:: - namespace Acme\Controller; + namespace AppBundle\Controller; + use AppBundle\TwitterClient; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Symfony\Bundle\FrameworkBundle\Controller\Controller; @@ -118,7 +132,7 @@ Here is a typical controller using the ``twitter_client`` service:: * @Route("/tweet") * @Method("POST") */ - public function tweetAction(Request $request) + public function tweetAction(Request $request, TwitterClient $twitterClient) { $user = $request->request->get('user'); $key = $request->request->get('key'); @@ -128,7 +142,10 @@ Here is a typical controller using the ``twitter_client`` service:: throw new BadRequestHttpException(); } - $this->get('twitter_client')->tweet($user, $key, $status); + $twitterClient->tweet($user, $key, $status); + + // or using the container + // $this->container->get(TwitterClient::class)->tweet($user, $key, $status); return new Response('OK'); } @@ -154,7 +171,7 @@ and not concrete classes. It allows to replace easily the current implementation if necessary. It also allows to use other transformers. You can create a ``TransformerInterface`` containing just one method (``transform()``):: - namespace Acme; + namespace AppBundle; interface TransformerInterface { @@ -182,18 +199,22 @@ And update ``TwitterClient`` to depend of this new interface:: } Finally the service definition must be updated because, obviously, the autowiring -subsystem isn't able to find itself the interface implementation to register: +subsystem isn't able to find itself the interface implementation to register. +You have to indicate which service must be injected for your interface when +using autowiring: .. configuration-block:: .. code-block:: yaml services: - rot13_transformer: - class: Acme\Rot13Transformer + AppBundle\Rot13Transformer: ~ + + # the ``AppBundle\Rot13Transformer`` service will be injected when + # a ``AppBundle\TransformerInterface`` type-hint is detected + AppBundle\TransformerInterface: '@AppBundle\Rot13Transformer' - twitter_client: - class: Acme\TwitterClient + AppBundle\TwitterClient: autowire: true .. code-block:: xml @@ -204,35 +225,47 @@ subsystem isn't able to find itself the interface implementation to register: xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - + + + - + .. code-block:: php - use Acme\TwitterClient; + use AppBundle\Rot13Transformer; + use AppBundle\TransformerInterface; + use AppBundle\TwitterClient; // ... - $container->register('rot13_transformer', 'Acme\Rot13Transformer'); - $container->autowire('twitter_client', TwitterClient::class); + $container->register(Rot13Transformer::class); + $container->setAlias(TransformerInterface::class, Rot13Transformer::class); -The autowiring subsystem detects that the ``rot13_transformer`` service implements -the ``TransformerInterface`` 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. + $container->autowire(TwitterClient::class); + +Thanks to the ``AppBundle\TransformerInterface`` alias, the autowiring subsystem +knows that the ``AppBundle\Rot13Transformer`` service must be injected when +dealing with the ``TransformerInterface`` 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. .. _service-autowiring-alias: 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 ``TransformerInterface`` -returning the result of the ROT13 transformation uppercased:: +To deal with multiple implementations of the same type, the manipulation is the +same as when dealing with an interface. You have to register a service whose id +is your type: this will indicate to the autowiring subsystem which service to +use by default for this type. +So if you have several services implementing the same type, you can decide which +one the subsystem should use. Let's introduce a new implementation of the +``TransformerInterface`` returning the result of the ROT13 transformation +uppercased:: - namespace Acme; + namespace AppBundle; class UppercaseTransformer implements TransformerInterface { @@ -254,8 +287,9 @@ This class is intended to decorate any transformer and return its value uppercas The controller can now be refactored to add a new endpoint using this uppercase transformer:: - namespace Acme\Controller; + namespace AppBundle\Controller; + use AppBundle\TwitterClient; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Symfony\Bundle\FrameworkBundle\Controller\Controller; @@ -269,9 +303,12 @@ transformer:: * @Route("/tweet") * @Method("POST") */ - public function tweetAction(Request $request) + public function tweetAction(Request $request, TwitterClient $twitterClient) { - return $this->tweet($request, 'twitter_client'); + // Here the client is automatically injected because it's the default + // implementation + + return $this->tweet($request, $twitterClient); } /** @@ -280,10 +317,13 @@ transformer:: */ public function tweetUppercaseAction(Request $request) { - return $this->tweet($request, 'uppercase_twitter_client'); + // not the default implementation + $twitterClient = $this->get('uppercase_twitter_client'); + + return $this->tweet($request, $twitterClient); } - private function tweet(Request $request, $service) + private function tweet(Request $request, TwitterClient $twitterClient) { $user = $request->request->get('user'); $key = $request->request->get('key'); @@ -293,7 +333,7 @@ transformer:: throw new BadRequestHttpException(); } - $this->get($service)->tweet($user, $key, $status); + $twitterClient->tweet($user, $key, $status); return new Response('OK'); } @@ -307,22 +347,22 @@ and a Twitter client using it: .. code-block:: yaml services: - rot13_transformer: - class: Acme\Rot13Transformer + AppBundle\Rot13Transformer: ~ - Acme\TransformerInterface: '@rot13_transformer' + # the ``AppBundle\Rot13Transformer`` service will *always* be used + # when ``AppBundle\TransformerInterface`` is detected by the + # autowiring subsystem + AppBundle\TransformerInterface: '@AppBundle\Rot13Transformer' - twitter_client: - class: Acme\TwitterClient + AppBundle\TwitterClient: autowire: true - uppercase_transformer: - class: Acme\UppercaseTransformer + AppBundle\UppercaseTransformer: autowire: true uppercase_twitter_client: - class: Acme\TwitterClient - arguments: ['@uppercase_transformer'] + class: AppBundle\TwitterClient + arguments: ['@AppBundle\UppercaseTransformer'] .. code-block:: xml @@ -332,59 +372,58 @@ and a Twitter client using it: xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - + - + - + - + - - + + .. code-block:: php - use Acme\Rot13Transformer; - use Acme\TransformerInterface; - use Acme\TwitterClient; - use Acme\UppercaseTransformer; + use AppBundle\Rot13Transformer; + use AppBundle\TransformerInterface; + use AppBundle\TwitterClient; + use AppBundle\UppercaseTransformer; use Symfony\Component\DependencyInjection\Reference; // ... - $container->register('rot13_transformer', Rot13Transformer::class); - $container->setAlias(TransformerInterface::class, 'rot13_transformer') + $container->register(Rot13Transformer::class); + $container->setAlias(TransformerInterface::class, Rot13Transformer::class); - $container->autowire('twitter_client', TwitterClient::class); - $container->autowire('uppercase_transformer', UppercaseTransformer::class); + $container->autowire(TwitterClient::class); + $container->autowire(UppercaseTransformer::class); $container->register('uppercase_twitter_client', TwitterClient::class) - ->addArgument(new Reference('uppercase_transformer')); + ->addArgument(new Reference(UppercaseTransformer::class)); This deserves some explanations. You now have two services implementing the -``TransformerInterface``. The autowiring subsystem cannot guess which one -to use which leads to errors like this: +``TransformerInterface``. As said earlier, the autowiring subsystem cannot guess +which one to use which leads to errors like this: .. code-block:: text [Symfony\Component\DependencyInjection\Exception\RuntimeException] - Unable to autowire argument of type "Acme\TransformerInterface" for the service "twitter_client". + Unable to autowire argument of type "AppBundle\TransformerInterface" for the service "AppBundle\TwitterClient". -Fortunately, the FQCN alias is here to specify which implementation -to use by default. +Fortunately, the FQCN alias (the ``AppBundle\TransformerInterface`` alias) is +here to specify which implementation to use by default. .. versionadded:: 3.3 Using FQCN aliases to fix autowiring ambiguities is allowed since Symfony 3.3. Prior to version 3.3, you needed to use the ``autowiring_types`` key. -Thanks to this alias, the ``rot13_transformer`` service is automatically injected -as an argument of the ``uppercase_transformer`` and ``twitter_client`` services. For -the ``uppercase_twitter_client``, a standard service definition is used to -inject the specific ``uppercase_transformer`` service. +Thanks to this alias, the ``AppBundle\Rot13Transformer`` service is +automatically injected as an argument of the ``AppBundle\UppercaseTransformer`` +and ``AppBundle\TwitterClient`` services. For the ``uppercase_twitter_client``, +a standard service definition is used to inject the specific +``AppBundle\UppercaseTransformer`` 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