Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

🔄 Как это работает

  1. На этапе компиляции мы объявляем, что ContainerInterface будет предоставляться через параметр
  2. В параметре используем LazyClosure, который при вызове возвращает сам контейнер
  3. ServiceLocator получает контейнер через внедрение зависимости
  4. При вызове locate() ServiceLocator использует контейнер для создания нужного сервиса

⚠️ Предостережения

  1. Избегайте злоупотребления - Service Locator может скрывать реальные зависимости
  2. Тестируемость - сервисы, получающие контейнер, сложнее тестировать
  3. Явные зависимости предпочтительнее - по возможности указывайте зависимости явно в конструкторе

Service Locator с передачей контейнера - мощный инструмент для сложных сценариев, но используйте его осознанно, отдавая предпочтение явному внедрению зависимостей там, где это возможно.