DI — Dependency Injection: IoC applied at the constructor level
Definition
Dependency Injection (DI) is an implementation of the Inversion of Control principle in which a class receives its dependencies as constructor arguments (or method/property arguments) rather than creating them internally. The critical distinction: the class declares what types it needs; the caller — or a container — decides which concrete instance to supply. DI is not a library or framework feature; it is a coding style that can be practiced in plain PHP with no tooling at all.
In Practice
<?php
// Constructor injection — the dominant form in Laravel
class ReportService
{
public function __construct(
private readonly UserRepository $users,
private readonly Mailer $mailer,
private readonly LoggerInterface $logger,
) {}
public function generate(int $userId): Report
{
$user = $this->users->findOrFail($userId);
$report = new Report($user);
$this->mailer->send($user->email, $report);
$this->logger->info('Report generated', ['user' => $userId]);
return $report;
}
}
// Method injection — Laravel does this in controller actions
class ReportController extends Controller
{
public function show(Request $request, ReportService $service): JsonResponse
{
// $service was injected by the container into this method call
return response()->json($service->generate($request->user()->id));
}
}In a Laravel codebase, constructor injection is the norm in services, repositories, listeners, and console commands. Method injection appears in controller actions and route closures. Laravel's container reads the type-hints via PHP's ReflectionClass API and resolves each dependency recursively before building the outermost class.
In Context
In interviews, a strong answer distinguishes the three DI forms: constructor injection (dependencies required to build the object at all), setter injection (optional dependencies configured after construction), and method injection (a dependency needed only for one operation). Laravel almost exclusively uses constructor and method injection; setter injection is rare because it implies an object that is partially valid before configuration, which violates the principle of keeping objects in a consistently valid state.
A common misconception is that DI requires a container. It does not. Manual DI — passing new SmtpMailer() explicitly into new OrderService() — is still DI. The container automates what you would otherwise wire by hand. The benefit of manual DI is zero magic; the cost is verbose wiring code that a container eliminates.
DI connects directly to the Dependency Inversion Principle (the D in SOLID): DI is the mechanical technique; DIP is the architectural rule that says you should inject abstractions (interfaces), not concretes. When you bind Mailer::class to SmtpMailer::class in a service provider, you are practicing both DI (injecting into constructors) and DIP (the constructor depends on the interface, not the class).