Laravel: Guida Completa a Controller e Dependency Injection

Scopri come organizzare la logica della tua applicazione Laravel utilizzando i Controller e come rendere il tuo codice flessibile grazie alla Dependency Injection.

Introduzione ai Controller in Laravel

Benvenuti nella settima lezione del nostro percorso "Impara Laravel in 50 lezioni". In questo modulo ci concentreremo su due pilastri fondamentali dello sviluppo moderno con Laravel: i Controller e la Dependency Injection (DI).

Se finora avete scritto la logica della vostra applicazione direttamente all'interno dei file delle rotte (routes/web.php), avrete notato che, man mano che l'applicazione cresce, quel file diventa caotico e difficile da mantenere. Immaginate di avere cento rotte, ognuna con dieci righe di logica di business: il vostro file di rotte diventerebbe un incubo di migliaia di righe.

I Controller nascono proprio per risolvere questo problema. In un'architettura MVC (Model-View-Controller), il Controller funge da "regista": riceve la richiesta dall'utente (tramite la rotta), interagisce con il Model per recuperare o salvare i dati e, infine, restituisce una View o una risposta JSON.

Creare e Organizzare i Controller

Per creare un controller in Laravel, non scriviamo manualmente la classe, ma utilizziamo l'interfaccia a riga di comando di Laravel, nota come Artisan.

Il comando Artisan

Per generare un nuovo controller, aprite il terminale nella cartella del progetto e digitate:

php artisan make:controller UserController

Questo comando creerà un file chiamato UserController.php all'interno della directory app/Http/Controllers.

Struttura di un Controller di base

Vediamo come appare un controller semplice. In questo esempio, creeremo un metodo per mostrare il profilo di un utente.

<?php

namespace App\\Http\\Controllers;

use Illuminate
egulars\\Request;
use App\\Models\\User;
use Illuminate	oHaveBeenCalled\\View;

class UserController extends Controller
{
    /**
     * Mostra il profilo di un utente specifico.
     */
    public function show($id)
    {
        // Recuperiamo l'utente dal database tramite il Model
        $user = User::findOrFail($id);

        // Restituiamo la vista 'user.profile' passando l'oggetto utente
        return view('user.profile', ['user' => $user]);
    }
}

Spiegazione del codice:

  1. Namespace: Definisce dove si trova la classe, permettendo a Laravel di caricarla correttamente.
  2. Method show($id): Questo è un'azione del controller. Riceve un parametro $id che verrà passato dalla rotta.
  3. User::findOrFail($id): Questo metodo del Model cerca l'utente. Se non lo trova, lancia automaticamente un errore 404, evitando che l'app crashi con un errore generico.
  4. view(): Questa funzione helper carica il file Blade corrispondente e gli inietta i dati.

Collegare il Controller alla Rotta

Ora che abbiamo il controller, dobbiamo dire a Laravel di usarlo. Nel file routes/web.php, invece di una funzione anonima, scriveremo:

use App\\Http\\Controllers\\UserController;
use Illuminate\\Support\\Facades\\Route;

Route::get('/user/{id}', [UserController::class, 'show']);

In questo modo, ogni volta che un utente visita /user/5, Laravel istanzia UserController e chiama il metodo show passando 5 come argomento.

Approfondimento: La Dependency Injection (DI)

La Dependency Injection è un concetto che spesso spaventa i principianti, ma in realtà è molto intuitivo se lo si guarda dal punto di vista pratico.

Cos'è la Dependency Injection?

In termini semplici, la DI consiste nel "iniettare" le dipendenze di una classe (altre classi di cui ha bisogno per funzionare) attraverso il costruttore o i metodi, invece di istanziarle manualmente all'interno della classe stessa.

Senza DI (Approccio errato):

public function store(Request $request)
{
    $logger = new FileLogger(); // Istanza manuale: la classe è "accoppiata" a FileLogger
    $logger->log("Utente creato");
}

Con DI (Approccio Laravel):

public function store(Request $request, LoggerInterface $logger)
{
    $logger->log("Utente creato");
}

Perché l'approccio con DI è migliore? Perché se un giorno decidessimo di cambiare FileLogger con DatabaseLogger, non dovremmo modificare ogni singolo controller, ma basterebbe cambiare la configurazione nel Service Container di Laravel.

