Skip to content

Feature Service subscribers

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

📥 Service subscribers — подписка на сервисы

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

Подписчик декларирует, какие сервисы ему нужны, и получает scoped-локатор с ленивым доступом только к ним — вместо всего контейнера. Аналог ServiceSubscriberInterface + ServiceLocator Symfony.

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

  1. Класс реализует ServiceSubscriberInterface::getSubscribedServices(): array — карту «локальный ключ → id сервиса».
  2. Конструктор принимает параметр типа Psr\Container\ContainerInterface.
  3. При autowiring контейнер подставляет туда не себя, а ServiceSubscriberLocator, ограниченный этой картой. Доступ ленивый: сервис создаётся только при get($key).
use CloudCastle\DI\Contract\ServiceSubscriberInterface;
use Psr\Container\ContainerInterface;

final class ReportController implements ServiceSubscriberInterface
{
    public function __construct(private readonly ContainerInterface $locator) {}

    public static function getSubscribedServices(): array
    {
        return ['db' => Connection::class, 'pdf' => PdfRenderer::class];
    }

    public function export(): string
    {
        // Connection и PdfRenderer создаются лениво, только когда действительно нужны.
        return $this->locator->get('pdf')->render($this->locator->get('db')->fetchAll());
    }
}

$c = new Container();
$c->enableAutowiring();
$c->get(ReportController::class);   // $locator — ServiceSubscriberLocator только с 'db' и 'pdf'

Плюсы

  • Явный контракт зависимостей: видно, какие сервисы использует класс, прямо в getSubscribedServices().
  • Лень без proxy: тяжёлые сервисы не создаются, пока не вызван get($key) — полезно, когда из многих зависимостей в конкретном запросе нужна одна.
  • Инкапсуляция: класс получает не весь контейнер (service-locator антипаттерн во всю ширь), а узкий срез — только объявленное.
  • PSR-11: локатор реализует Psr\Container\ContainerInterface — переносимо и тестируемо (легко подменить фейком).

Минусы

  • Это всё же локатор, а не чистая инъекция — доступ по строковому ключу, а не по типу; злоупотребление возвращает к service-locator антипаттерну.
  • Ключи getSubscribedServices() статичны и не проверяются на этапе autowiring (опечатка ключа всплывёт как NotFoundException при get()).
  • Только для параметра Psr\Container\ContainerInterface; полный CloudCastle\DI\ContainerInterface по-прежнему инъектирует весь контейнер.

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

  • Контроллеры/обработчики с множеством редко используемых зависимостей — подписка + ленивый get().
  • Комбинировать с тегами: подписаться на конкретные сервисы, а не тянуть весь тег.

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

  • Подписываться на десятки сервисов «на всякий случай» — это скрытая God-зависимость.
  • Прокидывать локатор дальше по коду как универсальный контейнер — теряется весь смысл ограничения.
  • Класть в getSubscribedServices() ключи, которых нет в контейнере, и полагаться на них без обработки NotFoundException.

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

Контейнер Аналог
PHP-DI нет (❌ — только прямая инъекция)
Symfony ServiceSubscriberInterface + ServiceLocator (эталон)
Laravel нет scoped-локатора подписки (❌)
Pimple нет (❌)
Nette нет (❌)

CloudCastle, Symfony — декларативная подписка со scoped-локатором.

См. также: Теги и tagged services · Lazy service · Contextual binding.

Clone this wiki locally