How to Decorate Services

When overriding an existing definition, the original service is lost:

既存の定義をオーバーライドすると、元のサービスが失われます。
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
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

Most of the time, that's exactly what you want to do. But sometimes, you might want to decorate the old one instead (i.e. apply the Decorator pattern). In this case, the old service should be kept around to be able to reference it in the new one. This configuration replaces App\Mailer with a new one, but keeps a reference of the old one as .inner:

ほとんどの場合、それはまさにあなたがやりたいことです。ただし、代わりに古いサービスを装飾したい場合があります (つまり、Decorator パターンを適用します)。この場合、新しいサービスで参照できるように、古いサービスを保持する必要があります。この構成は App\Mailer を新しいものに置き換えますが、古いものの参照を .inner として保持します。
  • Attributes
    属性
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
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
{
    // ...
}

6.1

6.1

The #[AsDecorator] attribute was introduced in Symfony 6.1.

#[AsDecorator] 属性は Symfony 6.1 で導入されました。

The decorates option tells the container that the App\DecoratingMailer service replaces the App\Mailer service. If you're using the default services.yaml configuration, the decorated service is automatically injected when the constructor of the decorating service has one argument type-hinted with the decorated service class.

decores オプションは、App\DecoratingMailerservice が App\Mailer サービスを置き換えることをコンテナに伝えます。デフォルトの services.yaml 構成を使用している場合、装飾サービスのコンストラクターが、装飾サービス クラスで型ヒントされた引数を 1 つ持つと、装飾サービスが自動的に挿入されます。

If you are not using autowiring or the decorating service has more than one constructor argument type-hinted with the decorated service class, you must inject the decorated service explicitly (the ID of the decorated service is automatically changed to '.inner'):

オートワイヤーを使用していない場合、または装飾サービスに、装飾されたサービス クラスで型ヒントされたコンストラクター引数が複数ある場合は、装飾されたサービスを明示的に注入する必要があります (装飾されたサービスの ID は自動的に「.inner」に変更されます)。
  • Attributes
    属性
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/DecoratingMailer.php
namespace App;

// ...
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\DependencyInjection\Attribute\MapDecorated;

#[AsDecorator(decorates: Mailer::class)]
class DecoratingMailer
{
    private $inner;

    public function __construct(#[MapDecorated] $inner)
    {
        $this->inner = $inner;
    }

    // ...
}

Tip

ヒント

The visibility of the decorated App\Mailer service (which is an alias for the new service) will still be the same as the original App\Mailer visibility.

装飾された App\Mailer サービス (新しいサービスのエイリアス) の可視性は、元の App\Mailer の可視性と同じままです。

Note

ノート

The generated inner id is based on the id of the decorator service (App\DecoratingMailer here), not of the decorated service (App\Mailer here). You can control the inner service name via the decoration_inner_name option:

生成された内部 ID は、装飾されたサービス (App\Mailerhere) ではなく、デコレータ サービス (ここでは App\DecoratingMailer) の ID に基づいています。 decoration_inner_name オプションを使用して内部サービス名を制御できます。
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
1
2
3
4
5
6
# config/services.yaml
services:
    App\DecoratingMailer:
        # ...
        decoration_inner_name: App\DecoratingMailer.wooz
        arguments: ['@App\DecoratingMailer.wooz']

Decoration Priority

When applying multiple decorators to a service, you can control their order with the decoration_priority option. Its value is an integer that defaults to 0 and higher priorities mean that decorators will be applied earlier.

複数のデコレーターをサービスに適用する場合、decoration_priority オプションを使用してそれらの順序を制御できます。その値は整数で、デフォルトは 0 で、優先度が高いほどデコレータが早く適用されることを意味します。
  • Attributes
    属性
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
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
// ...
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\DependencyInjection\Attribute\MapDecorated;

#[AsDecorator(decorates: Foo::class, priority: 5)]
class Bar
{
    private $inner;

    public function __construct(#[MapDecorated] $inner)
    {
        $this->inner = $inner;
    }
    // ...
}

#[AsDecorator(decorates: Foo::class, priority: 1)]
class Baz
{
    private $inner;

