Service Locator с использованием ContainerInterface
В некоторых сценариях необходимо передавать сам контейнер зависимостей как зависимость. Это полезно при реализации паттерна Service Locator, когда сервисы создаются лениво или динамически.
🎯 Зачем это нужно?
- Динамическое создание сервисов - когда не все зависимости известны на этапе компиляции
- Ленивая загрузка - создание объектов только по необходимости
- Плагины и расширения - динамическое подключение дополнительных компонентов
- Частичная инициализация - загрузка только необходимых частей приложения
📋 Пример реализации Service Locator
<?php
declare(strict_types=1);
namespace App;
interface Handler {
public function handle(): int;
}
class A implements Handler {
public function handle(): int {
return 0;
}
}
class B implements Handler {
public function handle(): int {
return 0;
}
}
class ServiceLocator
{
private array $map = [
'a' => A::class,
'b' => B::class,
// можно добавлять другие сервисы
];
public function __construct(
private \Psr\Container\ContainerInterface $container
) {}
public function locate(string $key): Handler
{
if (!array_key_exists($key, $this->map)) {
throw new \InvalidArgumentException("Сервис '$key' не найден");
}
return $this->container->get($this->map[$key]);
}
}
🔧 Настройка контейнера
Шаг 1: Конфигурация компилятора
new \Cekta\DI\Compiler(
containers: [
ServiceLocator::class,
],
params: [
\Psr\Container\ContainerInterface::class => new \Cekta\DI\LazyClosure(
function (\Psr\Container\ContainerInterface $container) {
return $container; // Возвращаем сам контейнер
}
),
],
fqcn: 'App\\Runtime\\Container',
)->compile();
Шаг 2: Использование в приложении
<?php
declare(strict_types=1);
namespace App;
// Создаём контейнер с параметром ContainerInterface
$container = new \App\Runtime\Container([
\Psr\Container\ContainerInterface::class => new \Cekta\DI\LazyClosure(
function (\Psr\Container\ContainerInterface $container) {
return $container;
}
),
]);
// Получаем ServiceLocator
$locator = $container->get(ServiceLocator::class);
// Используем для динамического получения сервисов
$handlerA = $locator->locate('a'); // instanceof A
$handlerB = $locator->locate('b'); // instanceof B
🔄 Как это работает
- На этапе компиляции мы объявляем, что
ContainerInterfaceбудет предоставляться через параметр - В параметре используем
LazyClosure, который при вызове возвращает сам контейнер - ServiceLocator получает контейнер через внедрение зависимости
- При вызове
locate()ServiceLocator использует контейнер для создания нужного сервиса
⚠️ Предостережения
- Избегайте злоупотребления - Service Locator может скрывать реальные зависимости
- Тестируемость - сервисы, получающие контейнер, сложнее тестировать
- Явные зависимости предпочтительнее - по возможности указывайте зависимости явно в конструкторе
Service Locator с передачей контейнера - мощный инструмент для сложных сценариев, но используйте его осознанно, отдавая предпочтение явному внедрению зависимостей там, где это возможно.