{
    "href": "/post/2017/05/16/action-injection-as-a-code-smell/",
    "relId": "2017/05/16/action-injection-as-a-code-smell",
    "title": "\"Action Injection\" As A Code Smell",
    "author": "pmjones",
    "markup": "html",
    "tags": [
        {
            "href": "/tag/adr/",
            "relId": "adr",
            "title": "Action Domain Responder",
            "author": null,
            "created": "2020-08-17 21:07:42 UTC",
            "updated": [
                "2020-08-17 21:07:42 UTC",
                "2020-09-22 15:41:16 UTC",
                "2020-10-14 18:20:29 UTC",
                "2020-10-14 18:36:31 UTC",
                "2020-10-14 18:36:53 UTC",
                "2020-10-14 18:37:08 UTC",
                "2020-10-14 18:37:48 UTC",
                "2020-10-14 18:39:26 UTC",
                "2020-10-14 19:03:17 UTC",
                "2020-10-14 19:03:35 UTC",
                "2020-10-26 18:12:53 UTC"
            ],
            "markup": "markdown"
        },
        {
            "href": "/tag/php/",
            "relId": "php",
            "title": "PHP",
            "author": null,
            "created": null,
            "updated": [],
            "markup": "markdown"
        },
        {
            "href": "/tag/programming/",
            "relId": "programming",
            "title": "Programming",
            "author": null,
            "created": null,
            "updated": [],
            "markup": "markdown"
        }
    ],
    "created": "2017-05-16 12:00:23 UTC",
    "updated": [
        "2017-05-16 12:00:23 UTC"
    ],
    "html": "<p>Circumstance has conspired to put Action Injection discussions in front of me multiple times in the past few days. Having seen the approach several times before, I have come to think that if Action Injection is the answer, you might be asking the wrong question. I find it to be a code smell, one that indicates the system needs refactoring or reorganizing.</p>\n<h3 id=\"action-injection\">\n<a name=\"user-content-action-injection\" href=\"#action-injection\" class=\"headeranchor-link\" aria-hidden=\"true\"><span class=\"headeranchor\"></span></a>Action Injection \u2026</h3>\n<p>As far as I can tell, the term \u201cAction Injection\u201d originates with Alex Meyer-Gleaves in <a href=\"https://alexmg.com/introducing-action-injection-with-autofac-aspnet-mvc-integration/\">a 2010 article on ASP.NET MVC development</a>. He summarizes Action Injection in this way:</p>\n<blockquote>\n<p>Your [controller class] constructor is provided the dependencies [by the DI container] that are shared by all actions in your controller, and each individual action [method] can request any additional dependencies that it needs.</p>\n</blockquote>\n<p>To expand on that, let\u2019s say you have a controller class with several action methods in it. You realize after a while that the different action methods have slightly different dependencies. For example, some of the methods need a logger, while others need a template system, or access to the router. But you don\u2019t want to pollute the controller class constructor with these method-specific dependencies, since those dependencies will be used only if that particular action method gets invoked.</p>\n<p>With Action Injection, when you pull the controller from your dependency injection container and call a particular action method, the DI container will automatically pass the right dependencies for the method call arguments. Voila: now you can define all the common dependencies as parameters on the controller constructor, and all the action-specific dependencies as parameters on the method definition.</p>\n<p>You can see a PHP-specific description of the problem, with Action Injection as the solution, in this Symfony pull request:</p>\n<p><a href=\"https://github.com/symfony/symfony/pull/21771\">https://github.com/symfony/symfony/pull/21771</a></p>\n<p>You can also hear about it in this presentation from Beau Simensen, from around 32:21 to 34:31:</p>\n<p><a href=\"https://www.youtube.com/watch?v=JyrgwMagwEM&amp;feature=youtu.be&amp;t=1941\">https://www.youtube.com/watch?v=JyrgwMagwEM&amp;feature=youtu.be&amp;t=1941</a></p>\n<p>The <a href=\"http://www.yiiframework.com/doc-2.0/guide-concept-di-container.html#method-injection\">Yii</a> and <a href=\"https://mattstauffer.co/blog/laravel-5.0-method-injection\">Laravel</a> containers appear to support this behavior as well, using the term \u201cmethod injection.\u201d (I think that\u2019s a misnomer; the term appears overloaded at best, as sometimes it may mean \u201cmethods called by the DI container at object-creation time\u201d instead of \u201cresolving method arguments at call-time\u201d.) Perhaps other DI containers support Action Injection as well.</p>\n<h3 id=\"as-a-code-smell\">\n<a name=\"user-content-as-a-code-smell\" href=\"#as-a-code-smell\" class=\"headeranchor-link\" aria-hidden=\"true\"><span class=\"headeranchor\"></span></a>\u2026 As A Code Smell</h3>\n<p>The explicit reason for using Action Injection is \u201cto reduce dependencies or overhead\u201d when constructing an object. You don\u2019t want to have to pass in a dozen dependencies, when only three are used in every method, and the others are used only in specific methods.</p>\n<p>But the fact that your controller has so many dependencies, used only in some cases and not in others, should be an indicator that the class is doing too much.  Indeed, it\u2019s doing so much that you cannot call its action methods directly; you have to use the dependency injection container <em>not only</em> to build the controller object  <em>but also</em> to invoke its action methods.</p>\n<h3 id=\"reorganizing-to-avoid-action-injection\">\n<a name=\"user-content-reorganizing-to-avoid-action-injection\" href=\"#reorganizing-to-avoid-action-injection\" class=\"headeranchor-link\" aria-hidden=\"true\"><span class=\"headeranchor\"></span></a>Reorganizing To Avoid Action Injection</h3>\n<p>What approaches exist to help you avoid the Action Injection code smell? I assert that the better solution is to change how you organize your controller structures.</p>\n<p>Instead of thinking in terms of \u201ca controller class with action methods,\u201d think in terms of \u201ca controller namespace with action classes.\u201d Actions are the targets for your routes anyway, not controller classes per se, so it makes sense to upgrade actions to \u201cfirst-class\u201d elements of the system. (Think of them as single-action controllers, if you like.)</p>\n<p>Thus, instead of \u2026</p>\n<pre><code>&lt;?php\nnamespace App;\n\nclass BlogController {\n    public function browse() { ... }\n    public function read() { ... }\n    public function edit() { ... }\n    public function add() { ... }\n    public function delete() { ... }\n}\n</code></pre>\n<p>\u2026 reorganize to:</p>\n<pre><code>&lt;?php\nnamespace App\\BlogController;\n\nclass Browse { ... }\nclass Read { ... }\nclass Edit { ... }\nclass Add { ... }\nclass Delete { ... }\n</code></pre>\n<p>Then the DI container can use plain old constructor injection to create the Action object, with all of its particular dependencies. To invoke the Action, call a well-known method with the user input from the route or request. (I like <code>__invoke()</code> but others may prefer <code>exec()</code> or something similar.)</p>\n<p>In fact, I realized only after watching the Simensen clip a second time that his example is a single-action controller in everything but name. His example code was this \u2026</p>\n<pre><code>&lt;?php\nclass Home {\n    /**\n     * @Route(\"/myaction\", name=\"my_action\")\n     */\n    public function myAction(\n        Request $request,\n        Router $router,\n        Twig $twig\n    ) {\n        if (!$request-&gt;isMethod('GET')) {\n            return new RedirectResponse(\n                $router-&gt;generateUrl('my_action'),\n                301\n            );\n        }\n\n        return new Response(\n            $twig-&gt;render('mytemplate.html.twig')\n        );\n    }\n}\n</code></pre>\n<p>\u2026 but the controller action method parameters might just as well be Action class constructor parameters:</p>\n<pre><code>&lt;?php\nnamespace Home;\n\nclass MyAction {\n\n    public function __construct(\n        Request $request,\n        Router $router,\n        Twig $twig\n    ) {\n        $this-&gt;request = $request;\n        $this-&gt;router = $router;\n        $this-&gt;twig = $twig;\n    }\n\n    /**\n     * @Route(\"/myaction\", name=\"my_action\")\n     */\n    public function __invoke() {\n        if (!$this-&gt;request-&gt;isMethod('GET')) {\n            return new RedirectResponse(\n                $this-&gt;router-&gt;generateUrl('my_action'),\n                301\n            );\n        }\n\n        return new Response(\n            $this-&gt;twig-&gt;render('mytemplate.html.twig')\n        );\n    }\n}\n</code></pre>\n<p>(UPDATE: That example code looks like it originates from <a href=\"https://dunglas.fr/2016/01/dunglasactionbundle-symfony-controllers-redesigned/\">Kevin Dunglas</a> and his <a href=\"https://github.com/dunglas/DunglasActionBundle\">DunglasActionBundle</a> -- which is itself a single-action controller implementation for Symfony.)</p>\n<h3 id=\"action-domain-responder\">\n<a name=\"user-content-action-domain-responder\" href=\"#action-domain-responder\" class=\"headeranchor-link\" aria-hidden=\"true\"><span class=\"headeranchor\"></span></a>Action Domain Responder</h3>\n<p>For more on this organizational structure, please read my <a href=\"http://pmjones.io/adr\">Action Domain Responder</a> offering. ADR is a refinement of MVC that is tuned specifically to server-side request/response over-the-network interactions.</p>\n<p>But you need not go full ADR to avoid Action Injection. Just using single-action controllers will do the trick. Then you can have well-factored single-responsibility controller classes that do not require a DI container in order to call their action methods, and Action Injection becomes a thing of the past.</p>\n<p class=\"reddit-links\">Read the Reddit discussion about this post <a href=\"https://www.reddit.com/r/PHP/comments/6bhbb7/action_injection_as_a_code_smell/\">here</a>.</p>\n"
}
