Ответ
Я строго придерживаюсь принципа внедрения зависимостей (Dependency Injection, DI) через конструктор. Это краеугольный камень тестируемого и поддерживаемого кода.
Почему передача через конструктор — лучшая практика:
- Явность зависимостей: Класс честно декларирует, что ему нужно для работы. Нет скрытых вызовов
newили глобальных состояний. - Тестируемость: Я могу легко подменить реальный репозиторий или сервис мок-объектом в юнит-тестах.
- Гибкость: Конфигурация объекта выносится наружу, что позволяет использовать разные реализации (например,
FileLoggerиDatabaseLogger).
Пример из моего кода:
class OrderProcessor {
private PaymentGateway $gateway;
private OrderRepository $repository;
private LoggerInterface $logger;
// Все зависимости передаются явно
public function __construct(
PaymentGateway $gateway,
OrderRepository $repository,
LoggerInterface $logger
) {
$this->gateway = $gateway;
$this->repository = $repository;
$this->logger = $logger;
}
public function process(Order $order): void {
$this->logger->info('Processing order', ['id' => $order->id]);
// ... логика с использованием $this->gateway и $this->repository
}
}
Исключения, когда создание внутри допустимо:
- Value Objects (например,
new DateTimeImmutable()илиnew Uuid()). - Внутренние вспомогательные объекты, которые не влияют на поведение класса с точки зрения внешнего контракта и не требуют мокирования в тестах.
Для управления сложными графами зависимостей я использую DI-контейнеры (например, из Laravel или Symfony).