Rectify: Turn All Action Injects to Constructor Injection in Your Symfony Application

This post was updated at August 2020 with fresh know-how.
What is new?

Updated Rector YAML to PHP configuration, as current standard.

Action Injections are much fun, but it can turn your project to legacy very fast. How to refactor out of the legacy back to constructor injection and still keep that smile on your face?

I wrote about How to Slowly Turn your Symfony Project to Legacy with Action Injection a few weeks ago. It surprised me that the approach had mostly positive feedback:

Couldn't agree more with pretty much everything said! Action injection makes it really confusing on whether an object is treated stateful or stateless (very grey area with the Session for example).
Iltar van der Berg
I'm a Symfony trainer and I'm told to teach people how to use Symfony and talk about this injection pattern. Sob.
Alex Rock
I'm working on a Project that uses action injection pattern and I hate it. I like autowiring but the whole idea about action injection is broken. And this project is in sf28 do we don't use autowiring. Maintenance and development with this pattern is a total nightmare.

It's natural to try new patterns with an open heart and validate them in practice, but what if you find this way as not ideal and want to go to constructor injection instead?

How would you change all your 50 controllers with action injections...

<?php declare(strict_types=1);

namespace App\Controller;

final class SomeController
    public function detail(int $id, Request $request, ProductRepository $productRepository)
        $product = $productRepository->find($id);
        // ...
} the constructor injection:

<?php declare(strict_types=1);

namespace App\Controller;

final class SomeController
     * @var ProductRepository
    private $productRepository;

    public function __construct(ProductRepository $productRepository)
        $this->productRepository = $productRepository;

    public function detail(int $id, Request $request)
        $product = $this->productRepository->find($id);
        // ...

How to Waste Week in 1 Team

  • 50 controllers, 4 actions per each → 200 services
  • some of them are duplicated
  • identify services, Request objects and Argument Resolver objects
  • code-reviews and discussions that might take up-to 5-10 days
  • and rebase on new merged PRs... well, you have 4-10 hours of team-work wasted ahead of you.

I find the time of my team very precious, don't you? So I Let Rector do the work.

3 Steps to Instant Refactoring of All Controllers

1. Install Rector

composer install rector/rector --dev

2. Prepare Config

Add the action-injection-to-constructor-injection set and configure your Kernel class name.

use Rector\Core\Configuration\Option;
use Rector\Set\ValueObject\SetList;
use Rector\Config\RectorConfig;

return function (RectorConfig $rectorConfig): void {

    $parameters = $rectorConfig->parameters();

    // the default value
    $parameters->set('kernel_class', 'App\Kernel');

3. Run Rector on Your Code

vendor/bin/rector process /app --dry-run

You should see diffs like:

 <?php declare(strict_types=1);

 namespace App\Controller;

 final class SomeController
+    /**
+     * @var ProductRepository
+     */
+    private $productRepository;
+    public function __construct(ProductRepository $productRepository)
+    {
+        $this->productRepository = $productRepository;
+    }
-    public function detail(int $id, Request $request, ProductRepository $productRepository)
+    public function detail(int $id, Request $request)
-        $product = $productRepository->find($id);
+        $product = $this->productRepository->find($id);
         // ...

3. Run It

Are all looking good? Run it:

vendor/bin/rector process /app

Clean Code... Done, but What About Beautiful?

You've probably noticed that code itself is not looking too good. Rector's job is not to clean, but to change the code. It's not a hipster designer, but rather a thermonuclear engineer. That's why there are coding standards. You can apply your own or if not good enough use Rector's prepared set:

composer require symplify/easy-coding-standard --dev
vendor/bin/ecs --config vendor/rector/rector/ecs-after-rector.php --fix

And your code is now both refactored and clean. That's it!

Happy instant refactoring!

Do you learn from my contents or use open-souce packages like Rector every day?
Consider supporting it on GitHub Sponsors. I'd really appreciate it!