Il framework PHP Laravel è in continua evoluzione, con ogni nuova versione che mira a migliorare la Developer Experience, le performance e la pulizia del codice. Uno dei cambiamenti significativi introdotti in Laravel 11 (e che definisce lo standard per Laravel 12) riguarda la classe base App\Http\Controllers\Controller. A differenza delle versioni precedenti come Laravel 9 e Laravel 10, dove questa classe ereditava da Illuminate\Routing\Controller e includeva di default utili trait come AuthorizesRequests e ValidatesRequests, la nuova classe base è minimale, spingendo gli sviluppatori a essere più espliciti riguardo alle funzionalità che i loro controller effettivamente necessitano.

Questo cambiamento, sebbene possa sembrare minore, ha implicazioni dirette sul refactoring del codice quando si aggiorna un'applicazione aziendale da Laravel 9/10 a Laravel 11/12. Questo articolo tecnico ti guiderà attraverso le ragioni di questa modifica e, con abbondanti esempi di codice, ti mostrerà come adattare i tuoi controller per gestire l'autorizzazione e la validazione in modo moderno, pulito e in linea con la filosofia di Laravel 12.

Stai cercando un programmatore PHP Laravel esperto e consolidato per implementare tecniche sicure e professionali di sviluppo e refactoring di vecchie applicazioni Legacy verso le più recenti versioni di Laravel 11 e Laravel 12? Contattami per una consulenza e scopri come posso aiutare la tua impresa a modernizzare le applicazioni. Affidarsi a un esperto è la chiave per garantire un passaggio fluido e sicuro, corroborato da anni di esperienza e una profonda conoscenza delle best practice di Laravel e della Ingegneria del Software.

La classe base Controller in Laravel 9/10: funzionalità "out-of-the-box"

Nelle applicazioni Laravel 9 e 10, il file app/Http/Controllers/Controller.php si presentava tipicamente così:

// app/Http/Controllers/Controller.php (Laravel 9/10 style)
namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs; // In versioni più vecchie, questo era BusDispatchesJobs
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController; // Eredita da questa classe del framework

abstract class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}

Grazie a questa configurazione, qualsiasi controller della tua applicazione che estendeva App\Http\Controllers\Controller aveva immediatamente accesso a metodi utili come:

  • $this->authorize('action', $model): fornito dal trait AuthorizesRequests per verificare se l'utente autenticato ha il permesso di eseguire una data azione su un modello o una risorsa, basandosi sulle Policy definite.
  • $this->validate(Request $request, array $rules, ...): fornito dal trait ValidatesRequests per validare i dati della richiesta HTTP in input.
  • $this->dispatch($job): fornito dal trait DispatchesJobs per inviare job alla coda.

Esempio di un Controller L9/L10 che sfrutta questi trait:

// app/Http/Controllers/LegacyPostController.php (Laravel 9/10)
namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;

class LegacyPostController extends Controller // Eredita i trait
{
    public function store(Request $request)
    {
        // Autorizzazione "magica" grazie al trait AuthorizesRequests
        $this->authorize('create', Post::class);

        // Validazione "magica" grazie al trait ValidatesRequests
        $validatedData = $this->validate($request, [
            'title' => 'required|string|max:255|unique:posts,title',
            'body' => 'required|string',
        ]);

        $post = Post::create($validatedData + ['user_id' => $request->user()->id]);

        // Invio di un job (se DispatchesJobs è usato)
        // $this->dispatch(new \App\Jobs\NotifySubscribersOfNewPost($post));

        return response()->json(new \App\Http\Resources\PostResource($post), 201);
    }
}

Questo approccio era comodo, ma poteva nascondere le reali dipendenze di un controller e contribuire a una certa "magia" del framework che, sebbene apprezzata da molti, può rendere il codice meno esplicito.

Il cambiamento in Laravel 11 (e quindi L12): la nuova classe base minimale

