Skip to content

Feature Decorators

github-actions[bot] edited this page Jul 8, 2026 · 1 revision

🎁 Декораторы

← Возможности · Главная · Сравнение

decorate($id, $decorator) оборачивает сервис в декоратор — добавляет поведение (логирование, кэш, метрики) не трогая исходный класс. Туториал — Tags-and-decorators.

Принцип работы

decorate() регистрирует цепочку декораторов для id. При разрешении сервиса контейнер создаёт исходный экземпляр, затем последовательно оборачивает его каждым декоратором (callable, получающий внутренний сервис и контейнер) — снаружи наружу. Результат кэшируется как обычный singleton.

$c->set(PaymentGateway::class, fn () => new StripeGateway());

$c->decorate(PaymentGateway::class,
    fn (PaymentGateway $inner, Container $c) => new LoggingGateway($inner, $c->get(Logger::class)),
);
$c->decorate(PaymentGateway::class,
    fn (PaymentGateway $inner) => new RetryingGateway($inner),
);

$c->get(PaymentGateway::class); // Retrying(Logging(Stripe))

Плюсы

  • Open/Closed: добавляем поведение без изменения исходного класса.
  • Композиция: несколько декораторов складываются в цепочку.
  • Идеально для cross-cutting concerns: логи, кэш, ретраи, метрики, транзакции.

Минусы

  • Глубокие цепочки усложняют отладку стека вызовов.
  • Декоратор должен реализовывать тот же интерфейс, что и внутренний сервис.

✅ Рекомендуется

  • Декорировать по интерфейсу — декоратор и оригинал взаимозаменяемы.
  • Держать декораторы «тонкими» и однонаправленными (одна ответственность на декоратор).

❌ Антипаттерны

  • Прятать в декораторе бизнес-логику, меняющую контракт, — это уже не декоратор.
  • Цепочки из множества декораторов, где порядок неочевиден и хрупок.

Декоратор атрибутом (#[AsDecorator])

При сканировании каталога класс с #[AsDecorator('id')] автоматически регистрируется декоратором сервиса id — без ручного decorate() (аналог Symfony #[AsDecorator]):

#[AsDecorator('logger')]
final class TimingLogger implements LoggerInterface
{
    public function __construct(private readonly LoggerInterface $inner) {}

    public function log(string $message): void
    {
        $this->inner->log('[' . microtime(true) . '] ' . $message);
    }
}

Декорируемый (внутренний) сервис передаётся первым параметром конструктора; остальные параметры должны иметь значения по умолчанию. Для декораторов со сложными зависимостями используйте factory в конфигурации или программный decorate().

Аналоги в других контейнерах

Контейнер Аналог
PHP-DI через фабрику-обёртку (⚠️ вручную) — есть
Symfony decorates: (эталонная поддержка)
Laravel extend() — обёртка сервиса
Pimple extend() (базовая обёртка) — но без autowiring (⚠️)
Nette декорирование через конфиг

Все кроме «чистого» Pimple; CloudCastle даёт цепочки декораторов с доступом к контейнеру.

См. также: Tags-and-decorators (гайд) · After-resolving hooks.

Clone this wiki locally