Il Service Container di Laravel

Laravel possiede un potentissimo strumento chiamato Service Container. È un "magazzino" che sa come creare ogni oggetto della tua applicazione. Quando Laravel vede che un metodo di un controller richiede un oggetto (come Request o un tuo servizio personalizzato), il container controlla se sa come crearlo, lo istanza automaticamente e lo passa al metodo.

Esempi Pratici: Implementare un Servizio di Notifiche

Per capire davvero l'utilità della DI, creiamo un caso d'uso reale. Immaginiamo di voler inviare una notifica all'utente dopo che ha aggiornato il suo profilo.

1. Creazione dell'Interfaccia

Per rendere il sistema flessibile, creiamo un'interfaccia. L'interfaccia definisce cosa deve fare il servizio, ma non come.

namespace App\\Contracts;

interface NotificationServiceInterface
{
    public function send($message);
}

2. Implementazione del Servizio

Ora creiamo una classe che implementa questa interfaccia, ad esempio per inviare email.

namespace App\\Services;

use App\\Contracts\\NotificationServiceInterface;

class EmailNotificationService implements NotificationServiceInterface
{
    public function send($message)
    {
        // Logica per inviare un'email reale
        return "Email inviata con messaggio: $message";
    }
}

3. Iniezione nel Controller

Ora iniettiamo l'interfaccia nel nostro UserController. Notate che non chiediamo EmailNotificationService, ma NotificationServiceInterface. Questo è il segreto della flessibilità.

namespace App\\Http\\Controllers;

use App\\Contracts\\NotificationServiceInterface;
use Illuminate\\Http\\Request;

class UserController extends Controller
{
    protected $notificationService;

    // Iniezione tramite costruttore
    public function __construct(NotificationServiceInterface $notificationService)
    {
        $this->notificationService = $notificationService;
    }

    public function update(Request $request, $id)
    {
        // Logica di aggiornamento utente...
        
        $this->notificationService->send("Il tuo profilo è stato aggiornato!");
        
        return response()->json(['status' => 'success']);
    }
}

4. Binding nel Service Provider

Infine, dobbiamo dire a Laravel: "Ogni volta che qualcuno chiede NotificationServiceInterface, dagli un'istanza di EmailNotificationService". Questo si fa in app/Providers/AppServiceProvider.php:

public function register()
{
    $this->app->bind(\\App\\Contracts\\NotificationServiceInterface::class, \\App\\Services\\EmailNotificationService::class);
}

Errori Comuni e FAQ

Perché ricevo l'errore "Target class [Controller] does not exist"?

Questo accade spesso se dimenticate di importare la classe del controller nel file delle rotte. Assicuratevi di avere l'istruzione use App\\Http\\Controllers\\UserController; in cima al file web.php.

Posso usare la DI solo nel costruttore?

No, Laravel supporta anche la Method Injection. Se una dipendenza serve solo in un singolo metodo del controller, potete aggiungerla come parametro di quel metodo (come abbiamo fatto con Request $request), e Laravel la risolverà automaticamente.

Qual è la differenza tra bind e singleton nel Service Container?

  • bind: Crea una nuova istanza della classe ogni volta che viene richiesta.
  • singleton: Crea l'istanza una sola volta e la riutilizza per tutta la durata della richiesta HTTP, risparmiando memoria e risorse.

Conclusione e Prossimi Passi

In questa lezione abbiamo imparato che i Controller sono essenziali per mantenere il codice pulito, separando la definizione delle rotte dalla logica di esecuzione. Abbiamo inoltre scoperto che la Dependency Injection, supportata dal Service Container di Laravel, ci permette di scrivere codice disaccoppiato, facile da testare e manutenere.

Per approfondire, vi suggerisco di:

  1. Sperimentare con i Resource Controllers: provate il comando php artisan make:controller UserController --resource per generare automaticamente i metodi index, create, store, show, edit, update e destroy.
  2. Studiare i Form Request: imparate a spostare la logica di validazione dai controller a classi dedicate per rendere i vostri metodi ancora più snelli.
  3. Approfondire i Service Providers, che sono il cuore della configurazione di qualsiasi applicazione Laravel professionale.

Continuate a praticare: provate a implementare un sistema di logging personalizzato usando l'interfaccia e la DI come abbiamo fatto per le notifiche!