Як декораувати сервіси
Дата оновлення перекладу 2025-01-17
Як декораувати сервіси
При перевизначенні існуючого визначення, оригінальний сервіс втрачається:
1 2 3 4 5 6 7 8
# config/services.yaml
services:
App\Mailer: ~
# це заміняє старе визначення app.mailer на нове,
# а старе визначення втрачається
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,
) {
}
// ...
}
Note
Видимість декорованого сервісу App\Mailer
(що є псевдонімом нового сервісу)
все ще буде така ж, як і видимість оригінального App\Mailer
.
Note
Усі користувацькі тегі сервісів з декорованого сервісу
видаляються у новому сервісі. Лише певні вбудовані теги сервісу,
визначені Symfony, залишаються: container.service_locator
, container.service_subscriber
,
kernel.event_subscriber
, kernel.event_listener
, kernel.locale_aware
,
та kernel.reset
.
Note
Згенерований внутрішній id засновано на id сервісу декоратора (тут - App\DecoratingMailer
),
а не декорованого сервісу (тут - 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
# використовуючи короткий синтаксис:
decorated_foo_stack:
stack:
- Baz: ['@.inner']
- Bar: ['@.inner']
- Foo: ~
# може бути спрощено з включеним автомонтуванням:
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()))));
Note
Щоб змінити існуючі стеки (тобто, з пропуску компілятора), ви можете отримати
доступ до кожної рамки за її згенерованим 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: ~
# ...
Id рамки Baz
тепер буде .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,
) {
}
// ...
}
Warning
При використанні 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';
}
}
Note
Іноді, вам може захотітися додати пропуск компілятора, який створює визначення
сервісів на льоту. Якщо ви хочете декорувати такий сервіс, переконайтеся, що ваш
пропуск компілятора зареєстровано з типом PassConfig::TYPE_BEFORE_OPTIMIZATION
,
щоб пропуск декорацій міг знайти створені сервіси.