Table of Contents
Laravel’s service container supports dependency injection at the method level, not just the constructor. When a service is only used in one controller method, inject it there instead of cluttering the constructor.
The Problem: Constructor Bloat
It’s common to see controllers with constructors packed with dependencies that are only used in one or two methods:
class OrderController extends Controller
{
public function __construct(
private OrderService $orderService,
private PaymentService $paymentService,
private ShippingService $shippingService, // Only used in ship()
) {}
public function index() {
return $this->orderService->list();
}
public function ship(Request $request) {
$this->shippingService->process(...);
}
}
The ShippingService is instantiated for every request to this controller, even though it’s only used in the ship() method.
The Solution: Method-Level Injection
Inject dependencies directly into the methods that use them:
class OrderController extends Controller
{
public function __construct(
private OrderService $orderService,
private PaymentService $paymentService,
) {}
public function index() {
return $this->orderService->list();
}
public function ship(Request $request, ShippingService $shippingService) {
$shippingService->process(...);
}
}
Laravel automatically resolves method parameters after route parameters. The service container inspects the type hint and provides the appropriate instance.
Benefits
- Constructor stays focused on shared dependencies used across multiple methods
- Clear intent — you can see exactly which methods use which services
- Easier testing — mock only what that specific method needs
- No unnecessary instantiation — dependencies are only created when the method is called
When to Use Which
| Scenario | Approach |
|---|---|
| Service used in 3+ methods | Constructor injection |
| Service used in 1 method only | Method injection |
| Service used in 2 methods | Judgment call (lean toward method) |
Route Parameters + Dependency Injection
When combining route model binding with method injection, Laravel resolves route parameters first, then dependencies:
// Route: /orders/{order}/ship
public function ship(Order $order, ShippingService $shippingService) {
// $order comes from route binding
// $shippingService comes from container
$shippingService->processOrder($order);
}
Laravel is smart enough to distinguish between route parameters (bound to the route) and type-hinted dependencies (resolved from the container).
Takeaway
Don’t default to constructor injection for everything. If a dependency is only used in one method, inject it there. Your constructor will stay clean, your intent will be clearer, and you’ll avoid unnecessary instantiations.
Leave a Reply