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 traitAuthorizesRequests
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 traitValidatesRequests
per validare i dati della richiesta HTTP in input.$this->dispatch($job)
: fornito dal traitDispatchesJobs
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'helperdispatch(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