Controladors

Com crear i utilitzar controladors a Laravel: resource controllers, invokable controllers i dependency injection.

Crear un controlador#

Els controladors agrupen la lògica de gestió de peticions en classes organitzades. En lloc de definir tota la lògica directament a les rutes (cosa que ràpidament es torna inviable en projectes reals), els controladors permeten mantenir el codi net, estructurat i fàcil de testejar. Per crear-ne un, utilitza la comanda Artisan:

php artisan make:controller UserController

Això genera un fitxer a app/Http/Controllers/UserController.php. Dins, cada mètode públic gestiona una acció concreta de la teva aplicació:

namespace App\Http\Controllers;
 
use App\Models\User;
use Illuminate\Http\Request;
 
class UserController extends Controller
{
    public function index()
    {
        $users = User::all();
        return view('users.index', compact('users'));
    }
 
    public function show(User $user)
    {
        return view('users.show', compact('user'));
    }
 
    public function store(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required|max:255',
            'email' => 'required|email|unique:users',
        ]);
 
        $user = User::create($validated);
 
        return redirect()->route('users.show', $user);
    }
}

Per connectar el controlador a les rutes, passa un array amb la classe i el mètode:

// routes/web.php
use App\Http\Controllers\UserController;
 
Route::get('/usuaris', [UserController::class, 'index']);
Route::get('/usuaris/{user}', [UserController::class, 'show']);
Route::post('/usuaris', [UserController::class, 'store']);

Route Model Binding#

Fixa't que al mètode show() del controlador anterior, el paràmetre $user té el type hint User. Laravel utilitza Route Model Binding per trobar automàticament l'usuari a la base de dades a partir del paràmetre de la ruta. Si no el troba, retorna un error 404 automàticament sense que hagis d'escriure cap lògica addicional.

// Sense Route Model Binding (manual)
public function show(string $id)
{
    $user = User::findOrFail($id);
    return view('users.show', compact('user'));
}
 
// Amb Route Model Binding (automàtic)
public function show(User $user)
{
    // Laravel ja ha buscat l'usuari per tu
    return view('users.show', compact('user'));
}

Per defecte, Laravel busca el model per la seva clau primària (id). Si vols buscar per un altre camp, com un slug, pots personalitzar-ho al model:

// app/Models/Article.php
public function getRouteKeyName(): string
{
    return 'slug';
}

Ara, una ruta com /articles/{article} buscarà l'article pel camp slug en lloc de l'id.

Resource Controllers#

Un resource controller inclou tots els mètodes necessaris per gestionar un recurs CRUD complet. Pots generar-lo amb el flag --resource:

php artisan make:controller ArticleController --resource

Això crea un controlador amb set mètodes predefinits, cadascun per a una acció CRUD diferent:

class ArticleController extends Controller
{
    public function index()    { /* Llistar articles */ }
    public function create()   { /* Formulari de creació */ }
    public function store()    { /* Desar nou article */ }
    public function show()     { /* Mostrar un article */ }
    public function edit()     { /* Formulari d'edició */ }
    public function update()   { /* Actualitzar article */ }
    public function destroy()  { /* Eliminar article */ }
}

Una sola línia a les rutes registra les set rutes corresponents:

Route::resource('articles', ArticleController::class);

Si no necessites totes les accions, pots limitar-les amb only() o except():

// Només lectura
Route::resource('articles', ArticleController::class)
    ->only(['index', 'show']);
 
// Tot excepte eliminar
Route::resource('articles', ArticleController::class)
    ->except(['destroy']);

Per a APIs on no necessites les vistes de formulari (create i edit), utilitza apiResource():

Route::apiResource('articles', ArticleController::class);
// Registra: index, store, show, update, destroy (sense create ni edit)

Invokable Controllers#

Quan un controlador només necessita una acció, crear una classe amb un sol mètode pot semblar excessiu. Els invokable controllers resolen això amb un únic mètode __invoke():

php artisan make:controller ShowDashboard --invokable
class ShowDashboard extends Controller
{
    public function __invoke()
    {
        $stats = [
            'users' => User::count(),
            'articles' => Article::count(),
            'comments' => Comment::count(),
        ];
 
        return view('dashboard', compact('stats'));
    }
}

A les rutes, només cal passar la classe sense especificar el mètode. PHP cridarà __invoke() automàticament:

Route::get('/dashboard', ShowDashboard::class);

Aquesta aproximació és ideal per a pàgines o accions que no formen part d'un CRUD: un dashboard, una pàgina de contacte, un informe, una exportació a CSV, etc.

Dependency Injection#

Laravel injecta automàticament les dependències tant al constructor com als mètodes dels controladors. Això significa que pots demanar qualsevol classe com a paràmetre i Laravel la resoldrà automàticament des del contenidor de serveis, sense que l'hagis de crear manualment:

class UserController extends Controller
{
    public function __construct(
        private UserRepository $users
    ) {}
 
    public function index()
    {
        $users = $this->users->getActive();
        return view('users.index', compact('users'));
    }
 
    public function store(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required|max:255',
            'email' => 'required|email|unique:users',
        ]);
 
        $this->users->create($validated);
 
        return redirect()->route('users.index');
    }
}

El Request al mètode store() s'injecta automàticament amb les dades de la petició actual. El UserRepository al constructor es resol des del contenidor de serveis. Aquesta funcionalitat fa que el codi sigui més testejable, perquè en un test pots substituir el UserRepository real per un mock.

Pots combinar la injecció de dependències amb Route Model Binding al mateix mètode:

public function update(Request $request, User $user)
{
    // $request conté les dades de la petició
    // $user és el model resolt automàticament per la ruta
    $validated = $request->validate([
        'name' => 'required|max:255',
    ]);
 
    $user->update($validated);
 
    return redirect()->route('users.show', $user);
}

Middleware als controladors#

A més d'assignar middleware a les rutes, pots fer-ho directament al constructor del controlador. Això és útil quan vols que certs mètodes del controlador requereixin autenticació i d'altres no:

class ArticleController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth')->except(['index', 'show']);
    }
}

Amb aquesta configuració, qualsevol persona pot veure la llista d'articles (index) i un article individual (show), però per crear, editar o eliminar articles cal estar autenticat.