Nicolas Grekas
Contributed by Nicolas Grekas in #26499

In Symfony 3.4 we made all services private by default meaning that you cannot longer call $this->get('my_service_id') in your controllers to quickly get some service.

We made this change because using the service container directly is not considered a good practice: it hides the dependencies of your classes, making them coupled to external configuration, thus harder to test and to review.

Whenever we remove a feature like that, we provide an alternative that is considered better and, if possible, as simple to use as the previous one. That's why controllers allow injecting services with type hints in their action methods and their constructors.

The only remaining drawback of "private services by default" is that testing was harder than before. Some developers even defined some config in the test environment to make all services public in tests. In Symfony 4.1, we did the same and now tests allow fetching private services by default.

In practice, tests based on WebTestCase and KernelTestCase now access to a special container via the static::$container property that allows fetching non-removed private services:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\Console\Tester\CommandTester;

class AddUserCommandTest extends WebTestCase
{
    private function assertUserCreated()
    {
        self::bootKernel();

        // returns the real and unchanged service container
        $container = self::$kernel->getContainer();

        // gets the special container that allows fetching private services
        $container = self::$container;

        $user = self::$container->get('doctrine')->getRepository(User::class)->findOneByEmail('...');
        $this->assertTrue(self::$container->get('security.password_encoder')->isPasswordValid($user, '...');
        // ...
}

Keep in mind that, because of how Symfony's service container work, unused services are removed from the container. This means that if you have a private service not used by any other service, Symfony will remove it and you won't be able to get it as explained in this article. The solution is to define the service as public explicitly so Symfony doesn't remove it.

Published in #Living on the edge