Allowed to define the priority of service decoration

Diego Saint Esteben
Contributed by Diego Saint Esteben in #15416

Service decoration is a powerful way to replace some service without actually removing it from the container. This way the new service can make use of the replaced service.

In Symfony 2.8, when more than one service decorate another one, you can define the priority of each decorator to have a more precise control over them. The new option is called decoration_priority, its default value is 0 and the higher its value, the earlier the decorator is applied:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
services:
    app.service1:
        class: AppBundle\Service\Service1

    app.service2:
        class: AppBundle\Service\Service2
        arguments: ['@app.service2.inner']
        decorates: app.service1
        public: false

    app.service3:
        class: AppBundle\Service\Service3
        arguments: ['@app.service3.inner']
        decorates: app.service1
        decoration_priority: 1
        public: false

In the above example, app.service3 is applied first because it defines a priority of 1 and the priority for app.service2 is 0 (the default value). Therefore, this config is equivalent to the following PHP code:

1
$this->services['app.service1'] = new Service2(new Service3(new Service1())));

Added logging of unused tags

Florian Pfitzer
Contributed by Florian Pfitzer in #15963

In Symfony 2.8, when the configuration of a service adds a tag which is not used in the application, a message like this one is logged:

1
2
Tag "this_tag_is_not_used" was defined on service "app.service1" but
was never used.

All the built-in Symfony service tags (such as console.command and twig.extension) have been whitelisted to avoid polluting your log files with useless messages.

Moreover, if the name of the unused tag is similar to any of the other tags, the log message will add a "Did you mean" section, because this is likely to be a typo in your configuration.

For example, when using the following configuration:

1
2
3
4
5
services:
    app.service1:
        class: AppBundle\Service\Service1
        tags:
            -  { name: 'kenrel.event_listener', ... }

You'll see the following log message:

1
2
Tag "kenrel.event_listener" was defined on service "app.service1", but
was never used. Did you mean "kernel.event_listener"?

Implemented resettable containers

Christophe Coevoet
Contributed by Christophe Coevoet in #15185

Symfony 2.8 includes a new ResettableContainerInterface which defines a single method called reset(). This method is useful to release memory when the container is no longer being used.

Symfony's Kernel class calls this method during the shutdown process:

1
2
3
4
5
6
7
8
9
10
// src/Symfony/Component/HttpKernel/Kernel.php
// ...
public function shutdown()
{
    // ...

    if ($this->container instanceof ResettableContainerInterface) {
        $this->container->reset();
    }
}

Symfony's service container implements this interface to remove references to all services during shutdown, improving the odds of destructing services and the container through refcounting rather than waiting for PHP's garbage collector:

1
2
3
4
5
6
7
8
// src/Symfony/Component/DependencyInjection/Container.php
// ...
public function reset()
{
    // ...

    $this->services = array();
}

Simplified Registering of Compiler Passes

Wouter De Jong
Contributed by Wouter De Jong in #13761

Compiler passes allow to modify the service container while Symfony is compiling it and before being used by the application. In Symfony 2.7 and previous versions, you needed to register those extensions as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace AppBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use AppBundle\DependencyInjection\Compiler\CustomPass;

class AppBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        parent::build($container);
        $container->addCompilerPass(new CustomPass());
    }
}

In Symfony 2.8, this code is no longer needed because if your extensions implement CompilerPassInterface they will be automatically registered and the process() method will be executed during the container compilation.

Published in #Living on the edge