如何裝飾服務
當覆寫現有的定義時,原始服務會遺失
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
時,您可能必須更新裝飾器建構子,以便使被裝飾的依賴項可為 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
類型註冊您的編譯器傳遞,以便裝飾傳遞能夠找到建立的服務。