Декоратор — это структурный паттерн проектирования, который позволяет динамически добавлять объектам новую функциональность, оборачивая их в полезные «обёртки».
При повторном переопределении существующего определения сервиса, для сервис-контейнера Symfony, то исходный сервис будет теряться:
# 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
В большинстве случаев это именно то, что вы хотите сделать, например для разных сред окружения (test, dev и т.д.).
Но иногда вы можете захотеть вместо этого декорировать старый сервис (то есть применить шаблон Decorator). В этом случае старый сервис должен быть сохранен, чтобы иметь возможность ссылаться на него в новом декорированном сервисе.
Конфигурация ниже заменяет сервис App\Mailer на новый, но сохраняет ссылку на старый как App\DecoratingMailer.inner:
# config/services.yaml
services:
App\Mailer: ~
App\DecoratingMailer:
# overrides the App\Mailer service
# but that service is still available as App\DecoratingMailer.inner
decorates: App\Mailer
Опция decorates сообщает сервис-контейнеру Symfony, что сервис App\DecoratingMailer заменяет сервис App\Mailer. Если вы используете конфигурацию services.yaml по умолчанию, то декорируемый сервис автоматически внедряется, когда у конструктора декорирующего сервиса есть один аргумент, с тайп-хинтом декорированного класса сервиса.
Если вы не используете autowiring или у сервиса декоратора есть несколько аргументов конструктора, с тайп-хинтом декорированного класса сервиса, то вы должны явно добавить декорированный сервис (идентификатор декорированного сервиса автоматически изменяется на decorating_service_id + ‘.inner’) :
# config/services.yaml
services:
App\Mailer: ~
App\DecoratingMailer:
decorates: App\Mailer
# pass the old service as an argument
arguments: ['@App\DecoratingMailer.inner']
Видимость декорируемого сервиса App\Mailer (который является алиасом для нового сервиса) будет по-прежнему такой же, как и исходная видимость App\Mailer.
Сгенерированный внутренний идентификатор основан на идентификаторе сервиса-декоратора (здесь App\DecoratingMailer), а не на декорированном сервисе (здесь App\Mailer). Вы можете контролировать внутреннее имя сервиса через опцию decoration_inner_name:
# config/services.yaml
services:
App\DecoratingMailer:
# ...
decoration_inner_name: App\DecoratingMailer.wooz
arguments: ['@App\DecoratingMailer.wooz']
Приоритет декораторов в сервис-контейнере.
Применяя несколько декораторов к сервису, вы можете управлять их порядком с параметром decoration_priority. Его значение представляет собой целое число со значением по умолчанию 0, а более высокие приоритеты означают, что декораторы будут применены раньше.
# config/services.yaml
Foo: ~
Bar:
public: false
decorates: Foo
decoration_priority: 5
arguments: ['@Bar.inner']
Baz:
public: false
decorates: Foo
decoration_priority: 1
arguments: ['@Baz.inner']
Сгенерированный код будет следующим:
$this->services[Foo::class] = new Baz(new Bar(new Foo()));