Как работает Dependency Injection (DI) в Laravel?

«Как работает Dependency Injection (DI) в Laravel?» — вопрос из категории Laravel, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Laravel Dependency Injection (внедрение зависимостей) реализовано через мощный Service Container. Контейнер автоматически разрешает и внедряет зависимости в классы, что способствует слабой связанности и лёгкому тестированию.

Автоматическое разрешение (Automatic Resolution) Контейнер анализирует типы параметров в конструкторе класса и рекурсивно создаёт все необходимые экземпляры.

// Сервис с зависимостью
class PaymentProcessor {
    public function __construct(private LoggerInterface $logger) {}
    public function process(Payment $payment) {
        $this->logger->info('Processing payment: ' . $payment->id);
        // ... логика оплаты
    }
}

// В контроллере Laravel автоматически внедрит LoggerInterface
class CheckoutController extends Controller
{
    public function pay(Request $request, PaymentProcessor $processor)
    {
        // $processor уже создан с переданным логгером
        $processor->process($payment);
    }
}

Явная привязка (Explicit Binding) Вы можете указать контейнеру, какую конкретную реализацию использовать для интерфейса. Это делается в сервис-провайдере.

// AppProvidersAppServiceProvider.php
public function register(): void
{
    $this->app->bind(
        AppContractsPaymentGateway::class,
        AppServicesStripeGateway::class // Конкретная реализация
    );

    // Синглтон: один экземпляр на всё приложение
    $this->app->singleton(
        AppServicesGeoLocation::class,
        function ($app) {
            return new GeoLocation($app->make('config')->get('services.geo.key'));
        }
    );
}

Внедрение в методы (Method Injection) Laravel также поддерживает внедрение зависимостей не только в конструктор, но и в методы контроллеров, middleware, job'ов и т.д.

// Внедрение конкретного Request и кастомного сервиса в метод
public function store(
    AppHttpRequestsCreatePostRequest $request,
    AppServicesSlugGenerator $slugGenerator
) {
    $post = new Post([
        'title' => $request->title,
        'slug' => $slugGenerator->fromString($request->title)
    ]);
    $post->save();
}

Почему это важно: Такой подход делает код модульным и легко тестируемым. В тестах вы можете легко подменить реальную зависимость (например, подключение к БД) на mock-объект.