Originally published on laravel.learnio.dev — part of the Laravel best practices course. View this lesson on the original site.
Let the container build what your action needs
Dependency injection keeps controllers thin by giving them ready-to-use collaborators, instead of constructing dependencies manually.
Constructor injection for shared dependencies
Use constructor injection when multiple methods need the same service:
use App\Services\OrderService;
class OrderController extends Controller
{
public function __construct(
private readonly OrderService $orders
) {}
}
Laravel resolves the service via the container when it creates the controller.
Method injection for request specific dependencies
Use method injection when a dependency is needed by one action only:
use Illuminate\Http\Request;
public function store(Request $request)
{
// use request data here
}
If route parameters are present, list them after dependencies:
public function update(Request $request, string $id)
{
// request first, route param second
}
Interface injection with explicit bindings
When type-hinting an interface, bind it in a service provider:
// AppServiceProvider::register
$this->app->bind(PaymentGateway::class, StripePaymentGateway::class);
Then inject the interface:
public function checkout(PaymentGateway $gateway)
{
// ...
}
This makes it easy to swap implementations for tests or different environments.
Bad way vs better way
Bad way (manual construction inside controller):
public function store(Request $request)
{
$service = new CreateOrderService();
$service->handle($request->all());
}
Better way (container-managed dependency):
public function store(Request $request, CreateOrderService $service)
{
$service->handle($request->all());
}
Why better:
- dependencies are explicit in the method signature,
- easier mocking and testing,
- less hidden coupling to concrete implementations.
Practical DI checklist
- inject request and services, do not instantiate them in actions,
- keep route params after injected dependencies,
- bind interfaces when no concrete class can be auto-resolved,
- keep controller actions orchestration-focused.
DI resolution flow
flowchart LR
A[HTTP request hits route] --> B[Container resolves controller dependencies]
B --> C[Controller action receives injected objects]
C --> D[Service work and response]
Next, you will apply these injection habits while handling input with the request object.
From Laravel best practices — Routing, Controllers & Request Flow