Con Laravel 11, la filosofia è cambiata verso un maggiore minimalismo e una configurazione più programmatica. La nuova classe base app/Http/Controllers/Controller.php generata da Laravel è ora una semplice classe PHP vuota (POPO - Plain Old PHP Object), che non estende nulla e non include alcun trait di default:

// app/Http/Controllers/Controller.php (Laravel 11/12 style)
namespace App\Http\Controllers;

abstract class Controller
{
    // Questo è tutto! Nessun trait, nessuna estensione di default.
}

Conseguenze di questo cambiamento: Se aggiorni un'applicazione Laravel 9/10 a L11/L12 e i tuoi controller estendono App\Http\Controllers\Controller, i metodi $this->authorize(), $this->validate() e $this->dispatch() non saranno più disponibili automaticamente, portando a errori.

Perché questo cambiamento?

  • Maggiore Esplicitezza: Rende chiaro quali funzionalità un controller sta effettivamente utilizzando.
  • Riduzione del "Peso": I controller caricano solo ciò di cui hanno bisogno, contribuendo a un framework leggermente più snello.
  • Incoraggiamento di Best Practice: Spinge verso l'uso di Form Request per la validazione e l'iniezione esplicita di Gate/Policy o l'uso dei metodi del modello utente per l'autorizzazione.

Guida pratica al refactoring dei Controller (da L9/L10 a L11/L12)

Vediamo come adattare i tuoi controller esistenti.

Passo 1: Analizzare l'uso dei Trait nei Controller esistenti

La prima cosa da fare è identificare quali dei tuoi controller utilizzano i metodi forniti dai trait AuthorizesRequests, ValidatesRequests e DispatchesJobs. Una ricerca nel codice per $this->authorize, $this->validate e $this->dispatch (o dispatch_now) ti darà un quadro chiaro.

Passo 2: Opzione A - Aggiungere esplicitamente i Trait ai Controller necessari

La soluzione più rapida per ripristinare la funzionalità è aggiungere manualmente i trait richiesti ai controller che ne fanno uso.

// app/Http/Controllers/ModernPostController.php (Laravel 11/12 style - Opzione A)
namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; // <-- Aggiunto esplicitamente
use Illuminate\Foundation\Validation\ValidatesRequests; // <-- Aggiunto esplicitamente
// use Illuminate\Foundation\Bus\DispatchesJobs; // <-- Aggiungi se usi $this->dispatch()

class ModernPostController extends Controller // Estende la nuova classe base snella
{
    use AuthorizesRequests, ValidatesRequests; // Indica quali trait vuoi usare
    // use DispatchesJobs; // Se necessario

    public function store(Request $request)
    {
        $this->authorize('create', Post::class); // Ora funziona di nuovo

        $validatedData = $this->validate($request, [ // Ora funziona di nuovo
            'title' => 'required|string|max:255|unique:posts,title',
            'body' => 'required|string',
        ]);

        $post = $request->user()->posts()->create($validatedData);
        // $this->dispatch(new \App\Jobs\NotifySubscribersOfNewPost($post)); // Funzionerebbe se DispatchesJobs è usato

        return response()->json(new \App\Http\Resources\PostResource($post), 201);
    }

    public function update(Request $request, Post $post)
    {
        $this->authorize('update', $post);

        $validatedData = $this->validate($request, [
            'title' => 'sometimes|required|string|max:255|unique:posts,title,' . $post->id,
            'body' => 'sometimes|required|string',
        ]);
        
        $post->update($validatedData);
        return response()->json(new \App\Http\Resources\PostResource($post->fresh()));
    }
}
  • Vantaggi: Semplice e diretto. Minimo impatto sul codice esistente all'interno dei metodi del controller.
  • Svantaggi: Può essere ripetitivo se molti controller necessitano degli stessi trait.

Passo 3: Opzione B - Creare una propria classe Controller Base personalizzata

Se la maggior parte dei tuoi controller necessita degli stessi trait, puoi creare una tua classe base personalizzata che li includa, e far ereditare i tuoi controller da questa.

