利用父服务来管理常规依赖
当你向程序中添加更多功能时,就有可能产生一些相关联的类,这些类共享着某些相同的依赖。例如你有一个Newsletter Manager,它使用setter注入来获取依赖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class NewsletterManager
{
protected $mailer;
protected $emailFormatter;
public function setMailer(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function setEmailFormatter(EmailFormatter $emailFormatter)
{
$this->emailFormatter = $emailFormatter;
}
// ...
} |
然后是一个Greeting Card类,与上面的代码共享了相同的依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class GreetingCardManager
{
protected $mailer;
protected $emailFormatter;
public function setMailer(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function setEmailFormatter(EmailFormatter $emailFormatter)
{
$this->emailFormatter = $emailFormatter;
}
// ...
} |
这两个类的服务配置,可能是下面这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
services:
my_mailer:
# ...
my_email_formatter:
# ...
newsletter_manager:
class: NewsletterManager
calls:
- [setMailer, ['@my_mailer']]
- [setEmailFormatter, ['@my_email_formatter']]
greeting_card_manager:
class: GreetingCardManager
calls:
- [setMailer, ['@my_mailer']]
- [setEmailFormatter, ['@my_email_formatter']] |
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="my_email_formatter">
<!-- ... -->
</service>
<service id="newsletter_manager" class="NewsletterManager">
<call method="setMailer">
<argument type="service" id="my_mailer" />
</call>
<call method="setEmailFormatter">
<argument type="service" id="my_email_formatter" />
</call>
</service>
<service id="greeting_card_manager" class="GreetingCardManager">
<call method="setMailer">
<argument type="service" id="my_mailer" />
</call>
<call method="setEmailFormatter">
<argument type="service" id="my_email_formatter" />
</call>
</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 |
use Symfony\Component\DependencyInjection\Reference;
// ...
$container->register('my_mailer', ...);
$container->register('my_email_formatter', ...);
$container
->register('newsletter_manager', 'NewsletterManager')
->addMethodCall('setMailer', array(
new Reference('my_mailer'),
))
->addMethodCall('setEmailFormatter', array(
new Reference('my_email_formatter'),
))
;
$container
->register('greeting_card_manager', 'GreetingCardManager')
->addMethodCall('setMailer', array(
new Reference('my_mailer'),
))
->addMethodCall('setEmailFormatter', array(
new Reference('my_email_formatter'),
))
; |
无论是类的代码还是配置代码,都存在着大量的重复内容。这意味着当你做出修改时,比如,EmailFormatter
的mailer
要通过构造器来注入,你需要更新配置文件中的两个地方。如同你在setter方法中做出修改时,你要在两个类中进行操作。对于关联类中的共性方法,一个典型处理方式是,把这些共性内容剥离到超类中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
abstract class MailManager
{
protected $mailer;
protected $emailFormatter;
public function setMailer(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function setEmailFormatter(EmailFormatter $emailFormatter)
{
$this->emailFormatter = $emailFormatter;
}
// ...
} |
这样一来,NewsletterManager
和GrettingCardManager
可以继承超类:
1 2 3 4 |
class NewsletterManager extends MailManager
{
// ...
} |
以及:
1 2 3 4 |
class GreetingCardManager extends MailManager
{
// ...
} |
类似的,Symfony服务容器也支持在配置文件中实现“服务继承”,这样你就可以减少冗余代码,通过指定一个父服务来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# ...
services:
# ...
mail_manager:
abstract: true
calls:
- [setMailer, ['@my_mailer']]
- [setEmailFormatter, ['@my_email_formatter']]
newsletter_manager:
class: NewsletterManager
parent: mail_manager
greeting_card_manager:
class: GreetingCardManager
parent: mail_manager |
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 |
<?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="mail_manager" abstract="true">
<call method="setMailer">
<argument type="service" id="my_mailer" />
</call>
<call method="setEmailFormatter">
<argument type="service" id="my_email_formatter" />
</call>
</service>
<service
id="newsletter_manager"
class="NewsletterManager"
parent="mail_manager" />
<service
id="greeting_card_manager"
class="GreetingCardManager"
parent="mail_manager" />
</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 |
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Reference;
// ...
$mailManager = new Definition();
$mailManager
->setAbstract(true);
->addMethodCall('setMailer', array(
new Reference('my_mailer'),
))
->addMethodCall('setEmailFormatter', array(
new Reference('my_email_formatter'),
))
;
$container->setDefinition('mail_manager', $mailManager);
$newsletterManager = new DefinitionDecorator('mail_manager');
$newsletterManager->setClass('NewsletterManager');
$container->setDefinition('newsletter_manager', $newsletterManager);
$greetingCardManager = new DefinitionDecorator('mail_manager');
$greetingCardManager->setClass('GreetingCardManager');
$container->setDefinition('greeting_card_manager', $greetingCardManager); |
根据上下文,拥有父服务即暗示子服务必须应用父服务的参数和方法等。特别是父服务中定义的setter方法,在子服务被实例化的时候,将会被调用。
如果你去在上述配置代码中去掉parent
键,相关服务仍然会被实例化,并且它们仍然继承了MailerManager类。区别在于,忽略parent
配置键,意味着mail_manager
服务中定义的方法,在子服务实例化的时候,将不再被调用。
scope
、abstract
和tag
属性应始终从子类中剥离出来。
父服务是“抽象”的,表示它不会被从服务容器中直接取出,也不能传给另一个服务。抽象的存在,仅是作为“模板”供其他服务取用。这就是为什么在配置代码中没有class
键的原因,否则的话,会报一个“需要非抽象服务”的异常。
为了使父服务能被解析,ContainerBuilder
必须先被编译。参阅本章前面的编译服务容器小节。
在上面例子中,一些类共享了相同的配置信息,并在PHP中继承同一个父类。这绝非“必须行为”。你可以只把服务定义中的通用部分,分离到父服务中,但却不在PHP中继承任何父类。
覆写parent dependencies ¶
有时候,你需要覆写的仅仅是一个“被当做依赖传入子服务”的类。幸运的是,通过在配置文件中添加对该子服务的“方法调用”,于父服务中设定好的依赖可以被复写。因此,若你需要传递一个不同的依赖给NewsletterManager
类,按下述方法配置服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# ...
services:
# ...
my_alternative_mailer:
# ...
mail_manager:
abstract: true
calls:
- [setMailer, ['@my_mailer']]
- [setEmailFormatter, ['@my_email_formatter']]
newsletter_manager:
class: NewsletterManager
parent: mail_manager
calls:
- [setMailer, ['@my_alternative_mailer']]
greeting_card_manager:
class: GreetingCardManager
parent: mail_manager |
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 35 36 37 38 |
<?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_alternative_mailer">
<!-- ... -->
</service>
<service id="mail_manager" abstract="true">
<call method="setMailer">
<argument type="service" id="my_mailer" />
</call>
<call method="setEmailFormatter">
<argument type="service" id="my_email_formatter" />
</call>
</service>
<service
id="newsletter_manager"
class="NewsletterManager"
parent="mail_manager">
<call method="setMailer">
<argument type="service" id="my_alternative_mailer" />
</call>
</service>
<service
id="greeting_card_manager"
class="GreetingCardManager"
parent="mail_manager" />
</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 28 29 30 |
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Reference;
// ...
$container->setDefinition('my_alternative_mailer', ...);
$mailManager = new Definition();
$mailManager
->setAbstract(true)
->addMethodCall('setMailer', array(
new Reference('my_mailer'),
))
->addMethodCall('setEmailFormatter', array(
new Reference('my_email_formatter'),
))
;
$container->setDefinition('mail_manager', $mailManager);
$newsletterManager = new DefinitionDecorator('mail_manager');
$newsletterManager->setClass('NewsletterManager');
->addMethodCall('setMailer', array(
new Reference('my_alternative_mailer'),
))
;
$container->setDefinition('newsletter_manager', $newsletterManager);
$greetingCardManager = new DefinitionDecorator('mail_manager');
$greetingCardManager->setClass('GreetingCardManager');
$container->setDefinition('greeting_card_manager', $greetingCardManager); |
GreetingCardManager
将收到和以前相同的依赖,但是NewsletterManager
将被传入my_alternative_mailer
而不是my_mailer service
。
你不能覆写方法调用。当你在子服务中定义一个新method call时,它将被添加到现有的“已配置方法集”中。这意味着,当setter覆写了当前属性时,它能够工作得很好——然而在setter把该属性附加到已经存在的数据时(例如addFilters()
方法),它就难以按预期工作了。此时,唯一的解决方案就是,不去继承父服务而是按常规情形来配置服务(如同之前你并不知道有“父服务”概念)。