Julien Maulny Guilhem N Nicolas Grekas
Contributed by Julien Maulny , Guilhem N and Nicolas Grekas in #32256

Symfony includes plenty of linters, which are commands that validate certain parts of the application (e.g. lint:yaml to check the syntax of all YAML config files; lint:twig to check the syntax of all Twig templates, etc.)

In Symfony 4 we added a new linter called lint:container. As you may have guessed, it checks the services defined in the container. Specifically, it ensures that arguments injected into services match type declarations.

Consider the following class created for a service:

1
2
3
4
5
6
7
8
9
namespace App\SomeNamespace;

class SomeService
{
    public function __construct(int $someProperty = 7)
    {
        // ...
    }
}

If you now try to add the following service configuration:

1
2
3
# config/services.yaml
services:
    App\SomeNamespace\SomeService: ~

You'll see the following error when running the lint:container command:

1
2
Invalid definition for service "App\SomeNamespace\SomeService": argument 1 of
"App\SomeNamespace\SomeService::__construct" accepts "int", "NULL" passed.

The new container linter goes much further and it can detect errors like the following. Consider this class with a variadic method:

1
2
3
4
5
6
7
8
9
namespace App\SomeNamespace;

class SomeService
{
     public function setSomeItems(SomeClass $item, SomeClass ...$items)
    {
        // ...
    }
}

If you use the following service definition:

1
2
3
4
5
6
7
8
9
10
11
12
13
# config/services.yaml
services:
    foo:
        class: App\SomeNamespace\SomeClass
    bar:
        class: App\AnotherNamespace\SomeDifferentClass

    App\SomeNamespace\SomeService:
        calls:
            - method: setSomeItems
              arguments:
                  - '@foo'
                  - '@bar'

You'll see the following error when running the lint:container command:

1
2
3
Invalid definition for service "App\SomeNamespace\SomeService": argument 2 of
"App\SomeNamespace\SomeService::setSomeItems" accepts "App\SomeNamespace\SomeClass",
"App\AnotherNamespace\SomeDifferentClass" passed.

If you are using a continuous integration service, consider adding this new command to the list of linters executed on each build. Check out this link for an example of how we do that for the Symfony Demo application.

Checking the types of all arguments for all services whenever the container is compiled can hurt performance. That's why this type checking is implemented in a compiler pass called CheckTypeDeclarationsPass which is disabled by default and enabled only when executing the lint:container command. If you don't mind the performance loss, enable the compiler pass in your application.

Published in #Living on the edge