Introduction
Controllers group related request-handling logic into a class instead of scattering it across route closures. This lesson covers make:controller, single-action controllers, constructor dependency injection, and the modern ways to attach middleware.
Key Concepts
- Controller: A class under
app/Http/Controllersthat groups actions around a resource or topic. - Single-action (invokable) controller: A class with one
__invoke()method for one-off complex actions. - Dependency injection: Type-hint a service in the constructor or method and Laravel resolves it.
HasMiddlewareinterface: Declares middleware in a staticmiddleware()method — the Laravel 11+ pattern.#[Middleware]attribute: Laravel 13's PHP-attribute-based alternative toHasMiddleware.
Real World Context
Closures work for 'Hello World' but scale badly. Every non-trivial Laravel app uses controllers so actions can share constructor-injected services, be unit-tested in isolation, and carry class-level middleware.
Deep Dive
Controllers group related request handling logic into a single class. Instead of defining all logic in route closures, controllers help organize your application.
Why Controllers?
php// ❌ Route closures work but don't scale Route::get('/users', function () { $users = User::all(); // 50 lines of logic... return view('users.index', compact('users')); }); Route::post('/users', function (Request $request) { // Another 50 lines... }); // ✅ Controllers organize related logic Route::get('/users', [UserController::class, 'index']); Route::post('/users', [UserController::class, 'store']);
Creating Controllers
Use Artisan to generate controllers:
bash# Basic controller php artisan make:controller UserController # Resource controller (with CRUD methods) php artisan make:controller PostController --resource # API resource controller (no create/edit) php artisan make:controller API/ProductController --api # Single action controller php artisan make:controller ShowProfile --invokable
Basic Controller Structure
php<?php namespace App\Http\Controllers; use App\Models\User; use Illuminate\Http\Request; use Illuminate\View\View; class UserController extends Controller { /** * Display a listing of users. */ public function index(): View { $users = User::all(); return view('users.index', ['users' => $users]); } /** * Display the specified user. */ public function show(User $user): View { return view('users.show', ['user' => $user]); } /** * Store a newly created user. */ public function store(Request $request) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:users', ]); $user = User::create($validated); return redirect()->route('users.show', $user); } }
Defining Routes for Controllers
phpuse App\Http\Controllers\UserController; // Single method Route::get('/users', [UserController::class, 'index']); // Multiple methods Route::get('/users', [UserController::class, 'index'])->name('users.index'); Route::get('/users/{user}', [UserController::class, 'show'])->name('users.show'); Route::post('/users', [UserController::class, 'store'])->name('users.store');
Single Action Controllers
For controllers with only one method, use __invoke:
php<?php namespace App\Http\Controllers; class ShowProfile extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request) { return view('profile', [ 'user' => $request->user(), ]); } }
Route definition is simpler:
php// No method needed! Route::get('/profile', ShowProfile::class);
Dependency Injection
Laravel's service container resolves controller dependencies:
php<?php namespace App\Http\Controllers; use App\Services\UserService; class UserController extends Controller { /** * Constructor injection */ public function __construct( private UserService $userService ) {} public function index() { $users = $this->userService->getAllActive(); return view('users.index', ['users' => $users]); } /** * Method injection */ public function store(Request $request, UserService $service) { $user = $service->create($request->validated()); return redirect()->route('users.show', $user); } }
Controller Middleware
Apply middleware to all or specific controller methods:
php// In routes Route::get('/dashboard', [DashboardController::class, 'index']) ->middleware('auth'); // Via the HasMiddleware interface (Laravel 11+) use Illuminate\Routing\Controllers\HasMiddleware; use Illuminate\Routing\Controllers\Middleware; class UserController extends Controller implements HasMiddleware { public static function middleware(): array { return [ 'auth', new Middleware('admin', only: ['destroy']), new Middleware('throttle:60,1', except: ['index', 'show']), ]; } } // Modern approach: PHP attributes (Laravel 13) use Illuminate\Routing\Attributes\Controllers\Middleware; #[Middleware('auth')] class UserController extends Controller { #[Middleware('admin')] public function destroy() { /* ... */ } }
Common Pitfalls
- Using the old constructor
$this->middleware()pattern — Laravel 11+ removed it. UseHasMiddlewareor#[Middleware]instead. - Putting business logic in controllers — Controllers should coordinate, not compute. Extract services or jobs.
Best Practices
- Keep controllers thin — One HTTP action per method, delegating the heavy lifting to services.
- Prefer invokable controllers for single actions — Cleaner than a 50-line class with one method.
Summary
- Controllers live in
app/Http/Controllersand extend the baseControllerclass. --invokablegenerates a one-method__invoke()controller.- Laravel injects dependencies via the service container.
- Apply middleware via the
HasMiddlewareinterface or Laravel 13#[Middleware]attributes. - Controllers should be thin — delegate to services.