1. Crea app/Http/Controllers/BaseController.php: Puoi usare il comando Artisan introdotto in Laravel 11: php artisan make:class Http/Controllers/BaseController

// app/Http/Controllers/BaseController.php (Nuova classe base custom per L11/L12)
namespace App\Http\Controllers;

// Importa i trait necessari
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;

// La classe 'Controller' qui è quella snella fornita da app/Http/Controllers/Controller.php
// Non estende Illuminate\Routing\Controller di default in L11+
abstract class BaseController extends Controller // Estende la classe base minimale di Laravel
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
    // Includi solo i trait che ti servono comunemente
}

2. Modifica i tuoi Controller per estendere BaseController:

// app/Http/Controllers/ModernPostController.php (L11/L12 style - Opzione B)
namespace App\Http\Controllers;

use App\Models\Post; // ... (altri use)
use Illuminate\Http\Request;

class ModernPostController extends BaseController // Estende la nostra nuova classe base
{
    // Ora $this->authorize(), $this->validate(), $this->dispatch() sono disponibili
    // grazie all'ereditarietà da BaseController.
    public function store(Request $request)
    {
        $this->authorize('create', Post::class);
        $validatedData = $this->validate($request, [ /* ... */ ]);
        // ...
        return response()->json(/* ... */, 201);
    }
}
  • Vantaggi: Centralizza l'inclusione dei trait comuni, mantenendo i controller figli più puliti.
  • Svantaggi: Reintroduce un po' della "magia" della vecchia classe base se gli sviluppatori non sono consapevoli di quali trait sono inclusi in BaseController. È una questione di preferenze del team.

Passo 4: Opzione C - Adottare approcci alternativi più espliciti (Best Practice)

Questo cambiamento nella classe base del controller è anche un'ottima opportunità per adottare pattern che promuovono una maggiore separazione delle responsabilità, rendendo i controller ancora più snelli. Questo è l'approccio che personalmente consiglio per la maggior parte delle applicazioni aziendali.

A. Per la Validazione: Usare le Form Request Le Form Request sono classi dedicate alla validazione dei dati di una richiesta HTTP e all'autorizzazione della richiesta stessa. Sono una best practice da tempo in Laravel, ma la nuova struttura dei controller ne incentiva ulteriormente l'uso.

// 1. Crea una FormRequest: php artisan make:request StorePostRequest
// app/Http/Requests/StorePostRequest.php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use App\Models\Post; // Per l'autorizzazione

class StorePostRequest extends FormRequest
{
    /**
     * Determina se l'utente è autorizzato a effettuare questa richiesta.
     */
    public function authorize(): bool
    {
        // Puoi usare il Gate qui, o $this->user()->can(...)
        return $this->user()->can('create', Post::class);
    }

    /**
     * Ottiene le regole di validazione che si applicano alla richiesta.
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
     */
    public function rules(): array
    {
        return [
            'title' => 'required|string|max:255|unique:posts,title',
            'body' => 'required|string',
        ];
    }
}

// 2. Usa la FormRequest nel Controller
// app/Http/Controllers/ModernPostController.php
namespace App\Http\Controllers;

use App\Models\Post;
use App\Http\Requests\StorePostRequest; // Importa la FormRequest
// Non servono più i trait AuthorizesRequests e ValidatesRequests nel controller
// se la validazione e l'autorizzazione della richiesta sono gestite dalla FormRequest.

class ModernPostController extends Controller // Estende la classe base snella
{
    public function store(StorePostRequest $request) // Iniezione della FormRequest
    {
        // L'autorizzazione (metodo authorize() della FormRequest) è già stata eseguita.
        // La validazione (metodo rules() della FormRequest) è già stata eseguita.
        // Se falliscono, Laravel ritorna automaticamente una risposta 403 o 422.

        // Puoi accedere ai dati validati con $request->validated()
        $post = $request->user()->posts()->create($request->validated());

        return response()->json(new \App\Http\Resources\PostResource($post), 201);
    }
}

Questo rende il controller estremamente snello e focalizzato.

