How to refactor to new Dependency Injection features in Symfony 3.3

This post was updated on December 2018
I've released Symplify/Autodiscovery package, that migrates old service.yml configs to autodiscovery format for you. Here is how.

Why do it manually? :)

This May will be released Symfony 3.3 with many DependencyInjection improvements. Each of them is quite nice, but combined together - they are huge jump compare to what we have now.

Today I will show you what code can you drop and how to migrate it.

What is New?

Symfony 3.3+ brings new that will completely change they we register services:

You can click read post/PR in detail, but you take this shortcut to learn them...


"A full code example is worth ten thousand words of explanation."
Stephen P. Thomas


Refactor Service Config in 5 Steps

This is service config in Application in Symfony 3.2 or lower.

We apply all features we can and I always add a small # comment to the code with explanation.

# app/config/services.yml
services:
    some_service:
        class: App\SomeService
        autowire: true

    some_controller:
        class: App\Controller\SomeController
        autowire: true

    first_repository:
        class: App\Repository\FirstRepository
        autowire: true
        calls:
            - ["setEntityManager", ["@entity_manager"]]
    second_repository:
        class: App\Repository\SecondRepository
        autowire: true
        calls:
            - ["setEntityManager", ["@entity_manager"]]

    first_command:
        class: App\Command\FirstCommand
        autowire: true
        tags:
            - { name: console.command }
    second_command:
        class: App\Command\SecondCommand
        autowire: true
        tags:
            - { name: console.command }

1. Let's add _defaults

# app/config/services.yml
services:
    _defaults:
        autowire: true # all services in this config are now autowired

    some_service:
        class: App\SomeService

    some_controller:
        class: App\Controller\SomeController

    first_repository:
        class: App\Repository\FirstRepository
        calls:
            - ["setEntityManager", ["@entity_manager"]]
    second_repository:
        class: App\Repository\SecondRepository
        calls:
            - ["setEntityManager", ["@entity_manager"]]

    first_command:
        class: App\Command\FirstCommand
        tags:
            - { name: console.command }
    second_command:
        class: App\Command\SecondCommand
        tags:
            - { name: console.command }

2. Use autoconfigure

# app/config/services.yml
services:
    _defaults:
        autowire: true
        autoconfigure: true # all Symfony native tags are now added automatically

    some_service:
        class: App\SomeService

    some_controller:
        class: App\Controller\SomeController

    first_repository:
        class: App\Repository\FirstRepository
        calls:
            - ["setEntityManager", ["@entity_manager"]]
    second_repository:
        class: App\Repository\SecondRepository
        calls:
            - ["setEntityManager", ["@entity_manager"]]

    first_command:
        class: App\Command\FirstCommand
    second_command:
        class: App\Command\SecondCommand

3. Use Class-Named Services

# app/config/services.yml
services:
    _defaults:
        autowire: true
        autoconfigure: true

    App\SomeService: ~ # no more thinking about creative and unique service name

    App\Controller\SomeController: ~

    App\Repository\FirstRepository:
        calls:
            - ["setEntityManager", ["@entity_manager"]]
    App\Repository\SecondRepository:
        calls:
            - ["setEntityManager", ["@entity_manager"]]

    App\Command\FirstCommand: ~
    App\Command\SecondCommand: ~

4. Use PSR-4 based service autodiscovery and registration

# app/config/services.yml
services:
    _defaults:
        autowire: true
        autoconfigure: true

    App\: # no more manual registration of similar groups of services
        resource: '../'

    App\Repository\FirstRepository:
        calls:
            - ["setEntityManager", ["@entity_manager"]]
    App\Repository\SecondRepository:
        calls:
            - ["setEntityManager", ["@entity_manager"]]

5. Use _instanceof...

# app/config/services.yml
services:
    _defaults:
        autowire: true
        autoconfigure: true

    App\:
        resource: '../'

    _instanceof: # clean and explicit dependency injection to abstract services
        App\Repository\AbstractRepository:
            calls:
                - ["setEntityManager", ["@entity_manager"]]

5. ...or Setter Injection

You can even remove the _instanceof with setter injection. First, modify the abstract repository to add the @required annotation to setEntityManager:

<?php

namespace App\Repository;

use Doctrine\ORM\EntityManagerInterface;

abstract class AbstractRepository
{
    /**
     * @var EntityManagerInterface
     */
    private $entityManager;

    /**
     * @required
     */
    public function setEntityManager(EntityManagerInterface $entityManager): void
    {
        $this->entityManager = $entityManager;
    }

    // ...
}

Now, remove the _instanceof in your services.yml:

# app/config/services.yml
services:
    _defaults:
        autowire: true
        autoconfigure: true

    App\:
        resource: '../'

You're awesome! Now you're using all the shiny new Symfony 3.3 Dependency Injection features.


What is your favorite change?


Typo? Fix it, please  and join 56 people who build this website