如何去装饰服务

3.4 版本
维护中的版本

本功能从symfony2.5开始被引入。

当既有定义被覆写时,老的服务定义会消失:

1
2
3
4
5
6
$container->register('foo', 'FooService');
 
// this is going to replace the old definition with the new one
// 这会用新定义取代老定义
// old definition is lost 旧定义消失了
$container->register('foo', 'CustomFooService');

多数情况下,这确实是你想要的。但有时,你可能需要改装旧服务而不是取而代之。在上例中,旧服务应该保持“能被新服务引用”的状态。下面的配置文件把foo替换成了一个新服务,但是新服务拥有一个旧foo的引用,叫bar.inner

1
2
3
4
5
bar:
  public: false
  class: stdClass
  decorates: foo
  arguments: ["@bar.inner"]
1
2
3
<service id="bar" class="stdClass" decorates="foo" public="false">
    <argument type="service" id="bar.inner" />
</service>
1
2
3
4
5
6
use Symfony\Component\DependencyInjection\Reference;
 
$container->register('bar', 'stdClass')
    ->addArgument(new Reference('bar.inner'))
    ->setPublic(false)
    ->setDecoratedService('foo');

此处发生的是:setDecoratedService()方法告诉容器,bar服务要取代foo服务。根据命名约定(convention),老的foo服务将被重命名为bar.inner,所以你可以将它注入到你的新服务中。

这个生成的inner id是基于装修者服务的(此处为bar),不是被装修的服务(此处为foo)。这允许多个装修者出现在同一服务上(它们需要生成不同的inner ids)。

多数情况下,装修者应该声明为私有(private),因为你不需要从容器中取用它。被装饰的foo服务(装饰之后该服务使用bar假名)的可见性,则仍然保持着原始的未装修之前的状态。

如果需要,你可以改变inner服务的名字:

1
2
3
4
5
6
bar:
  class: stdClass
  public: false
  decorates: foo
  decoration_inner_name: bar.wooz
  arguments: ["@bar.wooz"]
1
2
3
<service id="bar" class="stdClass" decorates="foo" decoration-inner-name="bar.wooz" public="false">
    <argument type="service" id="bar.wooz" />
</service>
1
2
3
4
5
6
use Symfony\Component\DependencyInjection\Reference;
 
$container->register('bar', 'stdClass')
    ->addArgument(new Reference('bar.wooz'))
    ->setPublic(false)
    ->setDecoratedService('foo', 'bar.wooz');

如果你需要把一个以上的装修者(decorator)用在一个服务上,你可以控制它们的顺序,通过配置装修优先级(priority of decoration),它可以是任何整数(高优先级的decorators会排在前面)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
foo:
    class: Foo

bar:
    class: Bar
    public: false
    decorates: foo
    decoration_priority: 5
    arguments: ['@bar.inner']

baz:
    class: Baz
    public: false
    decorates: foo
    decoration_priority: 1
    arguments: ['@baz.inner']
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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="foo" class="Foo" />
 
        <service id="bar" class="Bar" decorates="foo" decoration-priority="5" public="false">
            <argument type="service" id="bar.inner" />
        </service>
 
        <service id="baz" class="Baz" decorates="foo" decoration-priority="1" public="false">
            <argument type="service" id="baz.inner" />
        </service>
    </services>
</container>
1
2
3
4
5
6
7
8
9
10
11
12
13
use Symfony\Component\DependencyInjection\Reference;
 
$container->register('foo', 'Foo')
 
$container->register('bar', 'Bar')
    ->addArgument(new Reference('bar.inner'))
    ->setPublic(false)
    ->setDecoratedService('foo', null, 5);
 
$container->register('baz', 'Baz')
    ->addArgument(new Reference('baz.inner'))
    ->setPublic(false)
    ->setDecoratedService('foo', null, 1);

(在容器中)生成的代码会是下面这样:

1
$this->services['foo'] = new Baz(new Bar(new Foo())));

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

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