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