From 1f6edd4ab8f66ffbc9216c58e4a1e5ba5da94ea5 Mon Sep 17 00:00:00 2001 From: Guilhem Niot Date: Wed, 10 May 2017 18:34:31 +0200 Subject: [PATCH 1/2] Rework more controllers --- form/form_dependencies.rst | 14 ++-- service_container/autowiring.rst | 136 ++++++++++++++++--------------- 2 files changed, 78 insertions(+), 72 deletions(-) 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..02f984a3991 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 @@ -101,10 +100,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 +118,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 +128,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 +157,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 { @@ -189,11 +192,9 @@ subsystem isn't able to find itself the interface implementation to register: .. code-block:: yaml services: - rot13_transformer: - class: Acme\Rot13Transformer + AppBundle\Rot13Transformer: ~ - twitter_client: - class: Acme\TwitterClient + AppBundle\TwitterClient: autowire: true .. code-block:: xml @@ -204,24 +205,25 @@ 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\TwitterClient; // ... - $container->register('rot13_transformer', 'Acme\Rot13Transformer'); - $container->autowire('twitter_client', TwitterClient::class); + $container->register(Rot13Transformer::class); + $container->autowire(TwitterClient::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. +The autowiring subsystem detects that the ``AppBundle\Rot13Transformer`` 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. .. _service-autowiring-alias: @@ -232,7 +234,7 @@ Last but not least, the autowiring feature allows to specify the default impleme of a given type. 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 +256,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 +272,9 @@ transformer:: * @Route("/tweet") * @Method("POST") */ - public function tweetAction(Request $request) + public function tweetAction(Request $request, TwitterClient $twitterClient) { - return $this->tweet($request, 'twitter_client'); + return $this->tweet($request, $twitterClient); } /** @@ -280,10 +283,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 +299,7 @@ transformer:: throw new BadRequestHttpException(); } - $this->get($service)->tweet($user, $key, $status); + $twitterClient->tweet($user, $key, $status); return new Response('OK'); } @@ -307,22 +313,19 @@ and a Twitter client using it: .. code-block:: yaml services: - rot13_transformer: - class: Acme\Rot13Transformer + AppBundle\Rot13Transformer: ~ - Acme\TransformerInterface: '@rot13_transformer' + 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,38 +335,36 @@ 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 @@ -372,19 +373,20 @@ 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 From 3b751477c014d9453605a3fee4653d56ccf0a435 Mon Sep 17 00:00:00 2001 From: Guilhem Niot Date: Sat, 13 May 2017 19:44:23 +0200 Subject: [PATCH 2/2] update --- best_practices/security.rst | 15 ++----- service_container/autowiring.rst | 73 ++++++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 30 deletions(-) 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/service_container/autowiring.rst b/service_container/autowiring.rst index 02f984a3991..bcf75fc2268 100644 --- a/service_container/autowiring.rst +++ b/service_container/autowiring.rst @@ -86,13 +86,27 @@ 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 @@ -185,7 +199,9 @@ 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:: @@ -194,6 +210,10 @@ subsystem isn't able to find itself the interface implementation to register: services: AppBundle\Rot13Transformer: ~ + # the ``AppBundle\Rot13Transformer`` service will be injected when + # a ``AppBundle\TransformerInterface`` type-hint is detected + AppBundle\TransformerInterface: '@AppBundle\Rot13Transformer' + AppBundle\TwitterClient: autowire: true @@ -207,6 +227,8 @@ subsystem isn't able to find itself the interface implementation to register: + + @@ -214,25 +236,34 @@ subsystem isn't able to find itself the interface implementation to register: .. code-block:: php use AppBundle\Rot13Transformer; + use AppBundle\TransformerInterface; use AppBundle\TwitterClient; // ... $container->register(Rot13Transformer::class); + $container->setAlias(TransformerInterface::class, Rot13Transformer::class); + $container->autowire(TwitterClient::class); -The autowiring subsystem detects that the ``AppBundle\Rot13Transformer`` 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. +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 AppBundle; @@ -274,6 +305,9 @@ transformer:: */ public function tweetAction(Request $request, TwitterClient $twitterClient) { + // Here the client is automatically injected because it's the default + // implementation + return $this->tweet($request, $twitterClient); } @@ -315,6 +349,9 @@ and a Twitter client using it: services: AppBundle\Rot13Transformer: ~ + # the ``AppBundle\Rot13Transformer`` service will *always* be used + # when ``AppBundle\TransformerInterface`` is detected by the + # autowiring subsystem AppBundle\TransformerInterface: '@AppBundle\Rot13Transformer' AppBundle\TwitterClient: @@ -359,7 +396,7 @@ and a Twitter client using it: // ... $container->register(Rot13Transformer::class); - $container->setAlias(TransformerInterface::class, Rot13Transformer::class) + $container->setAlias(TransformerInterface::class, Rot13Transformer::class); $container->autowire(TwitterClient::class); $container->autowire(UppercaseTransformer::class); @@ -367,8 +404,8 @@ and a Twitter client using it: ->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