New in Symfony 4.3: Configuring services with immutable setters

Contributed by
Nicolas Grekas
in #30212.

A common need in some Symfony applications is to use immutable services while still using traits for composing their optional features. Although the Symfony service container supports setter injection, they have some drawbacks (e.g. setters can be called more than just at the time of construction so you cannot be sure the dependency is not replaced during the lifetime of the object).

A pattern to solve this problem are "wither methods". These methods typically use the with word as the prefix of their names (e.g. withPropertyName()) and they return a copy of an instance of an immutable class with only that one property changed:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyService
{
    use LoggerAwareTrait;

    // ...
}

trait LoggerAwareTrait
{
    private $logger;

    public function withLogger(LoggerInterface $logger)
    {
        $new = clone $this;
        $new->logger = $logger;

        return $new;
    }
}

$service = new MyService();
$service = $service->withLogger($logger);

This solves the most significant problems of the setter injections. That's why in Symfony 4.3 we added support for "wither methods" in the service container.

When using YAML to configure services, pass true as the third optional parameter of the method call:

1
2
3
4
5
6
7
# config/services.yaml
services:
    MyService:
        # ...
        calls:
            # the TRUE argument turns this into a wither method
            - ['withLogger', ['@logger'], true]

When using XML to configure services, set the returns-clone option to true in the method call:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<!-- config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        https://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="MyService">
            <!-- ... -->
            <call method="withLogger" returns-clone="true">
                <argument type="service" id="logger"/>
            </call>
        </service>
    </services>
</container>

When using service autowiring to configure services, add the @return static PHPdoc annotation to the wither methods:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class MyService
{
    // ...

    /**
     * @required
     * @return static
     */
    public function withLogger(LoggerInterface $logger)
    {
        // ...
    }
}

New in Symfony 4.3: Configuring services with immutable setters symfony.com/blog/new-in-symfony-4-3-configuring-services-with-immutable-setters

Tweet this

Comments

super...
> When using service autowiring to configure services, add the @return
static PHPdoc annotation to the wither methods

I think based on the example you meant `@required`
> When using service autowiring to configure services, add the @return
static PHPdoc annotation to the wither methods

Can this be done with return type?:
public function withLogger(LoggerInterface $logger): self
> Can this be done with return type?:

returning "self" is not enough: this would be inaccurate for child classes. The correct return type would be "static" - but that's not allowed in the language, that's why it has to be in the annotation.

Comments are closed.

To ensure that comments stay relevant, they are closed for old posts.