От каких типов зависимостей может зависеть абстрактный сервис в PHP-приложении?

«От каких типов зависимостей может зависеть абстрактный сервис в PHP-приложении?» — вопрос из категории Архитектура, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Абстрактный сервис, будучи шаблоном для конкретных реализаций, обычно зависит от интерфейсов (абстракций), а не от конкретных классов. Это следует принципу инверсии зависимостей (DIP). В моих проектах на Symfony/Laravel такие зависимости включали:

1. Репозитории (Data Access Layer) Абстрактный сервис получает интерфейсы репозиториев для доступа к данным, скрывая детали реализации (Doctrine ORM, Eloquent, прямой PDO).

abstract class AbstractOrderService
{
    protected OrderRepositoryInterface $orderRepository;
    protected UserRepositoryInterface $userRepository;

    public function __construct(
        OrderRepositoryInterface $orderRepository,
        UserRepositoryInterface $userRepository
    ) {
        $this->orderRepository = $orderRepository;
        $this->userRepository = $userRepository;
    }
}

2. Другие сервисы (Domain Services) Зависимость от интерфейсов других сервисов предметной области для разделения ответственности.

abstract class AbstractPaymentService
{
    protected NotificationServiceInterface $notifier;
    protected LoggerInterface $logger;
    // ...
}

3. Объекты передачи данных (DTO) и спецификации Сервис может быть параметризован классами DTO для ввода/вывода или интерфейсами спецификаций (Specification pattern) для инкапсуляции бизнес-правил.

4. Инфраструктурные сервисы

  • Логгер (PSR-3 LoggerInterface): Для записи событий и ошибок.
  • Отправщик событий (EventDispatcherInterface): Для декoupling логики через события домена.
  • Кеш (PSR-6 CacheItemPoolInterface или PSR-16 CacheInterface): Для оптимизации.
  • Валидатор: Часто через интерфейс, например, SymfonyComponentValidatorValidatorValidatorInterface.

5. Фабрики Интерфейсы фабрик для создания сложных объектов, если это входит в ответственность сервиса.

Ключевые принципы при проектировании:

  • Внедрение зависимостей через конструктор (Constructor Injection): Это делает зависимости явными и сервис сразу готов к работе.
  • Зависимость от абстракций: Это позволяет легко подменять реализации (например, мокировать в тестах) и соблюдать Open/Closed Principle.
  • Минимизация зависимостей: Абстрактный сервис не должен превращаться в "Божественный объект". Если зависимостей слишком много — возможно, сервис нарушает Single Responsibility Principle.

В итоге, абстрактный сервис определяет общий контракт и базовую логику для наследников, а его зависимости задают границы его взаимодействия с другими слоями приложения.