跳到內容

如何裝飾服務

編輯此頁面

當覆寫現有的定義時,原始服務會遺失

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_locatorcontainer.service_subscriberkernel.event_subscriberkernel.event_listenerkernel.locale_awarekernel.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 只能使用 publicdeprecated 屬性。

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 類型註冊您的編譯器傳遞,以便裝飾傳遞能夠找到建立的服務。

這項作品,包括程式碼範例,均根據 Creative Commons BY-SA 3.0 授權條款授權。
目錄
    版本