Ответ
Я строго придерживаюсь принципа внедрения зависимостей (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).
Ответ 18+ 🔞
Э, слушай, вот есть у меня принцип, как скала. Если я пишу класс, то все его зависимости — строго через конструктор. Это, блядь, святое, основа основ для кода, который не развалится от одного чиха и который можно нормально протестировать. Иначе получается пиздопроебина, где ничего не понять.
А почему это так, блядь, важно:
- Всё на виду, как в бане: Класс сразу ясно показывает, без чего он нихуя не работает. Никаких скрытых
newгде-то в углу и глобальных переменных, от которых потом волосы дыбом. - Тесты пишутся за пять минут: Хочешь потестить? Подсовываешь в конструктор мок вместо реальной базы или платежки — и всё, ебушки-воробушки. Никакой магии.
- Гибкость овердохуища: Можешь в разных местах прокидывать разные реализации. Скажем, логгер в файл для прода и логгер в
/dev/nullдля тестов. Красота.
Смотри, как у меня обычно выглядит:
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, например. Они эту всю хуйню сами аккуратно соберут, как конструктор.