如何装饰服务
当覆盖现有定义时,原始服务会丢失
1 2 3 4 5 6 7 8
# config/services.yaml
services:
App\Mailer: ~
# this replaces the old App\Mailer definition with the new one, the
# old definition is lost
App\Mailer:
class: App\NewMailer
大多数情况下,这正是您想要做的。但有时,您可能想要装饰旧的服务(即应用 装饰器模式)。在这种情况下,应保留旧的服务,以便在新服务中引用它。此配置用新服务替换 App\Mailer
,但保留旧服务的引用为 .inner
1 2 3 4 5 6 7 8 9 10 11
// src/DecoratingMailer.php
namespace App;
// ...
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
#[AsDecorator(decorates: Mailer::class)]
class DecoratingMailer
{
// ...
}
decorates
选项告诉容器 App\DecoratingMailer
服务替换了 App\Mailer
服务。如果您正在使用 默认的 services.yaml 配置,当装饰服务的构造函数具有一个类型提示为被装饰服务类的参数时,被装饰服务会自动注入。
如果您没有使用自动装配,或者装饰服务有多个类型提示为被装饰服务类的构造函数参数,则必须显式注入被装饰的服务(被装饰服务的 ID 会自动更改为 '.inner'
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/DecoratingMailer.php
namespace App;
// ...
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
#[AsDecorator(decorates: Mailer::class)]
class DecoratingMailer
{
public function __construct(
#[AutowireDecorated]
private object $inner,
) {
}
// ...
}
注意
被装饰的 App\Mailer
服务(它是新服务的别名)的可见性仍将与原始 App\Mailer
可见性相同。
注意
来自被装饰服务的所有自定义 服务标签 都会在新服务中被删除。只有 Symfony 定义的某些内置服务标签会被保留:container.service_locator
、container.service_subscriber
、kernel.event_subscriber
、kernel.event_listener
、kernel.locale_aware
和 kernel.reset
。
注意
生成的内部 ID 基于装饰器服务的 ID(此处为 App\DecoratingMailer
),而不是被装饰服务的 ID(此处为 App\Mailer
)。您可以通过 decoration_inner_name
选项控制内部服务名称
1 2 3 4 5 6
# config/services.yaml
services:
App\DecoratingMailer:
# ...
decoration_inner_name: App\DecoratingMailer.wooz
arguments: ['@App\DecoratingMailer.wooz']
装饰优先级
当对一个服务应用多个装饰器时,您可以使用 decoration_priority
选项控制它们的顺序。它的值是一个整数,默认为 0
,较高的优先级意味着装饰器将更早地应用。
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
// ...
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
#[AsDecorator(decorates: Foo::class, priority: 5)]
class Bar
{
public function __construct(
#[AutowireDecorated]
private $inner,
) {
}
// ...
}
#[AsDecorator(decorates: Foo::class, priority: 1)]
class Baz
{
public function __construct(
#[AutowireDecorated]
private $inner,
) {
}
// ...
}
生成的代码将如下所示
1
$this->services[Foo::class] = new Baz(new Bar(new Foo()));
堆叠装饰器
使用装饰优先级的另一种替代方法是创建一个有序服务的 stack
,每个服务都装饰下一个服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
# config/services.yaml
services:
decorated_foo_stack:
stack:
- class: Baz
arguments: ['@.inner']
- class: Bar
arguments: ['@.inner']
- class: Foo
# using the short syntax:
decorated_foo_stack:
stack:
- Baz: ['@.inner']
- Bar: ['@.inner']
- Foo: ~
# can be simplified when autowiring is enabled:
decorated_foo_stack:
stack:
- Baz: ~
- Bar: ~
- Foo: ~
结果将与上一节相同
1
$this->services['decorated_foo_stack'] = new Baz(new Bar(new Foo()));
像别名一样,stack
只能使用 public
和 deprecated
属性。
stack
的每个帧都可以是内联服务、引用或子定义。后者允许将 stack
定义嵌入到彼此之中,这是一个组合的高级示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# config/services.yaml
services:
some_decorator:
class: App\Decorator
embedded_stack:
stack:
- alias: some_decorator
- App\Decorated: ~
decorated_foo_stack:
stack:
- parent: embedded_stack
- Baz: ~
- Bar: ~
- Foo: ~
结果将是
1
$this->services['decorated_foo_stack'] = new App\Decorator(new App\Decorated(new Baz(new Bar(new Foo()))));
注意
要更改现有的堆栈(即从编译器传递中),您可以使用以下结构通过其生成的 ID 访问每个帧:.stack_id.frame_key
。从上面的示例中,.decorated_foo_stack.1
将是对内联 Baz
服务的引用,而 .decorated_foo_stack.0
将是对嵌入堆栈的引用。为了获得更明确的 ID,您可以为每个帧命名
1 2 3 4 5 6 7 8
# ...
decorated_foo_stack:
stack:
first:
parent: embedded_stack
second:
Baz: ~
# ...
Baz
帧 ID 现在将是 .decorated_foo_stack.second
。
控制当被装饰服务不存在时的行为
当您装饰一个不存在的服务时,decoration_on_invalid
选项允许您选择要采取的行为。
有三种不同的行为可用
exception
:将抛出ServiceNotFoundException
,提示装饰器的依赖项缺失。(默认)ignore
:容器将删除装饰器。null
:容器将保留装饰器服务,并将被装饰的服务设置为null
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// ...
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
use Symfony\Component\DependencyInjection\ContainerInterface;
#[AsDecorator(decorates: Mailer::class, onInvalid: ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]
class Bar
{
public function __construct(
#[AutowireDecorated] private $inner,
) {
}
// ...
}
警告
当使用 null
时,您可能需要更新装饰器构造函数,以使被装饰的依赖项可为空
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// src/Service/DecoratorService.php
namespace App\Service;
use Acme\OptionalBundle\Service\OptionalService;
class DecoratorService
{
public function __construct(
private ?OptionalService $decorated,
) {
}
public function tellInterestingStuff(): string
{
if (!$this->decorated) {
return 'Just one interesting thing';
}
return $this->decorated->tellInterestingStuff().' + one more interesting thing';
}
}
注意
有时,您可能想要添加一个编译器传递,以动态创建服务定义。如果您想要装饰这样的服务,请确保您的编译器传递注册为 PassConfig::TYPE_BEFORE_OPTIMIZATION
类型,以便装饰传递能够找到创建的服务。