B. Per l'Autorizzazione (se non gestita dalla FormRequest o per logiche più complesse): Invece di fare affidamento su $this->authorize(), puoi rendere l'autorizzazione più esplicita:

  • Iniezione del Gate: public function __construct(Gate $gate) e poi $this->gate->authorize(...).
  • Uso del facade Gate: Gate::authorize(...).
  • Metodi del modello utente: $request->user()->can('update', $post).
  • Policy direttamente: Risolvere la policy dal container e chiamare i suoi metodi.
// Esempio di autorizzazione esplicita nel controller (se non si usa il trait)
namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate; // Uso del Facade

class ModernPostController extends Controller
{
    public function update(Request $request, Post $post)
    {
        // Autorizzazione esplicita usando il Facade Gate
        Gate::authorize('update', $post);
        // Alternativa: if ($request->user()->cannot('update', $post)) { abort(403); }

        // Validazione (qui inline per esempio, ma meglio una FormRequest)
        $validatedData = $request->validate([ /* ... */ ]);
        $post->update($validatedData);
        // ...
        return response()->json(/* ... */);
    }
}

Considerazioni sul Trait DispatchesJobs

Similmente, se i tuoi controller inviavano job usando $this->dispatch(), ora dovrai:

  • Aggiungere use Illuminate\Foundation\Bus\DispatchesJobs; al controller o alla tua classe base personalizzata.
  • Oppure, usare il facade Bus::dispatch(new MyJob(...)) o l'helper dispatch(new MyJob(...)), che rendono l'azione più esplicita.

Benefici del Refactoring per le Applicazioni della tua Impresa

Adottare la nuova struttura dei controller e approcci più espliciti per autorizzazione e validazione porta a:

  • Maggiore Esplicitezza e Chiarezza: è immediatamente chiaro quali funzionalità sta usando un controller.
  • Codice Più Pulito e Aderente ai Principi SOLID: i controller diventano più piccoli e con responsabilità meglio definite (principalmente orchestrazione), spingendo logica di validazione e autorizzazione in classi dedicate (Form Request, Policy). Questo si sposa perfettamente con l'obiettivo di spostare la logica di business e l'accesso ai dati in Service Layer e Repository dedicati.
  • Testabilità Migliorata: controller più piccoli e Form Request dedicate sono più facili da testare unitariamente.
  • Migliore Manutenibilità: codice più chiaro e disaccoppiato è più facile da far evolvere.
  • Allineamento con la Filosofia di Laravel: il framework si sta muovendo verso un approccio più minimale e configurabile programmaticamente, come dimostra anche la nuova struttura applicativa snella di Laravel 11/12.

Il ruolo del programmatore Laravel esperto

Affrontare un refactoring per adattarsi a questi cambiamenti strutturali del framework, specialmente in un'applicazione aziendale di grandi dimensioni, richiede attenzione ai dettagli e una buona comprensione delle implicazioni. Come programmatore Laravel esperto (puoi consultare la mia pagina Chi Sono per maggiori dettagli sulla mia esperienza), posso assistere la tua impresa a:

  • Valutare l'impatto di questi cambiamenti sulla tua base di codice.
  • Scegliere la strategia di refactoring più appropriata (uso esplicito dei trait, classe base custom, o adozione spinta di Form Request e Policy).
  • Eseguire il refactoring in modo efficiente e sicuro.
  • Aggiornare i test per riflettere le modifiche.

Questo cambiamento nella classe base Controller di Laravel è un invito a scrivere codice ancora più pulito ed esplicito. Abbracciare questa evoluzione è un passo verso applicazioni Laravel 12 più robuste, moderne e piacevoli da mantenere per il tuo business.

Se la tua impresa sta pianificando un aggiornamento a Laravel 11 o 12, o se semplicemente desideri modernizzare i tuoi controller Laravel 9/10, contattami per una consulenza e una strategia di refactoring su misura.

Ultima modifica: Mercoledì 12 Marzo 2025, alle 09:26