使用服务配置器来配置服务

3.4 版本
维护中的版本

服务配置器(Service Configurator)是依赖注入容器(DI Container)的一个功能,它提供一个“回调”的机会,使你能够在服务被实例化之后改变其配置。

你可以在另一个服务中指定一个方法、一个PHP函数,或是一个静态方法。原服务的实例被传递到这个回调中,再由配置器做一些它该做的事——即,在服务被创建之后修改配置。

服务配置器可以被用于,举例来说,当你有一个需要进行复杂配置的服务,其配置信息来自于不同的资源/服务时。使用一个外部配置器,你可以令该服务保持不受干扰的可执行性,同时令它与其他那些“用来提供配置信息”的对象保持松藕合。

另一个有趣的使用场景是,当你有多个对象需要共享同一个配置信息,或是在运行时需要以某种相似方式进行配置的时候。

例如,假设你有一个程序是用来发送不同类型的邮件到用户处。邮件被发送时是有不同格式的,控制格式的开启和关闭是依靠一些动态的程序设定。你可以先定义NewsletterManager类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class NewsletterManager implements EmailFormatterAwareInterface
{
    protected $mailer;
    protected $enabledFormatters;
 
    public function setMailer(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }
 
    public function setEnabledFormatters(array $enabledFormatters)
    {
        $this->enabledFormatters = $enabledFormatters;
    }
 
    // ...
}

然后是另一个GreetingCardManager类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class GreetingCardManager implements EmailFormatterAwareInterface
{
    protected $mailer;
    protected $enabledFormatters;
 
    public function setMailer(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }
 
    public function setEnabledFormatters(array $enabledFormatters)
    {
        $this->enabledFormatters = $enabledFormatters;
    }
 
    // ...
}

如前述,我们的目标是要在运行时根据程序的设置来设定好格式。为了实现这个,你要有一个EmailFormatterManager类,用于负责加载并验证已经在程序中开启的格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class EmailFormatterManager
{
    protected $enabledFormatters;
 
    public function loadFormatters()
    {
        // code to configure which formatters to use
        // 配置哪些formatter可用的代码
        $enabledFormatters = array(...);
        // ...
 
        $this->enabledFormatters = $enabledFormatters;
    }
 
    public function getEnabledFormatters()
    {
        return $this->enabledFormatters;
    }
 
    // ...
}

如果你的目标是避免NewsletterManagerGreetingCardManager,同EmailFormatterManager发生藕合,你应该创建一个配置器类,来配置这些类的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class EmailConfigurator
{
    private $formatterManager;
 
    public function __construct(EmailFormatterManager $formatterManager)
    {
        $this->formatterManager = $formatterManager;
    }
 
    public function configure(EmailFormatterAwareInterface $emailManager)
    {
        $emailManager->setEnabledFormatters(
            $this->formatterManager->getEnabledFormatters()
        );
    }
 
    // ...
}

EmailConfigurator类的工作就是注入已经开启的格式过滤器到NewsletterManagerGreetingCardManager中,因为后二者并不关心“被开启的格式过滤器从何而来”。换句话说,本着“单一职责”的原则,EmailFormatterManager才是拥有“获知已开启的格式”和“如何加载这些格式”等相关信息的类。

配置器的服务配置 

前面提到的类,它们的服务配置可能是下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
services:
    my_mailer:
        # ...

    email_formatter_manager:
        class:     EmailFormatterManager
        # ...

    email_configurator:
        class:     EmailConfigurator
        arguments: ['@email_formatter_manager']
        # ...

    newsletter_manager:
        class:     NewsletterManager
        calls:
            - [setMailer, ['@my_mailer']]
        configurator: ['@email_configurator', configure]

    greeting_card_manager:
        class:     GreetingCardManager
        calls:
            - [setMailer, ['@my_mailer']]
        configurator: ['@email_configurator', configure]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?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 http://symfony.com/schema/dic/services/services-1.0.xsd">
 
    <services>
        <service id="my_mailer">
            <!-- ... -->
        </service>
 
        <service id="email_formatter_manager" class="EmailFormatterManager">
            <!-- ... -->
        </service>
 
        <service id="email_configurator" class="EmailConfigurator">
            <argument type="service" id="email_formatter_manager" />
            <!-- ... -->
        </service>
 
        <service id="newsletter_manager" class="NewsletterManager">
            <call method="setMailer">
                <argument type="service" id="my_mailer" />
            </call>
            <configurator service="email_configurator" method="configure" />
        </service>
 
        <service id="greeting_card_manager" class="GreetingCardManager">
            <call method="setMailer">
                <argument type="service" id="my_mailer" />
            </call>
            <configurator service="email_configurator" method="configure" />
        </service>
    </services>
</container>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
 
// ...
$container->setDefinition('my_mailer', ...);
$container->setDefinition('email_formatter_manager', new Definition(
    'EmailFormatterManager'
));
$container->setDefinition('email_configurator', new Definition(
    'EmailConfigurator'
));
$container->setDefinition('newsletter_manager', new Definition(
    'NewsletterManager'
))->addMethodCall('setMailer', array(
    new Reference('my_mailer'),
))->setConfigurator(array(
    new Reference('email_configurator'),
    'configure',
)));
$container->setDefinition('greeting_card_manager', new Definition(
    'GreetingCardManager'
))->addMethodCall('setMailer', array(
    new Reference('my_mailer'),
))->setConfigurator(array(
    new Reference('email_configurator'),
    'configure',
)));

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)