Service Method Calls and Setter Injection

Service Method Calls and Setter Injection

Tip

If you’re using autowiring, you can use @required to automatically configure method calls.

Usually, you’ll want to inject your dependencies via the constructor. But sometimes, especially if a dependency is optional, you may want to use “setter injection”. For example:

  1. // src/Service/MessageGenerator.php
  2. namespace App\Service;
  3. use Psr\Log\LoggerInterface;
  4. class MessageGenerator
  5. {
  6. private $logger;
  7. public function setLogger(LoggerInterface $logger): void
  8. {
  9. $this->logger = $logger;
  10. }
  11. // ...
  12. }

To configure the container to call the setLogger method, use the calls key:

  • YAML

    1. # config/services.yaml
    2. services:
    3. App\Service\MessageGenerator:
    4. # ...
    5. calls:
    6. - setLogger: ['@logger']
  • XML

    1. <!-- config/services.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/services"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/dic/services
    6. https://symfony.com/schema/dic/services/services-1.0.xsd">
    7. <services>
    8. <service id="App\Service\MessageGenerator">
    9. <!-- ... -->
    10. <call method="setLogger">
    11. <argument type="service" id="logger"/>
    12. </call>
    13. </service>
    14. </services>
    15. </container>
  • PHP

    1. // config/services.php
    2. namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    3. use App\Service\MessageGenerator;
    4. return function(ContainerConfigurator $configurator) {
    5. // ...
    6. $services->set(MessageGenerator::class)
    7. ->call('setLogger', [ref('logger')]);
    8. };

New in version 4.3: The immutable-setter injection was introduced in Symfony 4.3.

To provide immutable services, some classes implement immutable setters. Such setters return a new instance of the configured class instead of mutating the object they were called on:

  1. // src/Service/MessageGenerator.php
  2. namespace App\Service;
  3. use Psr\Log\LoggerInterface;
  4. class MessageGenerator
  5. {
  6. private $logger;
  7. /**
  8. * @return static
  9. */
  10. public function withLogger(LoggerInterface $logger): self
  11. {
  12. $new = clone $this;
  13. $new->logger = $logger;
  14. return $new;
  15. }
  16. // ...
  17. }

Because the method returns a separate cloned instance, configuring such a service means using the return value of the wither method ($service = $service->withLogger($logger);). The configuration to tell the container it should do so would be like:

  • YAML

    1. # config/services.yaml
    2. services:
    3. App\Service\MessageGenerator:
    4. # ...
    5. calls:
    6. - withLogger: !returns_clone ['@logger']
  • XML

    1. <!-- config/services.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/services"
    4. xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/dic/services
    6. https://symfony.com/schema/dic/services/services-1.0.xsd">
    7. <services>
    8. <service id="App\Service\MessageGenerator">
    9. <!-- ... -->
    10. <call method="withLogger" returns-clone="true">
    11. <argument type="service" id="logger"/>
    12. </call>
    13. </service>
    14. </services>
    15. </container>
  • PHP

    1. // config/services.php
    2. use App\Service\MessageGenerator;
    3. use Symfony\Component\DependencyInjection\Reference;
    4. $container->register(MessageGenerator::class)
    5. ->addMethodCall('withLogger', [new Reference('logger')], true);

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.