Introduction to Controllers

+15 Mana ✨

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/Controllers that 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.
  • HasMiddleware interface: Declares middleware in a static middleware() method — the Laravel 11+ pattern.
  • #[Middleware] attribute: Laravel 13's PHP-attribute-based alternative to HasMiddleware.

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

php
use 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

  1. Using the old constructor $this->middleware() pattern — Laravel 11+ removed it. Use HasMiddleware or #[Middleware] instead.
  2. Putting business logic in controllers — Controllers should coordinate, not compute. Extract services or jobs.

Best Practices

  1. Keep controllers thin — One HTTP action per method, delegating the heavy lifting to services.
  2. Prefer invokable controllers for single actions — Cleaner than a 50-line class with one method.

Summary

  • Controllers live in app/Http/Controllers and extend the base Controller class.
  • --invokable generates a one-method __invoke() controller.
  • Laravel injects dependencies via the service container.
  • Apply middleware via the HasMiddleware interface or Laravel 13 #[Middleware] attributes.
  • Controllers should be thin — delegate to services.
✓ Completed