-
- Notifications
You must be signed in to change notification settings - Fork 5.3k
[DX] ADR usage #8153
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
[DX] ADR usage #8153
Changes from 1 commit
c8050b1 0d0d4cc 7359837 885e696 220021e 1fda263 11c77c7 d21feb2 a449220 6d1ed62 a81b625 8444e98 a93fa6c 97bcbac 72c2c74 666a89f c8625fb 0d2022a 582f4bf a237312 cd46501 24416f5 dc6ddc6 f1aad20 16c8472 eca9a0a aa0a207 551aed6 3dacc68 File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| .. index:: | ||
| single: Action Domain Responder approach | ||
| | ||
| How to implement the ADR pattern | ||
| ================================ | ||
| | ||
| In Symfony, you're used to implement the MVC pattern and extending the default 'Controller' | ||
| class, since the 3.3 update, Symfony is capable of using natively the ADR approach. | ||
| ||
| | ||
| Updating your internal logic | ||
| ||
| ---------------------------- | ||
| | ||
| As you saw from earlier example, you must update the services.yml file in order to | ||
| ||
| use the latest features of the DependencyInjection component, this way, here's the updates :: | ||
| | ||
| # ... | ||
| ||
| | ||
| services: | ||
| 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 suggest to add xml and PHP versions too. | ||
| _defaults: | ||
| autowire: true | ||
| autoconfigure: true | ||
| public: false | ||
| | ||
| # Allow to load every actions | ||
| AppBundle\Action\: | ||
| resource: '../../src/AppBundle/Action/' | ||
| public: true | ||
| ||
| | ||
| Once the file is updated, delete your Controller folder and create an Action class using the ADR principles, i.e :: | ||
| | ||
| <?php | ||
| | ||
| namespace AppBundle\Action; | ||
| | ||
| use Twig\Environment; | ||
| use Symfony\Component\Templating\EngineInterface; | ||
| ||
| | ||
| final class HelloAction | ||
| { | ||
| private $renderer; | ||
| | ||
| public function __construct(Environment $render) | ||
Guikingone marked this conversation as resolved. Outdated Show resolved Hide resolved | ||
| { | ||
| $this->render = $render; | ||
| } | ||
| ||
| | ||
| public function __invoke() | ||
| ||
| { | ||
| return new Response($this->render->render('default/index.html.twig')); | ||
| } | ||
| } | ||
| | ||
| .. tip:: | ||
| | ||
| As described in the DependencyInjection doc, you must use the __construct() injection | ||
| approach, this way, your class is easier to update and keep in sync with the framework internal | ||
| ||
| services. | ||
| | ||
| By default, we define the class with the final keyword because this class shouldn't been extended, | ||
| ||
| the logic is pretty simple to understand as you understand the ADR pattern, in fact, the 'Action' | ||
| is linked to a single request and his dependencies are linked to this precise Action. | ||
| | ||
| .. tip:: | ||
| | ||
| By using the final approach and the private visibility (inside the container), our class | ||
| is faster to return and easier to keep out of the framework logic. | ||
| | ||
| | ||
| ||
| Once this is done, you can define the routes like before using multiples approach : | ||
| ||
| | ||
| .. configuration-block:: | ||
| | ||
| .. code-block:: php-annotations | ||
| # src/AppBundle/Action/HelloAction.php | ||
| // ... | ||
| /** | ||
| * @Route("/hello", name="hello") | ||
| */ | ||
| final class HelloAction | ||
| { | ||
| // ... | ||
| } | ||
| .. code-block:: yaml | ||
| # app/config/routing.yml | ||
| hello: | ||
| path: /hello | ||
| defaults: { _controller: AppBundle\Action\HelloAction } | ||
| .. code-block:: xml | ||
| <!-- app/config/routing.xml --> | ||
| <?xml version="1.0" encoding="UTF-8" ?> | ||
| <routes xmlns="http://symfony.com/schema/routing" | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://symfony.com/schema/routing | ||
| http://symfony.com/schema/routing/routing-1.0.xsd"> | ||
| <route id="hello" path="/hello"> | ||
| <default key="_controller">AppBundle\Action\HelloAction</default> | ||
| </route> | ||
| </routes> | ||
| .. code-block:: php | ||
| // app/config/routing.php | ||
| $collection->add('hello', new Route('/hello', array( | ||
| '_controller' => 'HelloAction::class', | ||
| ||
| ))); | ||
| Accessing the request | ||
| --------------------- | ||
| | ||
| As you can imagine, as the logic evolve, your class isn't capable of accessing | ||
| ||
| the request from simple method injection like this :: | ||
| | ||
| <?php | ||
| | ||
| // ... | ||
| | ||
| public function __invoke(Request $request) | ||
| ||
| { | ||
| return new Response($this->render->render('default/index.html.twig')); | ||
| } | ||
| } | ||
| | ||
| Like you can easily imagine, the container is the best option to gain access to ths request, | ||
| ||
| using this approach, a simple update is recommended :: | ||
| | ||
| <?php | ||
| | ||
| namespace AppBundle\Action; | ||
| | ||
| use Twig\Environment; | ||
| use Symfony\Component\HttpFoundation\Request; | ||
| use Symfony\Component\HttpFoundation\RequestStack; | ||
| use Symfony\Component\Templating\EngineInterface; | ||
| ||
| | ||
| final class HelloAction | ||
| { | ||
| private $requestStack; | ||
| | ||
| private $renderer; | ||
| | ||
| public function __construct(RequestStack $requestStack, Environment $render) | ||
| { | ||
| $this->requestStack = $requestStack | ||
| $this->render = $render; | ||
| } | ||
| | ||
| public function __invoke(Request $request) | ||
| { | ||
| return new Response($this->render->render('default/index.html.twig')); | ||
| } | ||
| } | ||
| | ||
| This way, you can easily access to parameters :: | ||
| | ||
| <?php | ||
| | ||
| // ... | ||
| | ||
| public function __construct(RequestStack $requestStack, Environment $render) | ||
| { | ||
| $this->requestStack = $requestStack | ||
| $this->render = $render; | ||
| } | ||
| | ||
| public function __invoke(Request $request) | ||
| { | ||
| $data = $this->requestStack->getCurrentRequest()->get('id'); | ||
| ||
| | ||
| return new Response($this->render->render('default/index.html.twig')); | ||
| } | ||
| } | ||
| | ||
| Final though | ||
| ||
| ------------ | ||
| ||
| | ||
| Keep in mind that this approach can be completely different from what you're used to use, in order to | ||
| keep your code clean and easy to maintain, we recommend to use this approach only if your code is | ||
| decoupled from the internal framework logic (like with Clean Architecture approach) or if you start a new | ||
| project and need to keep the logic linked to your business rules. | ||
| 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 don't understand this last paragraph and the supposed benefits. I don't see how this approach or what you refer to the old one is cleaner or easier. I don't understand why this approach is more decoupled than the other one. I'm not saying this is good or bad, just that from a beginner's perspective, the benefits are not very clear. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -80,96 +80,10 @@ If your controller implements the ``__invoke()`` method - popular with the | |
| Action-Domain-Response (ADR) pattern, you can simply refer to the service id | ||
| (``AppBundle\Controller\HelloController`` or ``app.hello_controller`` for example). | ||
| | ||
| If you want to use the ADR pattern rather than the default controller approach, you can use | ||
| the new features provided by the container like public/private injections and autowiring, | ||
| in order to work, you must update the services.yml file :: | ||
| As this approach is evolving faster and can be easily transposed into Symfony, we recommend | ||
| 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. How is it evolving? 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. At this stage, not so much, like before, there's was an error in the real sense of my words, ADR still young and the "logic" keep evolving with time and usage by the developers, the true meaning was to say that using ADR still a "WIP" in Symfony and as the "logic" of ADR still evolving by the feedbacks of the developers, his implementation inside Symfony can be modified with future evolution of the framework or pattern. In fact, it was possible since Symfony 3.1 when recompiling the container (or like @dunglas do it, by injecting the classes as controllers and mapping the return of each one of them) but using it in production is way easier since 3.3 and 3.4 (and even easier in 4.0) | ||
| you to read the following part : | ||
| ||
| | ||
| # ... | ||
| | ||
| services: | ||
| _defaults: | ||
| autowire: true | ||
| autoconfigure: true | ||
| public: false | ||
| | ||
| # Allow to load every actions | ||
| AppBundle\Action\: | ||
| resource: '../../src/AppBundle/Action/' | ||
| public: true | ||
| | ||
| Once the file is updated, delete your Controller folder and create an Action class using the ADR principles, i.e :: | ||
| | ||
| <?php | ||
| | ||
| namespace AppBundle\Action; | ||
| | ||
| use Twig\Environment; | ||
| use Symfony\Component\Templating\EngineInterface; | ||
| | ||
| final class HelloAction | ||
| { | ||
| private $renderer; | ||
| | ||
| public function __construct(Environment $render) | ||
| { | ||
| $this->render = $render; | ||
| } | ||
| | ||
| public function __invoke() | ||
| { | ||
| return new Response($this->render->render('default/index.html.twig')); | ||
| } | ||
| } | ||
| | ||
| By default, we define the class with the final keyword because this class shouldn't been extended, | ||
| the logic is pretty simple to understand as you understand the ADR pattern, in fact, the 'Action' | ||
| is linked to a single request and his dependencies are linked to this precise Action. | ||
| | ||
| Once this is done, you can define the routes like before using multiples approach : | ||
| | ||
| .. configuration-block:: | ||
| | ||
| .. code-block:: php-annotations | ||
| | ||
| # src/AppBundle/Action/HelloAction.php | ||
| // ... | ||
| | ||
| /** | ||
| * @Route("/hello", name="hello") | ||
| */ | ||
| final class HelloAction | ||
| { | ||
| // ... | ||
| } | ||
| | ||
| .. code-block:: yaml | ||
| | ||
| # app/config/routing.yml | ||
| hello: | ||
| path: /hello | ||
| defaults: { _controller: AppBundle\Action\HelloAction } | ||
| | ||
| .. code-block:: xml | ||
| | ||
| <!-- app/config/routing.xml --> | ||
| <?xml version="1.0" encoding="UTF-8" ?> | ||
| <routes xmlns="http://symfony.com/schema/routing" | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://symfony.com/schema/routing | ||
| http://symfony.com/schema/routing/routing-1.0.xsd"> | ||
| | ||
| <route id="hello" path="/hello"> | ||
| <default key="_controller">AppBundle\Action\HelloAction</default> | ||
| </route> | ||
| | ||
| </routes> | ||
| | ||
| .. code-block:: php | ||
| | ||
| // app/config/routing.php | ||
| $collection->add('hello', new Route('/hello', array( | ||
| '_controller' => 'HelloAction::class', | ||
| ))); | ||
| * :doc:`/controller/adr` | ||
| | ||
| Alternatives to base Controller Methods | ||
| --------------------------------------- | ||
| | ||
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.
'Controller'=>