    public function __construct(#[MapDecorated] $inner)
    {
        $this->inner = $inner;
    }

    // ...
}

The generated code will be the following:

生成されるコードは次のようになります。
1
$this->services[Foo::class] = new Baz(new Bar(new Foo()));

Stacking Decorators

An alternative to using decoration priorities is to create a stack of ordered services, each one decorating the next:

装飾の優先度を使用する代わりに、順序付けされたサービスのスタックを作成し、それぞれが次のサービスを装飾します。
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
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: ~

The result will be the same as in the previous section:

結果は前のセクションと同じになります。
1
$this->services['decorated_foo_stack'] = new Baz(new Bar(new Foo()));

Like aliases, a stack can only use public and deprecated attributes.

エイリアスと同様に、スタックは public および deprecated 属性のみを使用できます。

Each frame of the stack can be either an inlined service, a reference or a child definition. The latter allows embedding stack definitions into each others, here's an advanced example of composition:

スタックの各フレームは、インライン サービス、参照、または子定義のいずれかになります。後者では、スタック定義を相互に埋め込むことができます。構成の高度な例を次に示します。
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
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: ~

The result will be:

結果は次のようになります。
1
$this->services['decorated_foo_stack'] = new App\Decorator(new App\Decorated(new Baz(new Bar(new Foo()))));

Note

ノート

To change existing stacks (i.e. from a compiler pass), you can access each frame by its generated id with the following structure: .stack_id.frame_key. From the example above, .decorated_foo_stack.1 would be a reference to the inlined Baz service and .decorated_foo_stack.0 to the embedded stack. To get more explicit ids, you can give a name to each frame:

既存のスタックを (つまり、コンパイラ パスから) 変更するには、次の構造を持つ生成された ID で各フレームにアクセスできます。 .0 を embeddedstack に追加します。より明示的な ID を取得するには、各フレームに名前を付けることができます。
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
1
2
3
4
5
6
7
8
# ...
decorated_foo_stack:
    stack:
        first:
            parent: embedded_stack
        second:
            Baz: ~
        # ...

The Baz frame id will now be .decorated_foo_stack.second.

Baz フレーム ID は .decorated_foo_stack.second になります。

Control the Behavior When the Decorated Service Does Not Exist

When you decorate a service that doesn't exist, the decoration_on_invalid option allows you to choose the behavior to adopt.

存在しないサービスをデコレートする場合、decoration_on_invalid オプションを使用すると、採用する動作を選択できます。

Three different behaviors are available:

次の 3 つの異なる動作を使用できます。
  • exception: A ServiceNotFoundException will be thrown telling that decorator's dependency is missing. (default)
    例外: ServiceNotFoundException がスローされ、デコレーターの依存関係がないことが通知されます。 (デフォルト)
  • ignore: The container will remove the decorator.
    ignore: コンテナーはデコレーターを削除します。
  • null: The container will keep the decorator service and will set the decorated one to null.
    null: コンテナーはデコレーター サービスを保持し、装飾されたサービスを null に設定します。
  • Attributes
    属性
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ...
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\DependencyInjection\Attribute\MapDecorated;
use Symfony\Component\DependencyInjection\ContainerInterface;

#[AsDecorator(decorates: Mailer::class, onInvalid: ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]
class Bar
{
    private $inner;

    public function __construct(#[MapDecorated] $inner)
    {
        $this->inner = $inner;
    }

    // ...
}

Caution

注意

When using null, you may have to update the decorator constructor in order to make decorated dependency nullable:

null を使用する場合、修飾された依存関係を null 可能にするために、デコレータ コンストラクタを更新する必要がある場合があります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/Service/DecoratorService.php
namespace App\Service;

use Acme\OptionalBundle\Service\OptionalService;

class DecoratorService
{
    private $decorated;

    public function __construct(?OptionalService $decorated)
    {
        $this->decorated = $decorated;
    }

    public function tellInterestingStuff(): string
    {
        if (!$this->decorated) {
            return 'Just one interesting thing';
        }

        return $this->decorated->tellInterestingStuff().' + one more interesting thing';
    }
}

Note

ノート

Sometimes, you may want to add a compiler pass that creates service definitions on the fly. If you want to decorate such a service, be sure that your compiler pass is registered with PassConfig::TYPE_BEFORE_OPTIMIZATION type so that the decoration pass will be able to find the created services.

場合によっては、その場でサービス定義を作成するコンパイラ パスを追加したい場合があります。このようなサービスをデコレートしたい場合は、コンパイラ パスが PassConfig::TYPE_BEFORE_OPTIMIZATIONtype に登録されていることを確認してください。これにより、デコレート パスは作成されたサービスを見つけることができます。