Laravel 11

Indice dei contenuti

Laravel 11 – Guida Introduttiva al framework

Introduzione a Laravel 11

Laravel è un framework PHP open-source per lo sviluppo di applicazioni web, noto per la sua sintassi espressiva ed elegante. Come afferma la documentazione ufficiale, «Laravel is a web application framework with expressive, elegant syntax». L’obiettivo di Laravel è rendere lo sviluppo più piacevole e produttivo, fornendo strumenti e astrazioni che semplificano i compiti comuni (routing, accesso al database, autenticazione, ecc.) (GitHub ). Grazie a questo approccio, anche chi è alle prime armi può costruire applicazioni robuste senza dover reinventare da zero funzionalità di base.

Laravel 11, rilasciato nel 2024, continua la tradizione del framework introducendo miglioramenti e ottimizzazioni. Richiede PHP >= 8.2 e porta con sé alcune novità rilevanti, come un progetto iniziale più snello, un nuovo sistema WebSocket (Laravel Reverb) per comunicazioni in tempo reale, e varie semplificazioni nella configurazione e struttura delle cartelle (laravel-news.com) (laravel-news.com). Ad esempio, in Laravel 11 la struttura predefinita del progetto è stata alleggerita (meno file generati di default) e alcune componenti come i middleware di sistema e i kernel applicativi sono gestiti in modo diverso rispetto alle versioni precedenti (laravel-news.com). Queste modifiche non stravolgono l’uso quotidiano del framework per un principiante, ma riducono il “rumore” iniziale, lasciando solo il necessario per cominciare a costruire la propria applicazione.

In questa guida forniremo un tutorial passo-passo su Laravel 11, coprendo i concetti essenziali per iniziare a sviluppare. Ogni sezione spiegherà un aspetto chiave – dalla struttura dei file ai percorsi (routes), dai controller alle viste Blade, dall’accesso al database con Eloquent fino all’autenticazione e ai test. Al termine, estenderemo la guida introducendo Filament PHP, un potente toolkit per costruire pannelli di amministrazione in Laravel, spiegando cos’è, come integrarlo e usarlo (comprese le CRUD e la gestione di ruoli e permessi con Spatie Laravel-Permission).

Struttura del progetto Laravel 11

Quando si crea un nuovo progetto Laravel (es. con laravel new nomeprogetto oppure via Composer), si ottiene una struttura di cartelle già predisposta. Questa struttura rappresenta l’architettura MVC (Model-View-Controller) tipica di Laravel e organizza il codice in modo logico. In Laravel 11 lo scheletro è stato snellito rispetto al passato, ma le cartelle principali restano simili e hanno ruoli chiari:

Confronto Tra Laravel 10 E Laravel 11
(laravel-news.com) Confronto tra la struttura base di un progetto Laravel 10 (a sinistra) e Laravel 11 (a destra). Notiamo che Laravel 11 presenta meno file e cartelle predefinite: ad esempio la directory dei middleware di sistema non è più presente in app/Http (i middleware globali sono gestiti internamente) e il file Kernel.php è stato rimosso in favore di una configurazione nel bootstrap (laravel-news.com). Le restanti cartelle principali rimangono però organizzate in modo analogo, facilitando l’orientamento per chi ha usato versioni precedenti.
  • Root directory (cartella principale): contiene vari file di configurazione e avvio. Ad esempio, troviamo il file composer.json (gestione dipendenze), .env (configurazioni ambiente), artisan (console CLI di Laravel), ecc. Questi file sono in genere da non modificare spesso, tranne .env per impostare parametri come database o chiavi di API.
  • app/: qui risiede il codice applicativo. Per convenzione, la namespace base è App\. In Laravel 11, nella cartella app troviamo per default tre sottodirectory: Http/, Models/ e Providers/. In app/Http metteremo controllers, middleware e Form Request (richieste validate); in app/Models ci sono i modelli Eloquent (rappresentazione delle tabelle del database); in app/Providers ci sono i service provider dell’app (classi speciali che configurano servizi all’avvio – di solito non serve modificarle all’inizio). Da notare che rispetto a Laravel 10, la directory Middleware non è presente di default dentro Http e il file Http/Kernel.php è stato rimosso – Laravel 11 registra i middleware globali altrove (in bootstrap/app.php) e fornisce un controller base astratto che non estende nulla. Questa semplificazione significa che il progetto di partenza ha meno file, ma possiamo comunque aggiungere middleware personalizzati e altre classi con i comandi Artisan quando necessario.
  • bootstrap/: contiene principalmente app.php, che inizializza il framework. In Laravel 11 questo file configura provider, rotte e middleware globali in stile fluido. Nella sottocartella bootstrap/cache Laravel può mettere file cache generati (per accelerare il caricamento di rotte e configurazioni). Di solito non si interagisce molto con questa cartella, tranne che per eventualmente cancellare file di cache in fase di debug.
  • config/: contiene tutti i file di configurazione dell’applicazione (database, cache, mail, session, ecc.). Ogni aspetto ha un file PHP dedicato (ad es. config/app.php, config/database.php, config/mail.php, …). In Laravel 11, inizialmente questa cartella è quasi vuota (solo un .gitkeep), perché la maggior parte delle configurazioni di default sono ora consolidate nell’env. Puoi comunque pubblicare (generare) i file di configurazione standard con php artisan config:publish se desideri personalizzarli. Per un principiante, è consigliabile sfogliare questi file per familiarizzare con le opzioni disponibili, ma spesso non serve modificarli subito – Laravel funziona bene con le impostazioni di default, a patto di settare correttamente le variabili .env per database, chiavi, etc.
  • database/: include i file relativi al database, ad esempio le migration (in database/migrations), i seeder (in database/seeders) e le factory per generare dati fittizi (in database/factories). Se usi SQLite, puoi anche trovarvi un file database.sqlite. Le migration sono file PHP che definiscono le modifiche allo schema del database (tabelle, colonne, indici) e permettono di versionare il database insieme al codice. Ne parleremo nella sezione dedicata. In Laravel 11 i nomi dei file di migrazione sono stati semplificati (non più datati, ma con un ordinamento interno). La cartella database può essere estesa man mano che crei nuove migrazioni o altri file relativi ai dati.
  • public/: è la cartella pubblica esposta dal server web. Contiene il file index.php – il front controller che avvia l’app Laravel per ogni richiesta HTTP in ingresso – e asset pubblici (es. css/app.css, js/app.js se usi compilatori, o immagini). In sviluppo, con php artisan serve, questa cartella viene servita automaticamente. In produzione, il tuo host web (Apache/Nginx) dovrà puntare alla cartella public come document root. Public include anche file come favicon.ico e il file robots.txt. In Laravel 11, come nelle versioni precedenti, index.php è il punto d’entrata che inizializza l’autoloader Composer e l’app.
  • resources/: qui risiedono le risorse non compilate e le viste. Ad esempio la sottocartella views/ contiene i file Blade (.blade.php) che rappresentano l’output HTML delle pagine. Inoltre, resources/ è il posto dove mettere file di localizzazione (cartella lang/ per i testi multilingua) e file di asset sorgente (es. sass/ o js/ non ancora compilati, se usi Laravel Mix/Vite per processarli). In Laravel 11 trovi di default resources/views/welcome.blade.php (la vista di benvenuto iniziale), la cartella lang/en con i messaggi in inglese, e poco altro. Tutto ciò che riguarda la parte visuale della tua app (template Blade, email template, ecc.) vive qui.
  • routes/: contiene i file di definizione delle rotte (routes). Le rotte sono le URL e i relativi handler (controller o closure) che l’applicazione risponde. In Laravel 11, per impostazione predefinita, esistono due file di route: web.php e console.php. – web.php è per le rotte web tradizionali (quelle con stato, cookie, sessione, ecc., raggruppate dal framework nel middleware group “web”). Qui definirai la maggior parte delle rotte del tuo sito (pagine, form, ecc.). – console.php definisce i comandi Artisan personalizzati (rotte per la console, non HTTP). Se, ad esempio, vuoi creare un comando CLI custom (php artisan qualcosa), lo registri qui. Laravel 11 non include più di default il file api.php (per le rotte API REST) né channels.php (per i canali di broadcasting). Se vuoi aggiungere rotte API stateless, puoi creare il file routes/api.php eseguendo il comando php artisan install:api, che imposta anche Sanctum per l’autenticazione API. Allo stesso modo php artisan install:broadcasting creerà routes/channels.php per definire canali di broadcasting (usati con WebSocket e Laravel Echo). In sintesi, web.php e console.php ci sono sempre; api.php e channels.php sono opzionali (opt-in) in Laravel 11.
  • storage/: qui Laravel memorizza file generati dall’app durante il runtime. È suddivisa in sottocartelle: app/ (puoi usarla per file generati dalla tua applicazione, ad esempio upload degli utenti), framework/ (cache di template Blade compilati, cache di configurazione/rotte, sessioni file, ecc.), e logs/ (log dell’applicazione, ad es. laravel.log). In sviluppo vedrai crescere storage/logs/laravel.log con output di errori o info di debug. Importante: storage/app/public è pensata per contenere file che devono essere pubblicamente accessibili; per renderli realmente accessibili via web, Laravel ti fa creare un link simbolico public/storage che punta a storage/app/public (basta eseguire php artisan storage:link). Così, se un utente carica un’immagine che salvi in storage/app/public/images/qualcosa.jpg, questa sarà raggiungibile via URL come /storage/images/qualcosa.jpg. La cartella storage non va resa pubblica direttamente (contiene anche dati riservati); solo attraverso il symbolic link controllato su public/storage vengono esposti i file voluti.
  • tests/: contiene i test automatizzati dell’applicazione. Laravel include già due sottocartelle: Feature (test funzionali) e Unit (test unitari), ciascuna con un esempio (ExampleTest.php). I test unitari verificano porzioni molto isolate di codice – ad esempio una singola classe o metodo in isolamento, senza caricare l’intero framework. I test di feature invece coprono flussi più ampi, ad esempio simulando richieste HTTP complete o l’interazione di più componenti, per assicurarsi che il sistema nel complesso funzioni come atteso. Laravel viene fornito con tutto il necessario per eseguire i test: un file phpunit.xml già configurato, supporto integrato a PHPUnit e al più recente framework Pest PHP. Puoi eseguire i test con il comando php artisan test (che mostra un output dettagliato e colorato) o direttamente con vendor/bin/phpunit. Vedremo più avanti una panoramica sulla scrittura di test.
  • vendor/: è la directory delle dipendenze Composer. Tutte le librerie di terze parti (incluso lo stesso framework Laravel) risiedono qui, organizzate per pacchetti. Non dovresti mai modificare manualmente i file in vendor – vengono gestiti da Composer. Per dare un’idea, in vendor/laravel/ c’è il core del framework Laravel, in vendor/symfony/ ci sono componenti Symfony usati dal framework, e così via. Nella root c’è anche composer.lock che blocca le versioni installate. In caso di problemi, talvolta cancellare vendor/ e rifare composer install rigenera tutto. Ma normalmente, non toccare questa cartella; concentrati sul codice in app/ e resources/ per sviluppare la tua applicazione.

Inoltre, nella root trovi altri file come .env.example (un esempio di file di ambiente – da copiare in .env e riempire con i propri parametri), .gitignore (imposta cosa escludere dal controllo versione, ad esempio vendor e .env), README.md (breve documentazione del progetto). Questa panoramica copre i componenti principali di un progetto Laravel. In sintesi, Laravel offre una struttura chiara e modulare, pensata per scalare sia in piccoli progetti che in applicazioni di grandi dimensioni. Man mano che utilizzerai i comandi artisan make:... per creare componenti (controller, modelli, ecc.), vedrai comparire nuove cartelle (ad es. app/Http/Controllers si popolerà con i tuoi controller, app/Console verrà creata se fai un comando Artisan custom, ecc.) . Laravel non impone restrizioni rigide su dove mettere le classi – segue PSR-4 autoloading, quindi finché Composer sa dove caricare una classe, il framework la troverà – ma seguire le convenzioni ti aiuterà a mantenere il progetto organizzato.

Routing e Controller

Uno dei primi aspetti pratici nello sviluppo Laravel è definire le rotte (routes), cioè collegare gli URL alle logiche applicative da eseguire. In Laravel 11, come visto, le rotte HTTP si definiscono tipicamente nel file routes/web.php (per le pagine e API con stato) o in routes/api.php se hai abilitato le API REST stateless. Ogni rotta si registra usando la facciata Route. Esempio minimale in web.php:

Route::get('/', function () {
    return view('welcome');
});

Questo definisce che una richiesta GET all’URL / (home page) esegue la closure fornita, la quale in questo caso restituisce la vista di benvenuto. Laravel mette a disposizione metodi per tutti i verbi HTTP comuni: Route::post(), Route::put(), Route::delete(), ecc., oltre a metodi speciali come Route::match() (per combinare più metodi) o Route::any() (qualsiasi verbo).

Spesso, anziché definire la logica nella closure, si delega l’azione a un controller. Un controller è una classe PHP (tipicamente in App\Http\Controllers) che raggruppa logiche correlate per una “risorsa” o sezione del sito. Ad esempio, potremmo avere un ProductController con metodi per mostrare la lista prodotti, il dettaglio di un prodotto, creare, aggiornare, cancellare, ecc. Per collegare una rotta a un metodo di controller, si usa la sintassi con array o la notazione Controller@metodo (nelle vecchie versioni):


Route::get('/prodotti', [App\Http\Controllers\ProductController::class, 'index']);

Questo registra la rotta GET /prodotti assegnandola al metodo index() del ProductController. Alternativamente, Route::get('/prodotti', 'App\Http\Controllers\ProductController@index') è un modo equivalente (sintassi stringa). Il risultato è che quando un utente visita /prodotti, Laravel istanzia ProductController e chiama il metodo index, e la risposta generata verrà inviata al browser.

Per organizzare in modo RESTful, Laravel offre un meccanismo veloce: le resource controller. Un resource controller implementa automaticamente le convenzioni CRUD (Create, Read, Update, Delete). Puoi creare un controller di risorsa con Artisan: ad es. php artisan make:controller ProductController --resource genererà una classe con una serie di metodi stub (index, create, store, show, edit, update, destroy). Nel file di rotte, puoi registrare tutte le rotte CRUD insieme con:

Route::resource('prodotti', ProductController::class);

Questa singola dichiarazione crea automaticamente tutte le rotte necessarie per le azioni su “prodotti” (elencate in dettaglio nella documentazione: index per GET /prodotti, create per GET /prodotti/create, store per POST /prodotti, show per GET /prodotti/{id}, edit per GET /prodotti/{id}/edit, update per PUT/PATCH /prodotti/{id}, destroy per DELETE /prodotti/{id}). I nomi delle rotte e i metodi HTTP sono impostati secondo convenzione. Come riportato nei documenti, «This single route declaration creates multiple routes to handle a variety of actions on the resource. The generated controller will already have methods stubbed for each of these actions.». Ciò significa che definendo Route::resource, Laravel si aspetta che il controller abbia i metodi necessari (che, se hai usato --resource, sono già presenti come stub vuoti) e li collega alle URL appropriati.

Per applicazioni API REST senza interfaccia Blade, spesso si usano rotte nel file api.php (che rispondono tipicamente al sottodominio api. o prefisso /api e sono pensate senza stato di sessione, utilizzando token per l’autenticazione). In questo contesto, si potrebbe usare Route::apiResource('prodotti', ProductController::class) che è simile a resource ma esclude le rotte create/edit (non necessarie per API REST JSON). In Laravel 11, ricorda di eseguire php artisan install:api se vuoi abilitare facilmente tutto il necessario per le API (crea il file api.php e configura Sanctum per i token).

Le rotte possono includere parametri dinamici (es. /prodotti/{id}) che diventano parametri del metodo del controller. Laravel supporta Route Model Binding: se nel controller accetti un modello tipizzato, ad esempio show(Product $product), Laravel risolverà automaticamente {id} come un’istanza di Product dal database (se esiste, altrimenti 404). Questo è molto comodo perché evita di scrivere codice per fare Product::find($id) manualmente – è tutto automatico.

Definire Middleware sulle rotte

Le middleware (intermediari) sono filtri che possono essere eseguiti prima o dopo l’handling di una rotta, per svolgere compiti come autenticazione, logging, cache, etc. Laravel ne include varie di default (ad esempio auth, verified, throttle, guest per redirezionare utenti autenticati, csrf per protezione CSRF, ecc.). Puoi assegnare una middleware a una rotta o a un gruppo di rotte usando il metodo middleware() nella definizione della route (laravel.com). Ad esempio:

Route::get('/dashboard', DashboardController::class)
    ->middleware('auth');

In questo modo, solo gli utenti autenticati potranno accedere alla rotta /dashboard – la middleware auth verificherà la presenza di una sessione valida e, in caso contrario, reindirizzerà al login (laravel.com). Puoi anche applicare più di una middleware separandole in un array o con più chiamate middleware. Le middleware possono essere globali (cioè applicate a tutte le richieste – Laravel ne imposta alcune globali come quelle per manutenzione e trimming input) o di gruppo (ad es. il gruppo “web” applica una serie di middleware comuni a tutte le rotte web, definito internamente dal framework). In Laravel <=10 c’era un file Http/Kernel.php dove registrare le middleware globali e i gruppi; in Laravel 11 questo avviene in bootstrap/app.php, ma per molti scopi pratici cambiare le middleware globali non è necessario per iniziare (laravel.com).

Creare un Controller

Per creare un nuovo controller, si utilizza Artisan. Esempio: php artisan make:controller MioController genererà app/Http/Controllers/MioController.php. Un controller base in Laravel 11, per impostazione, è una semplice classe che (nel caso di controller resource) può essere lasciata senza estendere alcuna classe base – Laravel 11 fornisce un abstract App\Http\Controllers\Controller vuoto come riferimento, ma non è obbligatorio usarlo. In pratica puoi scegliere di non far estendere nulla ai tuoi controller, oppure farli estendere da App\Http\Controllers\Controller (che magari tu potresti modificare per aggiungere common logic o middleware costruttori). Nelle versioni precedenti i controller estendevano la classe base fornita dal framework (Illuminate\Routing\Controller tramite l’alias in App\Http\Controllers\Controller). La differenza in Laravel 11 è perlopiù accademica: i tuoi controller funzioneranno anche senza extends, e sei libero di aggiungere traits se ti servono funzionalità (ad esempio c’erano traits come DispatchesJobs, ValidatesRequests per aggiungere metodi utili).

Un controller può avere qualsiasi metodo pubblico; Laravel lo considera “action” se lo colleghi in una rotta. Puoi anche utilizzare l’injection delle dipendenze nel costruttore o nei metodi: Laravel risolverà automaticamente i parametri, ad esempio puoi chiedere in un metodo di controller l’istanza di Request $request per ottenere la richiesta HTTP corrente, o altri servizi (es. un repository) se impostato correttamente nel service container.

Inoltre, i controller possono essere organizzati in sottocartelle (namespace). Se hai molte funzionalità, ad esempio area Admin separata, potresti avere App\Http\Controllers\Admin\... e definire rotte come Route::namespace('App\Http\Controllers\Admin')->group(...); oppure specificare il full namespace quando registri le rotte.

Riassumendo la sezione: Routing – definisci URL e metodi HTTP e li colleghi a una logica (closure o controller). Controller – classi PHP dove implementi la logica per gestire le richieste, raggruppando codice correlato. Utilizza resource controller e route resource per aderire allo stile RESTful rapidamente (laravel.com). Usa le middleware per proteggere o modificare il comportamento delle rotte (ad esempio, per richiedere autenticazione su certe pagine) (laravel.com). Nei prossimi paragrafi approfondiremo cosa sono esattamente le middleware e come crearne di proprie.

Middleware

In Laravel, una middleware è un componente che “in mezzo” al ciclo di richiesta/risposta, in grado di ispezionare, filtrare o manipolare le richieste HTTP entranti e le risposte usciti. È come un guardiano per le rotte: può decidere se far proseguire la richiesta al controller successivo oppure bloccarla/modificarla. La definizione ufficiale: «Middleware provide a convenient mechanism for inspecting and filtering HTTP requests entering your application» (laravel.com). Un esempio tipico: la middleware di autenticazione (auth) controlla che l’utente sia loggato; se non lo è, reindirizza alla pagina di login, altrimenti lascia passare la richiesta al controller protetto (laravel.com). Allo stesso modo, la middleware CSRF verifica i token anti-csrf nei form POST, la middleware di logging può registrare info su ogni richiesta, e così via.

Laravel include alcune middleware di sistema già pronte, come: Authenticate (auth), EncryptCookies, StartSession, VerifyCsrfToken, ThrottleRequests (limita il numero di richieste per prevenire abusi), EnsureEmailIsVerified, ecc. In Laravel <=10 queste erano visibili in app/Http/Middleware come file singoli. In Laravel 11, tali middleware core non sono più presenti nel progetto (risiedono internamente nel framework), ma il loro effetto rimane disponibile e configurabile. Ad esempio, per escludere un cookie dalla cifratura, ora si agisce nel boot di un service provider invece che modificare EncryptCookies locale. Per l’utente alle prime armi, questo significa solo che vede meno file “misteriosi” nel proprio app/Http/Middleware all’inizio.

Creare e usare una Middleware personalizzata

Puoi creare nuove middleware per esigenze specifiche. Artisan aiuta: con il comando php artisan make:middleware NomeMiddleware viene generata una classe in app/Http/Middleware/NomeMiddleware.php. Aprendo questo file troverai uno scheletro con un metodo handle(Request $request, Closure $next) dove inserire la tua logica. Ad esempio, supponiamo di voler bloccare l’accesso a una certa rotta se nel query string non c’è un token specifico:

public function handle(Request $request, Closure $next): Response
{
    if ($request->input('token') !== 'mio-segreto') {
        return redirect('/home');
    }
    return $next($request);
}

Questo corrisponde all’esempio generato di default. In pratica, controlliamo un parametro token della richiesta; se non corrisponde al valore atteso, invece di procedere oltre restituiamo un redirect alla home, interrompendo il flusso. Se invece il controllo passa, chiamiamo $next($request) per passare la richiesta alla prossima “layer” (che potrebbe essere un’altra middleware o infine il controller). Concettualmente, puoi immaginare le middleware come strati di una cipolla: la richiesta entra e attraversa vari strati (ad esempio, il trimming degli input, poi la gestione sessione, poi la tua middleware custom, poi il controller). La risposta torna indietro attraversando eventualmente gli strati in uscita (alcune middleware hanno anche un metodo terminate per agire dopo l’invio risposta).

Una volta creata la tua middleware, devi registrarla e applicarla. In Laravel 11 la registrazione (assegnare un alias o inserirla nei gruppi global/web/api) avviene in bootstrap/app.php o tramite metodi di configurazione di Application – un po’ diverso dal passato kernel. Un modo semplice: puoi registrare la tua middleware come alias nel route group specifico. Esempio, se la tua EnsureTokenIsValid deve essere usata su certe rotte, puoi fare:

Oppure potresti volerla come globale (così che impatti su tutte le richieste). In Laravel 11 per aggiungere una middleware globale devi modificare la chiamata Application::configure()->withMiddleware(...) in bootstrap/app.php. Ad esempio, come indicato negli snippet di documentazione, potresti fare $middleware->web(append: \App\Http\Middleware\EnsureTokenIsValid::class) dentro la closure di withMiddleware. Ciò aggiunge la tua middleware custom alla fine dello stack web. Tuttavia, per i principianti è spesso sufficiente applicare middleware tramite i metodi delle rotte (come visto con ->middleware(‘alias’)). Se vuoi definire un alias per una tua middleware (ad esempio chiamarla 'token.valid' anziché usare il nome classe ogni volta), nelle vecchie versioni avresti aggiunto nel Kernel; in Laravel 11 potresti dover usare un Service Provider o metodo dedicato (es. c’è un metodo Route::aliasMiddleware() in Laravel 10, probabilmente ancora disponibile, da chiamare magari in routes/web.php o in un provider).

Riassumendo: Middleware è il modo di Laravel di centralizzare certe logiche cross-cutting (autorizzazione, sicurezza, logging, ecc.). La coda di middleware globale include già elementi che non vedi direttamente nel codice in Laravel 11 (perché integrati), ma continua a funzionare come prima. Puoi scrivere middleware personalizzate usando artisan make:middleware, implementare la logica nel metodo handle, e poi usarle assegnandole alle rotte appropriate (o rendendole globali se necessario). Laravel fornisce inoltre alcune middleware utili come redirectIfAuthenticated (sposta utenti loggati fuori dalla pagina di login), ensureEmailIsVerified (controlla email verificata) ecc., che tipicamente vengono utilizzate nel contesto dell’autenticazione base – spesso già incluse nei stub dei controller di autenticazione dei kit come Breeze.

Views e Blade

Nel pattern MVC, le View sono ciò che l’utente finale vede – tipicamente pagine HTML. In Laravel le view sono spesso scritte usando Blade, il motore di template fornito dal framework. Blade consente di inserire codice PHP e costrutti logici nei file di template in modo pulito e con una sintassi semplificata. Come descrive la documentazione, «Blade is the simple, yet powerful templating engine that is included with Laravel. Unlike some PHP templating engines, Blade does not restrict you from using plain PHP code in your templates.». In pratica Blade offre una serie di direttive (iniziano con @) e la comoda sintassi {{ ... }} per fare echo di variabili in sicurezza (escaping automatico HTML).

Le viste Blade hanno estensione .blade.php e risiedono di solito in resources/views. Possono essere organizzate in sottocartelle. Ad esempio potresti avere resources/views/prodotti/index.blade.php per la lista prodotti, resources/views/prodotti/show.blade.php per la pagina dettaglio, ecc. Per renderizzare una view dal controller, si usa la funzione helper view('nomeview', $dati), dove 'nomeview' è il percorso del file senza .blade.php (le slash delle directory diventano punti nel nome – ad es. view('prodotti.index') cerca resources/views/prodotti/index.blade.php). Il secondo parametro $dati è opzionale ed è un array di variabili da passare alla view.

Sintassi Blade di base

  • Stampa variabili: utilizzare {{ $variabile }} dentro la view. Ad esempio, se passi ['nome' => 'Mario'] alla view, puoi fare Hello, {{ $nome }} e Blade stamperà “Hello, Mario”. Queste doppie graffe eseguono l’escape dei caratteri HTML per sicurezza (quindi sono adatte per contenuti testuali). Se vuoi stampare HTML “raw” fornito da una variabile (attenzione ai rischi XSS!), puoi usare {!! $variabile !!}.
  • Direttive di controllo: Blade fornisce costrutti come @if ... @endif, @foreach ... @endforeach, @for, @while, ecc., che ricalcano quelli di PHP ma senza dover chiudere con endforeach; etc. Esempio: @if(count($prodotti) > 0) <ul> @foreach($prodotti as $prod) <li>{{ $prod->nome }}</li> @endforeach </ul> @else <p>Nessun prodotto trovato.</p> @endif Questo codice mostrerà una lista <ul> di prodotti se l’array $prodotti (passato dal controller) non è vuoto, altrimenti un messaggio alternativo. Blade supporta anche @elseif e la sintassi rapida @unless (che è un if negato).
  • Layout e sezioni: Una delle funzionalità più potenti di Blade è la gestione dei layout tramite direttive @extends e @section. Puoi definire un layout base (es. layouts/app.blade.php) che contenga la struttura HTML comune (header, footer, ecc.) e degli spazi segnaposto (sezioni) in cui le viste figlie inseriranno contenuto. Nel layout definirai delle sezioni con @yield('nomeSezione'). Nella view figlia, all’inizio metterai @extends('layouts.app') per indicare quale layout ereditare, e poi definisci il contenuto delle sezioni con: @section('title', 'Titolo Pagina') @section('content') <h1>Benvenuto</h1> <p>Questo è il contenuto della pagina.</p> @endsection Il primo @section('title', '...') usa la forma breve per inserire direttamente una stringa nella sezione (utile per titoli). Il secondo mostra la sintassi completa, dove tutto il blocco tra @section ... @endsection verrà catturato e passato al layout. Nel layout layouts.app, avrai ad esempio nell’head: <title>MySite - @yield('title')</title> e nel body magari: <div class="container">@yield('content')</div>. Blade provvederà a unire il layout con la view figlia, producendo l’HTML finale completo. Questo approccio evita duplicazioni: puoi avere un unico file di layout con menu e footer, e tante viste più piccole che si inseriscono nel suo content.
  • Include e componenti: Puoi includere altre view dentro una view usando @include('partial.nome') (inserisce il contenuto di partial/nome.blade.php). Utile per frammenti riutilizzabili (es. una navbar, un form comune, ecc.). Laravel offre anche components Blade che sono una versione più strutturata degli include (puoi creare componenti con logica associata). Da principiante, @include è spesso sufficiente.
  • Altre funzionalità: Blade ha tante piccole comodità: @csrf inserisce un input hidden con token CSRF (da mettere nei form POST), @method('PUT') per spoofare metodi HTTP in form (poiché i form HTML supportano solo GET e POST), @json($var) per stampare una variabile come JSON, direttive per autorizzazioni (@can, @guest, @auth), e perfino la gestione di componenti UI (Blade component & slot). Tutto questo è documentato nei manuali Blade.

Una cosa importante: Blade non impone restrizioni sul PHP – puoi comunque usare codice PHP puro se necessario (<?php ... ?>). Ma nella pratica quasi tutto può essere fatto con le sue direttive. Blade compila i template in PHP puro la prima volta (li trovi in storage/framework/views compilati) e li cachea, quindi le performance sono buone; inoltre il suo utilizzo è facoltativo. Se per qualche motivo volessi usare un altro sistema di template o puro PHP, potresti, ma Blade è talmente integrato e comodo che raramente c’è ragione di evitarlo.

Esempio di flusso Controller-View

Mettiamo insieme routing, controller e view con un piccolo esempio: supponiamo di voler mostrare una lista di articoli in una pagina.

  • Nel controller (es. App\Http\Controllers\ArticleController): public function index() { $articoli = Article::all(); return view('articoli.index', ['articoli' => $articoli]); } Qui recuperiamo tutti gli articoli dal modello Eloquent Article (vedremo Eloquent più avanti) e passiamo i dati alla view articoli.index.
  • Nel file routes/web.php: Route::get('/articoli', [ArticleController::class, 'index']); Così che visitando http://mysite.test/articoli si attivi il metodo sopra.
  • Nella view Blade resources/views/articoli/index.blade.php: @extends('layouts.app') @section('title', 'Elenco Articoli') @section('content') <h1>Articoli</h1> @if($articoli->isEmpty()) <p>Nessun articolo presente.</p> @else <ul> @foreach($articoli as $articolo) <li> <a href="{{ url('/articoli', $articolo->id) }}">{{ $articolo->titolo }}</a> </li> @endforeach </ul> @endif @endsection In questa view estendiamo un layout generico layouts.app. Impostiamo la sezione titolo e poi nella sezione content elenchiamo gli articoli. Usiamo {{ url('/articoli', $articolo->id) }} per creare il link all’articolo (in alternativa, potevamo definire una route nominata e usare route('articoli.show', $articolo)).
  • Layout resources/views/layouts/app.blade.php (semplificato): <!DOCTYPE html> <html lang="it"> <head> <meta charset="utf-8"> <title>@yield('title') - MyBlog</title> <link rel="stylesheet" href="{{ asset('css/app.css') }}"> </head> <body> <nav>...menu...</nav> <div class="container"> @yield('content') </div> <footer>...footer...</footer> </body> </html> Qui includiamo un CSS (ipotetico) e definiamo le sezioni title e content.

Quando un utente apre la pagina, Laravel esegue il controller, che restituisce la view. Blade fonde articoli.index col layout app e produce l’HTML finale con la lista di articoli. Questo flusso separa nettamente i compiti: la logica in controller, la presentazione in view, e consente di manutenere e far evolvere l’applicazione in modo pulito.

Eloquent ORM e Models

Laravel offre un potente strumento per interagire con il database: l’ORM Eloquent. ORM sta per Object-Relational Mapping: in pratica, Eloquent ti consente di rappresentare le tabelle del database come classi PHP (models) e di lavorare con i dati come oggetti anziché scrivendo direttamente SQL. Ogni classe modello Eloquent corrisponde a una tabella, e ogni istanza della classe corrisponde a una riga (record) di quella tabella. Questo implementa il pattern Active Record, dove le istanze contengono sia i dati che la logica per interagire con quei dati (salvataggio, aggiornamento, cancellazione).

Definizione di un Model

Per creare un model, si usa Artisan: ad esempio php artisan make:model Article genererà app/Models/Article.php. All’interno, questa classe estende Illuminate\Database\Eloquent\Model e Laravel la configurerà automaticamente per puntare alla tabella articles (noto: Eloquent pluralizza il nome del modello per cercare la tabella, ma puoi sovrascrivere il nome con una proprietà $table). Nel modello puoi definire quali colonne sono fillable (assegnabili in massa), eventuali attributi nascosti, cast di tipo, ecc., ma inizialmente non è necessario toccare nulla. Se esegui il comando con -m (--migration), Laravel creerà anche una migrazione boilerplate per la tabella associata.

Una volta definito il modello, puoi iniziare a usarlo. Ad esempio, se hai una tabella articles con colonne id, title, content, etc., e un model Article, potrai fare cose come:

  • Retrieve (leggere dati): $all = Article::all(); // prende tutti i record (Collection di Article) $primo = Article::find(1); // trova per chiave primaria (id=1) oppure null se non trovato $filtro = Article::where('categoria', 'news')->orderBy('created_at', 'desc')->get(); Eloquent fornisce query builder fluenti: ad esempio where()->orderBy()->get() come sopra costruisce una query SELECT con WHERE e ORDER ed esegue il get (ritorna una Collection di modelli). Puoi aggiungere più filtri: Article::where('status','pubblicato')->where('utente_id', 5)->get(). Puoi anche ottenere un singolo risultato con first() invece di get.
  • Create (inserire): $art = new Article; $art->title = "Nuovo articolo"; $art->content = "Testo..."; $art->save(); Questo istanzia un modello, setta attributi e chiama save() che fa INSERT. Oppure in modo rapido: Article::create([ 'title' => 'Titolo', 'content' => 'Lorem ipsum', ]); (Per usare create(), devi definire nel model la proprietà $fillable o $guarded per la sicurezza Mass Assignment, altrimenti Laravel protegge da inserimenti di massa non autorizzati).
  • Update (aggiornare): $art = Article::find(1); $art->title = "Titolo modificato"; $art->save(); Oppure in un colpo: $art->update(['title' => 'Nuovo titolo']);.
  • Delete (cancellare): $art = Article::find(1); $art->delete(); Oppure Article::destroy(1); per cancellare direttamente per id (o passare un array di id). Se hai definito il soft delete sul modello (trait SoftDeletes), il delete eseguirà un soft delete impostando timestamp di cancellazione invece di rimuovere la riga.

Eloquent rende queste operazioni semplici e piacevoli, come dicono i documenti, “Laravel includes Eloquent, an object-relational mapper (ORM) that makes it enjoyable to interact with your database” . Sotto il cofano, quando interagisci con Eloquent, le query SQL vengono eseguite usando il database configurato (MySQL, Postgres, SQLite, ecc.) ma tu lavori solo con oggetti PHP.

Convenzioni Eloquent

Eloquent segue convenzioni (che puoi personalizzare se serve):

  • Nome tabella plurale inglese del modello (Model User -> tabella users). Se difforme, specifica protected $table = 'nome_tabella';.
  • Chiave primaria auto-incrementale id per default. Se hai un PK differente, definisci $primaryKey e $keyType. Se non è autoincrement, imposta $incrementing = false.
  • Colonne created_at e updated_at gestite automaticamente (timestamp). Puoi disattivarle con public $timestamps = false; nel model, o personalizzare i nomi con const CREATED_AT, UPDATED_AT.
  • Nome della foreign key in relazioni: di default modello padre + _id. Esempio: tabella posts ha user_id che referenzia users.id.

Relazioni tra modelli

Uno dei punti forti di Eloquent è la gestione delle relazioni (1-1, 1-N, N-N, polimorfiche). Puoi definire metodi nel modello che restituiscono relazioni. Ad esempio, in un modello User puoi avere:

public function posts() {
    return $this->hasMany(Post::class);
}

Questo definisce che User ha molti Post (uno-a-molti), assumendo che nella tabella posts c’è user_id. Poi nel codice potrai fare $user->posts per ottenere la Collection dei post di quell’utente. Viceversa, nel modello Post:

public function user() {
    return $this->belongsTo(User::class);
}

così $post->user restituirà l’User proprietario. Le relazioni Eloquent permettono di scrivere query annidate, ad esempio $user->posts()->where('published', true)->get() (filtra i post dell’utente corrente), oppure utilizzare lazy loading e eager loading. Eager loading: se prevedi di accedere a $post->user per tanti post, meglio recuperare tutto in un colpo con Post::with('user')->get() per evitare il problema N+1. Altre relazioni:

  • hasOne (uno a uno),
  • belongsTo (inversa di hasOne/Many),
  • belongsToMany per relazioni molti-a-molti (richiede tabella pivot, che Laravel convenzionalmente chiama in ordine alfabetico dei modelli, es. post_user),
  • relazioni polimorfiche (un modello figlio che può appartenere a più modelli genitore di tipo diverso – es. un modello Comment potrebbe morphTo sia Post che Video).

Grazie alle relazioni, puoi navigare il tuo modello come oggetti collegati: es. $post->comments (1-N), $comment->commentable (polimorfico per trovare se il commento è su un post o altro), e anche definire relazioni nidificate (es. “post di un utente appartenente a un team” ecc.). Questo rende il codice più espressivo e coerente con il dominio dell’applicazione, senza scrivere join manuali.

Altre caratteristiche di Eloquent

  • Casts e Accessors/Mutators: puoi definire nel modello come convertire automaticamente attributi da/verso tipi PHP. Ad esempio protected $casts = ['is_admin' => 'boolean'] farà sì che $user->is_admin sia sempre un boolean (Laravel cast da tinyint 0/1). Laravel 11 introduce i cast definiti come metodi (ad es. metodo casts() invece di proprietà), ma concettualmente svolgono lo stesso ruolo. Puoi anche definire accessor (getNomeAttribute) o mutator (setNomeAttribute) per manipolare valori in lettura/scrittura.
  • Scopes: metodi che definiscono query ricorrenti riutilizzabili (es. public function scopePopular($query) { return $query->where('visite', '>', 1000); } permetterà di fare Article::popular()->get()).
  • Mutators di relazione (touch, update timestamps relazioni), eventi Eloquent (creating, updating, etc., puoi attaccare observer o usare trait come Notifiable per notifiche).
  • Database Transactions, Locking: Eloquent supporta transazioni (via DB facade) e lock ottimistici (colonna version) se necessario.

In generale, Eloquent mira a fornire un’interfaccia fluida e leggibile per la stragrande maggioranza delle operazioni sul database, con performance adeguate. In casi di query molto complesse, puoi sempre usare il Query Builder (che Eloquent stesso utilizza sotto al cofano) o ricorrere a chiamate SQL grezze (facade DB::select, etc.). Ma inizialmente, Eloquent coprirà tutte le necessità comuni.

Per riassumere, Models in Laravel rappresentano le entità della tua applicazione legate al database. Sono classi in app/Models che estendono Eloquent. Tramite esse:

  • Definisci come l’app mappa sul database (tabelle, chiavi).
  • Incapsuli logica di business legata ai dati (es: metodi tipo calculateDiscount() dentro Product model).
  • Interagisci con i dati usando metodi forniti dall’ORM invece di scrivere SQL manuale.

Laravel ha una documentazione estesa su Eloquent, e giustamente: è uno dei motivi per cui tanti sviluppatori amano Laravel, perché consente di concentrarsi sulla logica senza dover continuamente scrivere query SQL, rendendo lo sviluppo più veloce e meno incline a errori di sintassi SQL o di sicurezza (le query parametrizzate in Eloquent prevengono SQL injection di default, ecc.).

Migration e Seeder

Come accennato, Laravel incoraggia un flusso di lavoro dove anche la struttura del database è sotto controllo del codice sorgente. Questo avviene tramite le migrazioni (migrations) e i seeder.

Migrations (Migrazioni del Database)

Le migrazioni sono file PHP che descrivono modifiche allo schema del database (creazione/modifica di tabelle, indici, ecc.) e possono essere eseguiti in sequenza per portare il database da uno stato all’altro. Pensale come la “version control” per il database: «Migrations are like version control for your database, allowing your team to define and share the application’s database schema definition.». Anziché dire a voce “aggiungi una colonna X alla tabella Y” a un teammate, scrivi una migrazione che aggiunge quella colonna; quando lui eseguirà la migrazione, il suo database verrà aggiornato automaticamente a quella modifica.

In Laravel, ogni migrazione è una classe che estende Migration e tipicamente ha due metodi: up() (da eseguire per applicare la migrazione) e down() (da eseguire per tornare indietro, ovvero rollback). Le migrazioni vivono in database/migrations e il loro nome file inizia con timestamp per l’ordinamento (Laravel 11 li genera con un prefisso numerico non legato alla data corrente per semplificare).

Creare una migrazione: usare php artisan make:migration crea_tabella_xyz. Ad esempio:

php artisan make:migration create_articles_table

Questo genererà un file tipo 2025_03_23_150000_create_articles_table.php in database/migrations/. All’interno, troverai uno scheletro:

public function up()
{
    Schema::create('articles', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->text('content');
        $table->timestamps();
    });
}

public function down()
{
    Schema::dropIfExists('articles');
}

Laravel cerca di indovinare dal nome (“create_X_table”) e auto-riempie alcune cose – come la chiamata a Schema::create con le colonne comuni id e timestamps. Sta a te aggiungere eventuali colonne aggiuntive e modificarlo secondo le necessità. In questo esempio stiamo creando una tabella articles con un ID auto-incrementale (bigint) e colonne title (varchar 255) e content (TEXT), più i timestamp created_at e updated_at.

Eseguire le migrazioni: una volta scritte le migrazioni desiderate, lancia php artisan migrate. Laravel eseguirà tutte le migrazioni non ancora eseguite (tiene traccia di quelle già fatte in una tabella migrations). Dopo il comando, troverai la tua tabella articles nel database. Se qualcosa va storto, puoi fare rollback con php artisan migrate:rollback (che esegue i metodi down delle ultime migrazioni eseguite, tipicamente invertendo l’ultimo batch).

Modificare tabelle esistenti: se vuoi aggiungere una colonna a una tabella già creata, creerai una nuova migrazione (es. add_published_to_articles_table) e dentro up() userai Schema::table('articles', ...) con $table->boolean('published')->default(false) ad esempio, e nel down il corrispondente dropColumn. Poi artisan migrate eseguirà questa nuova migrazione e modificherà la tabella. In questo modo, ogni modifica allo schema è registrata cronologicamente. Laravel fornisce un’ampia gamma di tipi di colonne (string, text, integer, bigInteger, foreignId per chiavi esterne, timestamps, etc.) e metodi per indici e chiavi esterne (ad es. $table->foreignId('user_id')->constrained(); crea colonna e vincolo esterno automaticamente). Nota: per alcune operazioni avanzate (es. modificare tipo di una colonna) potrebbe essere necessario il package doctrine/dbal.

Migrazioni di partenza: quando crei un progetto Laravel, alcune migrazioni di base potrebbero essere incluse (ad es. per utenti, password reset tokens, ecc., a seconda se hai usato uno starter kit). In Laravel 11 lo scheletro di default include solo due file di migrazione generici (invece di quelli datati 2014_10_12 etc.), e se non usi sistemi di autenticazione, potresti non averne altre. Comunque, se ad esempio installi Laravel Breeze (che vedremo più avanti), verranno aggiunte migrazioni per creare la tabella users, password_resets, etc.

In generale, usa le migrazioni per ogni cambiamento di schema: così mantenere coerenza tra ambienti (dev, staging, prod) è molto più facile. Puoi anche generare un file “schema dump” per ridurre il numero di migrazioni se diventano troppe col tempo (comando php artisan schema:dump), ma quello è un dettaglio avanzato.

Seeding (Popolamento dati di prova)

I seeder sono classi pensate per inserire dati di default o di test nel database. Ad esempio, potresti voler precaricare una lista di categorie, o creare utenti fittizi per sviluppo, o riempire tabelle di lookup (es. province, etc.). Laravel permette di definire questi inserimenti in classi di seeder e poi eseguirli facilmente.

Un seeder si crea con php artisan make:seeder NomeSeeder. Verrà generato in database/seeders/NomeSeeder.php con un metodo run(). Dentro run() puoi scrivere codice Eloquent/DB per inserire i dati. Esempio:

public function run()
{
    // Creazione di categorie di esempio
    Category::create(['name' => 'Tech']);
    Category::create(['name' => 'Lifestyle']);
}

Puoi usare direttamente i model Eloquent o anche la facade DB per eseguire query manuali. Tipicamente userai i model perché sono comodi.

Laravel include un seeder di esempio DatabaseSeeder (in Laravel 11 è presente in database/seeders). Questo DatabaseSeeder è il seeder principale che viene chiamato quando esegui db:seed. Dentro di esso puoi chiamare altri seeder:

public function run()
{
    $this->call([
        UsersTableSeeder::class,
        PostsTableSeeder::class,
    ]);
}

In questo modo puoi organizzare i seeders per entità e poi richiamarli tutti da DatabaseSeeder.

Esecuzione: il comando php artisan db:seed lancia DatabaseSeeder (quindi tutto ciò che c’è in call verrà eseguito). Puoi anche specificare un seeder particolare con --class=NomeSeeder. Spesso si semina il database dopo le migrazioni, e infatti c’è un comando conveniente php artisan migrate --seed che esegue migrate e poi seed.

I seeder sono utilissimi in sviluppo e testing: puoi creare rapidamente dati fake. Abbinati a Model Factories (classi factory per generare modelli con Faker), puoi popolare decine di record realistici con poche righe. Ad esempio, Laravel di default fornisce una factory per User (che genera nome, email finta, ecc.), e potresti fare:

User::factory()->count(50)->create();

dentro un seeder per avere 50 utenti di prova. I factory si definiscono in database/factories/*Factory.php e usano la libreria Faker per i dati casuali.

In produzione, i seeder di solito non vengono eseguiti (non vuoi riempire il DB di dati di test). Ma potresti avere seeder per dati di configurazione reali (tipo un elenco di ruoli predefiniti, ecc.). Sta a te usarli con cautela nell’ambiente giusto.

Riassumendo: migrations e seeder aiutano a mantenere sincronizzati codice e database, e a automatizzare sia la creazione dello schema che il caricamento di dati iniziali o di prova. Questo favorisce un team a collaborare senza dover scambiare dump SQL manualmente, e permette anche di versionare lo schema: puoi tornare indietro eseguendo rollback di migrazioni se qualcosa va storto, il che è molto più ordinato che cercare di ricordare cosa cancellare dal DB.

Form Request e Validazione

La validazione dei dati è parte fondamentale di quasi ogni applicazione web. Laravel fornisce diversi modi per validare gli input degli utenti in modo semplice e dichiarativo. Il meccanismo di base è attraverso la classe Illuminate\Http\Request e il metodo validate(), oppure usando le Form Request class (richieste formali) che incapsulano le regole di validazione e l’autorizzazione.

Validazione in Controller con $request->validate()

Il modo rapido per validare dati di una form è, nel metodo del controller, utilizzare il metodo $request->validate([...]). Questo metodo accetta un array di regole di validazione per ciascun campo e automaticamente:

  • Verifica i dati rispetto alle regole fornite.
  • In caso di fallimento, genera una risposta di redirect alla pagina precedente con gli errori e input vecchi (tipico per form HTML), oppure restituisce un JSON 422 in caso di API JSON.
  • In caso di successo, restituisce i dati validati (che puoi assegnare a una variabile).

Esempio:

public function store(Request $request)
{
    $validatedData = $request->validate([
        'title' => 'required|unique:posts|max:255',
        'body'  => 'required',
    ]);
    // Se arrivate qui, i dati sono validi
    // $validatedData contiene solo title e body (filtrati)
    Post::create($validatedData);
    return redirect('/posts');
}

Le regole in questo esempio dicono: title è obbligatorio, unico nella tabella posts, massimo 255 caratteri; body è obbligatorio. Laravel ha un ricco set di regole (numeric, email, min, max, between, in, date, etc.) e permette anche messaggi di errore personalizzati e traduzione.

Come sviluppatore, devi definire solo le regole; Laravel gestisce il resto. Durante il redirect per errori, gli errori sono disponibili nella sessione e Blade li può mostrare facilmente con la variabile $errors (ad esempio: @error('title') <div class="error">{{ $message }}</div> @enderror mostra il messaggio di errore per il campo titolo).

Questo metodo è ottimo per controller piccoli. Tuttavia, quando le regole diventano tante o complesse, Laravel propone di spostarle in una classe dedicata: la Form Request.

Form Request: richieste convalidanti personalizzate

Una Form Request è una classe che estende Illuminate\Foundation\Http\FormRequest e che ha il compito di autorizzare e validare una certa richiesta specifica. È come un oggetto comando che rappresenta una determinata richiesta (ad esempio “StorePostRequest” per la richiesta di creare un post).

Creazione: php artisan make:request StorePostRequest. Laravel genererà app/Http/Requests/StorePostRequest.php. Dentro troverai:

  • Un metodo authorize() – deve restituire true/false a seconda se l’utente è autorizzato a fare questa richiesta. Di solito puoi mettere return true; se non hai logiche di autorizzazione particolari, oppure controllare qualcosa (es. che l’utente ha un certo ruolo).
  • Un metodo rules() – restituisce l’array di regole di validazione, come quello passato a validate() prima.

Ad esempio:

public function authorize()
{
    // Permetti solo se l'utente è admin:
    return auth()->user() && auth()->user()->is_admin;
}

public function rules()
{
    return [
        'title' => 'required|unique:posts|max:255',
        'body'  => 'required',
    ];
}

In questa maniera abbiamo incapsulato le regole. Possiamo anche definire messaggi personalizzati con un metodo messages() o attributi personalizzati con attributes(), e possiamo usare la Form Request per centralizzare logiche di verifica.

Utilizzo nel controller: invece di tipizzare il parametro come Request $request, lo tipizziamo con la nostra form request:

public function store(StorePostRequest $request)
{
    // Se il codice entra qui, significa che:
    // 1) l'authorize() ha restituito true (altrimenti lancia 403 Forbidden)
    // 2) i dati hanno passato la validazione (altrimenti redirect con errori)
    $data = $request->validated(); // ottieni i dati validati
    Post::create($data);
    return redirect('/posts');
}

Laravel automaticamente esegue la validazione prima di chiamare il metodo del controller. Quindi dentro il metodo puoi assumere di avere dati puliti e validi. $request->validated() fornisce l’array dei dati già filtrati (simile a $request->validate() di prima). In caso di errori di validazione, non entrerai nemmeno nel metodo: Laravel redireziona subito indietro con gli errori. Se authorize() torna false, lancia una HttpException 403.

Il vantaggio delle Form Request è la pulizia: il controller resta snello, e riutilizzabile se magari la stessa validazione serve in due posti. Inoltre, sposti logiche potenzialmente complesse (es. autorizzazioni condizionate) fuori dal controller, rendendo il tutto più mantenibile. Puoi persino iniettare dipendenze nel rules() se servisse (Laravel risolve anche lì, ad es. potresti iniettare un repository per decidere qualche regola).

In progetti piccoli puoi non averne stretta necessità, ma non appena le validazioni crescono, ti accorgerai che è utile. Ad esempio, immaginiamo 15 campi di un form di registrazione con tante regole: meglio metterle in una form request dedicata (es. RegisterUserRequest) che congestionarle nel controller.

Regole di validazione e messaggi

Laravel ha molte regole built-in: required, email, max, min, between, size, url, date, after:date, before:date, unique:table,column, exists:table,column, ecc. e supporta regole condizionali, regole personalizzate (puoi creare Rule objects o usare Closure). Per i messaggi di errore, c’è un file lang (es. lang/it/validation.php) con i messaggi standard in italiano. Se definisci i tuoi messaggi, puoi farlo nella Form Request con messages() restituendo un array tipo ['title.required' => 'Il titolo è obbligatorio', ...].

Una chicca: se nel file di lang metti chiavi per i campi, puoi ottenere messaggi del tipo “Il campo Nome Utente è obbligatorio” invece di “username è obbligatorio”. Oppure in attributes() della Form Request puoi mappare 'username' => 'Nome Utente' per migliorare i testi.

Infine, la Form Request può anche sanificare o modificare input nel metodo prepareForValidation() prima che le regole vengano applicate (ad es. trim di spazi, convertire virgole in punti decimali, etc.).

Riassumendo: Laravel rende la validazione molto semplice. Per poche regole, $request->validate() è spesso sufficiente e chiaro. Per casi più complessi, le Form Request forniscono una struttura organizzata per definire autorizzazione e regole in un posto riusabile. In entrambi i casi, beneficia di:

  • Messaggi di errore automatici e localizzati.
  • Redirezione automatica con vecchi input (così l’utente non perde quello che aveva scritto nei campi validi).
  • Integrazione con Blade (direttiva @error o il componente @foreach ($errors->all() as $error)).
  • Possibilità di usare le stesse regole anche lato frontend (Laravel può esportare regole JS con Laravel Validator, ma questo è un aspetto avanzato).

Esempio veloce: se stai facendo un form di registrazione utente:

  • In RegisterController, potresti avere use Illuminate\Support\Facades\Validator; e nel metodo store: $validator = Validator::make($request->all(), [ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:users', 'password' => 'required|min:8|confirmed', ]); (La regola confirmed cerca un campo password_confirmation che combaci). Poi verificare con if ($validator->fails()) etc. Ma: è ancora più semplice fare: $data = $request->validate([... regole ...]); // crea utente che incapsula tutto ciò. Oppure definire una RegisterRequest class con le stesse regole e iniettarla.

Il sistema di validazione di Laravel copre anche validazione di array, files (es. image|mimes:jpeg,png|size:1024), regole personalizzate (ad es. Rule::in(['val1','val2'])), validazione dopo un primo fail (bail), e altro ancora, ma per iniziare quanto sopra è sufficiente.

Autenticazione di base

Quasi ogni applicazione ha bisogno di gestire utenti, registrazione, login, permessi. Laravel fornisce un sistema di autenticazione completo e configurabile. Ci sono diversi livelli: uno è usare i starter kit (pacchetti ufficiali come Laravel Breeze o Laravel Jetstream che forniscono implementazioni preconfezionate di registrazione/login UI e relative logiche). Un altro è usare il sistema sottostante manualmente (guard, provider, ecc.). Per un principiante, l’approccio più rapido è utilizzare Laravel Breeze, che è un pacchetto leggero per autenticazione che include tutte le funzionalità standard: registrazione, login, logout, reset password, verifica email, e perfino integrazione API se servisse.

Laravel Breeze

Laravel Breeze è descritto come “an authentication scaffolding package for Laravel. Using it you can have a fully working login and registration system in minutes”. È minimalista e utilizza Blade (esiste anche variante Inertia/Vue o React, ma concentriamoci sulla Blade). In pratica, con pochi comandi:

composer require laravel/breeze --dev
php artisan breeze:install
npm install && npm run dev (se vuoi le risorse front-end, per gli stili Tailwind)
php artisan migrate

ti trovi il sistema di autenticazione pronto. Il comando breeze:install pubblica varie cose:

  • Le view Blade per login, register, forgot password, reset password, verify email, dashboard ecc.
  • I controller per gestire la registrazione, sessione, password (li mette in App\Http\Controllers\Auth).
  • Le route preimpostate (nel file routes/auth.php che poi viene incluso in web.php).
  • Eventuali migration (Breeze presuppone tu abbia la migrazione users, password_resets già pronte; se no, eseguendo php artisan migrate vengono create le tabelle utenti e password reset incluse in Laravel di default).
  • Aggiunge anche un semplice HomeController per /dashboard con middleware auth.

Dopo questi passi, se avvii l’app, avrai la possibilità di registrare nuovi utenti, loggarti, vedere la dashboard (che per ora è solo “You are logged in!”), uscire, etc. Breeze supporta:

  • Login/Logout con sessione.
  • Registrazione utente nuovo.
  • Reset password (invia email con link di reset, etc – richiede configurare un mail driver SMTP per testarlo veramente).
  • Verifica email (ha la schermata e logica per inviare mail di verifica, di nuovo serve un mailer configurato e coda se necessario).
  • Profile management di base: Breeze include pure una rotta per aggiornare profilo e password (facoltativo).

Il tutto con un design molto semplice (Tailwind CSS).

Breeze è ideale perché ti evita di dover scrivere tutto da zero. Se non volessi usarlo, la strada manuale in Laravel sarebbe:

  • Creare il modello User (già esiste in app/Models/User.php con i trait HasFactory, Notifiable).
  • Configurare in config/auth.php i guard e provider (default: guard “web” usa provider “users” con model App\Models\User).
  • Creare le route per login, register, etc.
  • Creare controller per gestire le form.
  • Usare magari Laravel Fortify (il backend headless di Jetstream) se non vuoi scrivere a mano login (Fortify fornisce le actions per login, register, etc., ma senza UI).
  • Scrivere le view Blade per login/register.
  • Gestire la protezione delle rotte con middleware auth e guest.

Tutto fattibile, ma perché farlo quando Breeze lo fa per noi in 2 minuti? Come dice Kinsta, “you can have a fully working login and registration system in minutes”. Breeze è pensato proprio per essere didattico: il codice è semplice e ben commentato, così puoi imparare come Laravel implementa l’autenticazione.

Cosa succede dietro le quinte: Laravel usa di base l’autenticazione session-based con guard web. Quando fai login (Auth::attempt in Breeze), se credenziali ok, Laravel crea una sessione e un cookie di sessione. Da lì in poi, il middleware auth rileva il cookie, carica l’utente loggato e ti considera autenticato. Tutte le chiamate a Auth::user() o auth()->user() restituiranno l’utente corrente. Logout pulisce la sessione. Per le API stateless invece si userebbe Laravel Sanctum o Passport per token, ma lasciamo stare per ora.

Laravel Breeze aggiunge anche una middleware verified per proteggere rotte che richiedono email verificata – e route per inviare l’email di verifica e per confermare cliccando il link. Quindi è piuttosto completo.

Autenticazione manuale di base

Giusto per accennare: se uno volesse implementare login manualmente senza Breeze, i passi tipici nel controller:

use Illuminate\Support\Facades\Auth;

public function login(Request $request) {
    $credenziali = $request->validate([
        'email' => 'required|email',
        'password' => 'required'
    ]);
    if(Auth::attempt($credenziali, $request->boolean('remember'))) {
        $request->session()->regenerate();
        return redirect()->intended('/dashboard');
    }
    return back()->withErrors([
        'email' => 'Credenziali non valide',
    ]);
}

Qui Auth::attempt verifica email e password (va a cercare un utente con quella email e verifica l’hash della password usando il driver bcrypt/hashing configurato). Se ok, logga l’utente. intended() reindirizza alla pagina che l’utente voleva vedere prima di essere intercettato dal middleware auth (classico per login). In caso di errore, torni indietro con errore. Logout:

public function logout(Request $request) {
    Auth::logout();
    $request->session()->invalidate();
    $request->session()->regenerateToken();
    return redirect('/');
}

Questo leva l’utente dalla sessione e rigenera il token CSRF (misura di sicurezza). Breeze fa essenzialmente queste cose.

Guard e provider: Laravel supporta autenticare più tipologie di utenti (es. utenti e admin con tabelle diverse, guard differenti) e diversi metodi (session, API token, etc.), ma inizialmente non serve complicarsi – si usa l’autenticazione standard. Il modello User è configurato in config/auth.php come provider per il guard web.

Reset Password: Laravel ha già tutto pronto: c’è un trait Illuminate\Auth\Passwords\CanResetPassword che il modello User usa (in User.php), e un controller in Breeze che chiama le funzioni di broker password. In breve, se chiami Password::sendResetLink(['email'=>$email]), Laravel manda una mail (usando la notifica ResetPassword presente) con un token temporaneo e un link al form di reset. Il form di reset poi manda il token insieme alla nuova password a Password::reset() che verifica e cambia la password. Non devi implementare tu l’algoritmo – configuri la mail (nel .env MAIL_...) e le view di Breeze per forgot/reset sono già pronte.

Email Verification: Simile, c’è un trait MustVerifyEmail che se messo sul modello User fa sì che i nuovi registrati debbano verificare. Breeze se lo abiliti aggiunge questo trait (mi pare che di default è già predisposto in User model commentato). Il sistema invia mail di verifica con un link firmato verso una route verification.verify. Tutto orchestrato dal framework, tu devi solo mettere middleware verified sulle rotte che vuoi proteggere.

Conclusione sulla autenticazione: Per iniziare rapidamente:

  • Installa Breeze e lancia le migrazioni. Avrai un sistema immediato per registrare e gestire utenti.
  • Usa la middleware auth sulle rotte che vuoi proteggere (Breeze già protegge /dashboard ad esempio).
  • Se serve, personalizza le view Blade generate (sono pienamente modificabili).
  • Breeze è pensato per essere poi rimosso facilmente se passi a Jetstream o guardi come riferimento.

Per alternative: Laravel Jetstream è un kit più avanzato (offre opzioni Livewire+Alpine o Inertia+Vue, gestione team, 2FA, etc.), ma è più complesso. Laravel Fortify è il backend di Jetstream che fornisce le route di auth senza interfaccia: potresti usarlo se vuoi gestire tu le view ma delegare logica. Tuttavia, per un beginner, Breeze è la scelta ottimale perché “simple yet customizable authentication scaffold”.

Dopo aver messo in piedi l’autenticazione, avrai utenti che possono loggarsi e funzionalità protette. Da lì potresti esplorare l’Authorization (Autorizzazione) che in Laravel è gestita con Gate e Policy (regole su cosa un utente può fare o meno, ad esempio un post può essere modificato solo dal suo autore). Quello però esula da questa introduzione, anche se è un concetto importante in progetti reali.

Testing (Unit test e Feature test)

Laravel è costruito con un’ottima considerazione per il testing automatico. Include già una struttura per i test e molte utility per scrivere test in modo fluido. Come visto, nella cartella tests/ ci sono due sottocartelle:

  • Unit – per test unitari (piccole unità di codice, isolati da contesto).
  • Feature – per test funzionali/integrati (simulano richieste o testano più componenti insieme).

Secondo la documentazione, “Unit tests… focus on a very small, isolated portion of your code… most unit tests probably focus on a single method… do not boot your Laravel application”, mentre “Feature tests may test a larger portion of your code, including… full HTTP request… provide the most confidence that your system as a whole is functioning”. In altri termini: i test unitari verificano che singoli metodi o classi diano l’output atteso per input noti, senza interagire col framework o il database; i test di feature invece fanno girare l’applicazione (bootstrappano Laravel) e ad esempio chiamano un URL come farebbe un utente e controllano che la risposta sia corretta.

Laravel di default fornisce:

  • PHPUnit come framework di testing (con file phpunit.xml preconfigurato).
  • Pest PHP supporto (un nuovo testing framework più sintetico, opzionale).
  • Alcune classi base per test di feature: ad esempio Illuminate\Foundation\Testing\RefreshDatabase trait che puoi usare per far sì che ogni test effettui rollback delle transazioni sul database (mantiene test isolati senza sporcare il DB).
  • Helper per testare HTTP: il trait WithoutMiddleware se vuoi disabilitare middleware in un test, oppure metodi come $this->get('/url') nel test che simula una GET e ritorna un oggetto risposta su cui fare asserzioni (es. $response->assertStatus(200)).
  • Helper per testare autenticazione (puoi fare $this->actingAs($user) per simulare utente loggato in un test).
  • Helper per testare viste (es. $response->assertViewIs('nome.view')).
  • E perfino per testare eventi, code, mail, e console commands.

Scrivere un test unitario semplice

Immaginiamo di avere una classe semplice:

class Calcolatore {
    public function somma($a, $b) {
        return $a + $b;
    }
}

Possiamo scrivere un test unitario:

class CalcolatoreTest extends TestCase {
    public function testSomma() {
        $calc = new Calcolatore();
        $result = $calc->somma(2, 3);
        $this->assertEquals(5, $result);
    }
}

La classe di test estende PHPUnit\Framework\TestCase o, in Laravel, potrebbe estendere Tests\TestCase che a sua volta deriva da base TestCase di Laravel (che bootstrap). Per puro unit test potremmo anche non voler caricare il framework, ma in genere non importa.

L’asserzione assertEquals verifica che il risultato sia 5. Se la somma restituisse un valore sbagliato, il test fallirebbe indicando la differenza.

Scrivere un test di feature (HTTP)

Supponiamo di voler testare che l’endpoint /login mostri la pagina di login correttamente:

public function testLoginPageAccessible()
{
    $response = $this->get('/login');
    $response->assertStatus(200);
    $response->assertSee('Login'); // controlla che la parola "Login" sia presente nella risposta HTML
}

Qui $this->get() simula una richiesta GET. Questo metodo è disponibile perché i test di feature ereditano da Illuminate\Foundation\Testing\TestCase che ha il trait MakesHttpRequests. Possiamo anche testare il flusso di login:

public function testUserCanLoginWithCorrectCredentials()
{
    // Arrange: create a user
    $user = User::factory()->create([
        'password' => bcrypt($pw = 'password123')
    ]);
    // Act: submit login form
    $response = $this->post('/login', [
        'email' => $user->email,
        'password' => $pw,
    ]);
    // Assert: user is authenticated and redirected
    $response->assertRedirect('/dashboard');
    $this->assertAuthenticatedAs($user);
}

Qui abbiamo usato una factory per generare un utente in test (usando SQLite in memoria magari). Poi facciamo una POST su /login con email e password corretti. Controlliamo che ci sia un redirect a /dashboard e usiamo l’helper assertAuthenticatedAs per verificare che nell’app risulti autenticato quell’utente.

Laravel mette a disposizione tonnellate di helper:

  • assertGuest per verificare che nessun utente è loggato.
  • assertDatabaseHas('table', ['col'=>'val']) per controllare che nel database esista un record con certi dati (utile dopo un create).
  • assertDatabaseMissing complementare.
  • assertSee e assertDontSee per cercare testo nella risposta.
  • assertSessionHas('key', 'value') per controllare dati in sessione (es. messaggi flash).
  • assertCookie etc.

Inoltre, puoi testare invio email usando i fake: Mail::fake() per disabilitare invio reale e poi Mail::assertSent(ClasseMail::class) per vedere se è stata “spedita”. Similmente per Notification, Event, Queue.

Esecuzione test

Come detto, esegui php artisan test. Laravel in automatico setta l’ENV testing, usa un database separato (vedi in phpunit.xml c’è likely DB_DATABASE=:memory: per SQLite in memory se predisposto) e gestisce tutto isolatamente. Quindi i test non toccano i dati di sviluppo. Ricordati di fare migrate nel database di test (Laravel lo fa in automatico se usi RefreshDatabase trait).

I test possono essere eseguiti singolarmente (con --filter per nome metodo ad esempio).

Avere test automatici è una grande garanzia: man mano che sviluppi, puoi eseguire la suite e assicurarti di non aver rotto funzionalità precedenti (regressioni). Laravel rende il testing così semplice da integrarsi nel flusso di sviluppo quotidiano.

Pest vs PHPUnit

Laravel include Pest (https://pestphp.com) di default che consente di scrivere test in stile più conciso (function style). Ad esempio invece di definire una classe, potresti scrivere in Pest:

test('somma di due numeri', function(){
    $calc = new Calcolatore();
    expect($calc->somma(2,3))->toBe(5);
});

Dipende dalle preferenze. Pest e PHPUnit coesistono, puoi usarli entrambi.

La cartella tests di default potrebbe avere due file ExampleTest: uno in Unit (che fa un assert true) e uno in Feature (che verifica se la homepage è 200). Sono esempi che puoi eliminare o modificare.

Consiglio: per iniziare, potresti non scrivere test per tutto, ma è utile provarne qualcuno, soprattutto per parti critiche. Laravel offre un ottimo terreno per fare TDD (Test-driven development) qualora volessi adottarlo, ma anche se non lo fai strettamente, avere test di feature sui punti chiave (es: registrazione utente, CRUD principali, permessi) ti aiuta a refactorare senza paura.

In conclusione, i test unitari ti aiutano a verificare la correttezza di logica isolata (esempio: algoritmo di calcolo, funzione di utility). I test di feature simulano scenari reali e verificano che l’app reagisca correttamente (es: un utente compila un form errato -> si aspetta errori di validazione, un utente valido -> viene creato un record nel DB e reindirizzato). Laravel semplifica molto la scrittura di entrambi, incoraggiandoti a adottare buone pratiche sin dall’inizio dello sviluppo.

REST API con Resource Controllers e API Resource

Laravel è eccellente non solo per generare HTML, ma anche per creare API RESTful. Molte applicazioni moderne offrono un backend con API JSON per essere consumate da frontend JavaScript (Vue/React) o app mobile. In questa sezione vediamo come Laravel facilita la creazione di API seguendo lo stile REST, sfruttando i resource controller e le risorse JSON.

Resource Controller per API REST

I Resource Controller di cui abbiamo parlato nel routing sono perfetti per costruire API REST standard. Ad esempio, se vogliamo creare un API per gestire “Articoli”:

php artisan make:controller Api/ArticleController --api --model=Article

L’opzione --api genera un controller resource senza i metodi create/edit (visto che nell’API non servono pagine HTML per form). L’opzione --model può pre-riempire type-hint e route model binding. Laravel metterebbe questo controller magari nel namespace App\Http\Controllers\Api per organizzarlo.

Nelle route, potremmo definire:

Route::middleware('auth:sanctum')->apiResource('articles', Api\ArticleController::class);

Se l’API è protetta da token (Sanctum in questo caso), altrimenti anche senza middleware per un’API pubblica read-only.

Laravel registrerà:

  • GET /api/articles -> index (lista articoli)
  • POST /api/articles -> store (crea nuovo articolo)
  • GET /api/articles/{article} -> show (singolo articolo)
  • PUT/PATCH /api/articles/{article} -> update (aggiorna)
  • DELETE /api/articles/{article} -> destroy (cancella)

Nel controller tu implementerai questi metodi per restituire e manipolare dati. Ad esempio:

public function index() {
    return ArticleResource::collection(Article::paginate(10));
}
public function show(Article $article) {
    return new ArticleResource($article);
}
public function store(Request $request) {
    // validazione...
    $article = Article::create($request->all());
    return new ArticleResource($article);
}
...

Notiamo che stiamo restituendo un ArticleResource – cos’è? Entra in gioco la seconda parte: API Resources.

API Resources (Trasformazione JSON delle entità)

Quando costruiamo API, è buona pratica non esporre direttamente il modello Eloquent così com’è (anche se potremmo fare return $article e Laravel lo serializzerebbe in JSON in automatico). Per maggiore controllo sul formato e per eventualmente nascondere campi interni, Laravel propone le Resource classes JSON. Una resource class rappresenta tipicamente un singolo modello e definisce come serializzarlo a JSON.

Si crea con Artisan: php artisan make:resource ArticleResource. Laravel la posiziona in App\Http\Resources\ArticleResource.php (Eloquent: API Resources). Dentro, c’è una classe che estende JsonResource e un metodo toArray($request) da implementare:

class ArticleResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'titolo' => $this->title,
            'contenuto' => $this->content,
            'autore' => new UserResource($this->whenLoaded('user')),
            'data_pubblicazione' => $this->created_at->toDateString(),
        ];
    }
}

Qui stiamo trasformando i campi: magari rinominiamo title in italiano “titolo”, content in “contenuto”, formattiamo la data. Inoltre, possiamo includere relazioni condizionatamente (this->whenLoaded serve a includere user solo se era caricato con eager load). Se avessimo bisogno di aggiungere campi “calcolati” o meta-informazioni, potremmo farlo qui.

Vantaggi di API Resource:

  • Decoupling tra schema del database e API response. Puoi cambiare attributi interni senza rompere l’API pubblica.
  • Facile aggiungere logica: e.g. ->toDateString() oppure mettere URL di immagini pre-fissando un dominio, ecc.
  • Possibilità di includere sotto-resource (come l’esempio con UserResource).
  • Struttura consistente delle risposte. Puoi uniformare ad esempio sempre avere un wrapper data o meta.

Di default, una Resource restituita singolarmente verrà wrappata come JSON object con quei campi. Se restituisci una collection di Resource (ad esempio ArticleResource::collection($articles) oppure come nel codice sopra usando short-syntax in controller), Laravel restituisce un array di oggetti. Puoi anche creare una classe Collection Resource separata se volessi aggiungere meta (es. link di paginazione) – ma Laravel fa già qualcosa di comodo con Paginator: se passi un Paginator a collection(), la risposta includerà automaticamente campi links e meta per paginazione. Questo perché Laravel aggiunge questi nel caso di Paginator/LengthAwarePaginator.

La documentazione dice: “Eloquent’s resource classes allow you to expressively and easily transform your models and model collections into JSON.”, fornendo un livello di trasformazione tra i modelli e le risposte JSON effettive inviate al client. Questo è utile ad esempio se vuoi filtrare attributi (es. non includere password o remember_token ovviamente), oppure aggiungere campi derivati (es. un URL costruito, un flag booleano in base a più campi). Puoi anche definire nel Resource un metodo with($request) per aggiungere dati extra a tutte le risposte (meta globali), o toArray su Collection resource per meta di collezione.

Nel nostro controller API, restituendo new ArticleResource($article), Laravel eseguirà automaticamente toArray e produrrà JSON. Se l’articolo non esiste, potremmo far tornare un 404 (Laravel con route model binding fa già 404 se non trova $article in show). Per store, convenzionalmente si potrebbe tornare un 201 Created con Location header – Laravel non lo fa in automatico, ma puoi farlo manualmente usando response()->json([...], 201) o altri helper.

Best practice varie per API:

  • Versionamento: potresti avere namespace v1, v2 per le rotte/controller, se l’API evolve rompendosi.
  • Autenticazione API: Laravel Sanctum è l’opzione light per token API (anche per SPA), Passport per OAuth2 full. Con Sanctum puoi proteggere rotte con middleware auth:sanctum e utilizzare token personali o cookie di sessione SPA.
  • Formattazione errori: puoi intercettare validazioni e fare in modo che in API rispondano con JSON di errori invece che redirect. Fortunatamente, se la request è XHR/asks JSON, Laravel già restituisce 422 JSON con errori validazione.
  • Throttle: valuta di applicare ThrottleRequests middleware sulle API (c’è alias throttle:api di default 60/min).
  • Documentazione: considera l’uso di tool come Laravel OpenAPI/Swagger per documentare le tue API, soprattutto se aperte ad altri.

Ma tornando al contesto Laravel 11 per principianti: definire resource controllers con rotte API e usare API Resources per formattare l’output è un ottimo modo per creare rapidamente un’API pulita. Ad esempio:

// routes/api.php (dopo php artisan install:api)
use App\Http\Controllers\Api\ArticleController;
Route::apiResource('articles', ArticleController::class);

nel controller:

class ArticleController extends Controller {
    public function index() {
        return ArticleResource::collection(Article::all());
    }
    public function show(Article $article) {
        return new ArticleResource($article->load('user'));
    }
    ...
}

Risultato: una GET /api/articles darà un JSON array di articoli (tutti) con i campi definiti; GET /api/articles/5 darà un JSON con i dettagli dell’articolo 5 (includendo per esempio i dati utente se abbiamo caricato la relazione). L’header Content-Type sarà application/json. Si può anche aggiungere nel controller logica per filtri, ordinamenti (magari leggendo query string), etc., a seconda delle necessità.

In conclusione, Laravel rende lo sviluppo di API molto naturale, sfruttando ciò che già sappiamo: resource controller per mappare operazioni CRUD a metodi controller , e API Resource per trasformare i modelli in JSON in maniera controllata e consistente. In più, tutto il sistema di middleware e guard può essere applicato alle API per gestire autenticazioni e permessi.


Fin qui, abbiamo esplorato Laravel 11 nei suoi aspetti principali per un principiante: dall’organizzazione dei file, al routing, controller, viste Blade, accesso ai dati con Eloquent, gestione schema e dati con migrations/seeder, validazione e form request, autenticazione out-of-the-box e testing. Con questa base, dovresti essere in grado di iniziare a sviluppare una tipica applicazione web con Laravel.

Nei prossimi capitoli, ci sposteremo su un argomento complementare avanzato: Filament PHP, un toolkit che si integra con Laravel e permette di costruire rapidamente pannelli di amministrazione (admin panel) per gestire i dati della tua applicazione tramite interfaccia grafica, con funzionalità CRUD automatiche, pagine personalizzate e widget informativi, il tutto sfruttando Laravel, Livewire e Tailwind. Filament è utile quando hai bisogno di un backend di amministrazione per gestire contenuti o configurazioni del tuo sito/app in modo comodo, senza dover programmare manualmente ogni funzionalità. Vedremo cos’è, come si installa e come usarlo efficacemente assieme a pacchetti come Spatie Laravel-Permission per il controllo dei ruoli e permessi.

Filament PHP – Pannello di Amministrazione per Laravel

Cos’è Filament e perché usarlo

Filament è un toolkit open-source per Laravel pensato per costruire interfacce di amministrazione in modo rapido e moderno. In poche parole, con Filament puoi aggiungere al tuo progetto Laravel un pannello admin completo (tipicamente accessibile a /admin) attraverso il quale gestire le entità della tua applicazione con CRUD (creazione, lettura, aggiornamento, cancellazione), visualizzare dashboard con widget statistici, e implementare funzionalità di back-office, il tutto con un minimo sforzo di configurazione.

Filament si basa sullo stack TALL (Tailwind CSS per lo stile, Alpine.js per piccole interazioni JS, Livewire per componenti reattivi lato server, Laravel come base) (Tighten.com). Ciò significa che senza scrivere JavaScript complesso, puoi avere interfacce dinamiche: Filament utilizza Livewire per far sì che, ad esempio, compilando un form in backend, questo possa avere validazione in tempo reale o aggiornare parti della pagina senza ricaricarla completamente.

Perché usarlo? Alcuni motivi:

  • Rapidità di sviluppo: invece di creare manualmente pagine di amministrazione, form, tabelle, Filament genera automaticamente interfacce CRUD basandosi sui tuoi modelli Eloquent. In pochi minuti hai pannelli funzionanti.
  • Consistenza e Best Practice: l’interfaccia è uniforme e segue best practice di UX per admin (filtri, paginazione, modali di conferma, etc.).
  • Personalizzazione: pur generando molto automaticamente, è altamente personalizzabile. Puoi modificare i form (quali campi, in che ordine, con quali widget es. text input, datepicker, file upload…), le tabelle (colonne, filtri, azioni di massa, ricerca), aggiungere pagine custom con logica propria, e creare widget per dashboard con grafici o statistiche.
  • Ecosistema: Filament ha plugin e integrazioni (per esempio plugin per gestire facilmente ruoli e permessi con Spatie, per editor WYSIWYG, per charts, ecc.).
  • Open Source e community: essendo gratuito e comunitario, trovi documentazione, forum e aggiornamenti frequenti.

In sintesi, Filament è “a modern admin panel framework that makes it easy to build powerful and user-friendly admin interfaces for your Laravel applications”. Il vantaggio per un principiante è che puoi concentrarti sulla logica della tua app, e lasciare a Filament la parte di interfaccia di gestione interna. Per esempio, se stai creando un blog in Laravel, con Filament puoi avere un pannello per inserire nuovi post, gestire categorie, approvare commenti, in poche righe, invece di creare manualmente controller + view + js per ogni operazione.

Setup di Filament in un progetto Laravel

Prerequisito: Filament richiede Laravel 10+ e PHP 8.1+, nonché Laravel Livewire 3+. Se stai seguendo questo tutorial su Laravel 11, sei a posto. Diamo per scontato che tu abbia già il progetto Laravel funzionante (magari con le migrazioni eseguite e un sistema di autenticazione per gli admin, ad esempio gli User).

Installazione: Filament si installa via Composer:

composer require filament/filament:"^3.2" -W

(la versione attuale al momento è la 3.x; -W risolve eventuali dipendenze come Livewire). Dopodiché, si esegue il comando di installazione:

php artisan filament:install --panels

Questo comando registra il necessario nel progetto. In Filament 3, il concetto di “Panel” è introdotto – significa che puoi avere più pannelli di amministrazione se volessi (es. uno per admin, uno per utenti con ruoli diversi). Il comando di install crea per default un Panel admin a path /admin.

Cosa fa in concreto filament:install:

  • Pubblica asset (CSS/JS) di Filament.
  • Crea i file di configurazione necessari (es. config/filament.php in Filament v2; in v3 crea in app/Providers/Filament un provider per il Panel).
  • (In Filament 3) Crea in app/Providers/Filament/AdminPanelProvider.php che configura il pannello admin (URL prefix, colori, autenticazione middleware, ecc.).
  • Esegue eventuali setup come generare risorse default (p.es. potrebbe generare una semplice dashboard vuota).

Una volta installato, se lanci l’app (php artisan serve) e visiti http://localhost:8000/admin, dovresti vedere la schermata di login di Filament. Per accedere, Filament per default non crea utenti nuovi: utilizza gli utenti esistenti della tua applicazione. Di base, Filament usa il guard web e il modello User di Laravel, permettendo a qualsiasi utente autenticato di loggarsi nel panel (in ambiente locale). In ambiente production però, Filament blocca l’accesso a tutti finché non configuri diversamente (per sicurezza). Quindi dopo installazione:

  • Assicurati di avere almeno un utente nel database per poter fare login. Se hai Breeze/Jetstream, crea un account admin. Se no, puoi crearlo via Tinker o migrazione.
  • In alternativa, Filament fornisce un comando per creare un utente rapidamente: php artisan make:filament-user avvia un wizard in console per inserire nome, email e password, e crea un nuovo record User. Utile per aggiungere un admin di test senza dover scrivere codice.

Per default, come detto, Filament nel contesto locale permette accesso a qualunque utente registrato. In produzione invece, dovrai esplicitare chi può accedere. Il modo più semplice è implementare nel modello User l’interfaccia Filament\Models\Contracts\FilamentUser e il metodo canAccessPanel(Panel $panel): bool . Ad esempio:

use Filament\Models\Contracts\FilamentUser;
class User extends Authenticatable implements FilamentUser {
    public function canAccessPanel(\Filament\Panel $panel): bool {
        return $this->is_admin; // supponendo tu abbia una colonna is_admin
    }
}

Così solo gli utenti con is_admin=true potranno loggarsi al panel (agli altri, in env production, sarà negato l’accesso con errore 403). Filament offre anche un trait predefinito IsFilamentUser per la stessa cosa e colonne statiche $filamentUserColumn e $filamentAdminColumn per configurare queste logiche. In alternativa, c’è il plugin Shield di cui parleremo, che integra spatie/permission e automatizza il controllo accessi.

Riepilogando:

  1. Install il pacchetto e run artisan filament:install.
  2. Prova ad accedere a /admin. In locale dovresti poter entrare con quell’utente appena creato.
  3. Vedrai la dashboard Filament (inizialmente vuota o con un messaggio di benvenuto). Da qui, potrai iniziare a creare risorse CRUD.

Creazione di Resources, Pages e Widgets

Filament organizza le funzionalità del pannello principalmente in tre categorie:

  • Resources: sono unità CRUD legate ad un modello Eloquent. Creando una Resource, Filament genera tutte le pagine necessarie per creare, leggere, aggiornare, cancellare quel modello, con form e tabelle configurabili.
  • Pages: sono pagine personalizzate, non necessariamente legate ad un modello, per logiche custom (es: una pagina “Report vendite” con calcoli, una pagina “Importa dati” con una procedura guidata, ecc.).
  • Widgets: sono piccoli componenti di interfaccia che mostrano informazioni, tipicamente usati nella Dashboard o nelle pagine delle Resources per dare statistiche o contatori.

Resources (CRUD per i modelli)

La creazione di una Resource Filament è molto semplice con Artisan:

php artisan make:filament-resource NomeModello

Ad esempio, se vuoi gestire il modello Article, esegui php artisan make:filament-resource Article. Filament genererà più file sotto app/Filament/Resources/:

  • ArticleResource.php – la classe principale della resource.
  • Una cartella ArticleResource/Pages/ contenente:
    • ListArticles.php (pagina per elenco + ricerca + filtro degli Article),
    • CreateArticle.php (pagina per form di creazione),
    • EditArticle.php (pagina per form di modifica).

Queste classi Page sono Livewire components dietro le quinte, ma Filament già implementa in esse la maggior parte del necessario.

La classe ArticleResource al suo interno definisce:

  • Il modello associato (protected static string $model = Article::class;).
  • Il form() builder: un metodo statico che restituisce un form schema (una serie di campi da mostrare nelle pagine create/edit).
  • Il table() builder: metodo statico per definire le colonne, filtri, azioni nella tabella di listing.
  • Eventuali getRelations() se hai definito sub-relations views (non comuni per iniziare).
  • Possibili override di comportamenti (per esempio autorizzazioni per varie azioni, titoli delle pagine, ecc.).

Out-of-the-box, Filament cerca di impostare form e table automaticamente basandosi sulle colonne del modello. Ad esempio, se Article ha campi string per titolo e testo, forse Filament di default aggiungerà un TextInput per title e un Textarea per content nel form, e colonne testuali nella tabella. Ti conviene comunque aprire ArticleResource.php e personalizzare un po’:

protected static ?string $navigationIcon = 'heroicon-o-document';
protected static ?string $navigationGroup = 'Contenuti';

public static function form(Form $form): Form
{
    return $form
        ->schema([
            TextInput::make('title')
                ->required()
                ->maxLength(255),
            Textarea::make('content')
                ->required()
                ->rows(5),
            Select::make('category_id')
                ->relationship('category', 'name')
                ->required(),
            Toggle::make('is_published'),
        ]);
}

public static function table(Table $table): Table
{
    return $table
        ->columns([
            TextColumn::make('id')->sortable(),
            TextColumn::make('title')->searchable()->limit(50),
            BooleanColumn::make('is_published'),
            TextColumn::make('category.name')->label('Category'),
            TextColumn::make('created_at')->dateTime('d/m/Y'),
        ])
        ->filters([
            TernaryFilter::make('published')->nullable()
                ->trueLabel('Pubblicati')->falseLabel('Bozze')
                ->attribute('is_published'),
        ])
        ->actions([
            Tables\Actions\EditAction::make(),
            // Filament genera automaticamente Edit, Delete actions
        ])
        ->bulkActions([
            Tables\Actions\DeleteBulkAction::make(),
        ]);
}

Questo è un esempio di come configurare campi e colonne. Filament ha molti tipi di campi: TextInput, Textarea, Select (può fare relationship pickers come sopra), FileUpload, DatePicker, Toggle (switch booleano), Radio, CheckboxList, RichEditor (editor WYSIWYG), repeater (campi ripetibili per array JSON), ecc. E molti tipi di colonne: TextColumn, BooleanColumn (mostra un’icona check/cross), ImageColumn, etc., con possibilità di formattare (->dateTime() come sopra), troncare (->limit()), ordinare (->sortable()) e cercare (->searchable()). I filtri permettono all’utente di restringere la vista (in questo caso TernaryFilter crea un filtro a 3 stati: tutti, pubblicati, non pubblicati).

Nonostante le tante opzioni, se non personalizzi nulla, Filament funziona comunque: l’interfaccia di default che genera è basica ma già utilizzabile. Potrai entrare nella pagina “Articles” nel menu (il menu di navigazione laterale viene generato con voce “Articles” con icona che hai specificato) e vedere l’elenco di articoli, con pulsante “Create Article”, modale o pagina di conferma per deletion, etc., tutto senza aver scritto HTML.

Inoltre, Filament gestisce:

  • Validazione: le regole required, maxLength che hai messo nei campi saranno applicate (client-side in parte via HTML5 e server-side via Livewire). Puoi definire regole addizionali o custom validation.
  • Autorizzazione: puoi definire metodi statici canView, canEdit, etc., o usare policies Laravel e Filament le rispetterà (matcha actions con gates).
  • Relazioni: come nel form con Select->relationship, Filament può generare dropdown per scegliere record correlati (es. categoria). Supporta anche polimorfiche, e campi speciali come BelongsToManyMultiSelect per i molti-a-molti.
  • Upload file/images: con campi FileUpload o ImageUpload, gestisce storage e preview.
  • Ordinamento drag & drop: se il modello usa un trait HasOrder (di Spatie, etc.), c’è supporto per ordering con drag in tabella.

Quindi, le Resource sono il cuore per il CRUD. Crei resource per ciascuna entità che vuoi gestire. Filament ti consente di generarle rapidamente e poi personalizzare ciò che serve. Dopo aver creato una Resource, appare nel menu di navigazione automaticamente (puoi raggrupparle per sezione con $navigationGroup, come messo “Contenuti” sopra, per avere heading nel menu).

Pages (Pagine personalizzate)

Non tutto è CRUD. A volte nel pannello admin vuoi pagine con funzionalità specifiche. Ad esempio, una pagina “Dashboard” con una panoramica di dati (che Filament crea di default), oppure una pagina per eseguire un certo script (es. “Rigenera Sitemap”), o un wizard multi-step. Filament permette di creare pagine Livewire stand-alone integrate nel panel.

Comando:

php artisan make:filament-page NomePagina

Ad esempio php artisan make:filament-page SalesReport potrebbe creare app/Filament/Pages/SalesReport.php e la relativa view in resources/views/filament/pages/sales-report.blade.php (in Filament v2; in v3 usa completamente Livewire render).

La classe SalesReport estenderà Filament\Pages\Page e definisce proprietà come $view (puntando a blade) se usa un view separata. Puoi anche scrivere direttamente HTML in Blade, usando componenti di Filament se vuoi lo stile coerente.

Nella classe Page puoi definire azioni (buttons) o mount logic, autorizzazioni (->authorizeAccess()), etc.

Ad esempio, una pagina semplice:

class ClearCache extends Page {
    protected static ?string $navigationLabel = 'Pulisci Cache';
    protected static ?string $navigationIcon = 'heroicon-o-trash';
    protected static string $view = 'filament.pages.clear-cache';

    public function clear() {
        Artisan::call('cache:clear');
        $this->dispatchBrowserEvent('notify', 'Cache pulita con successo!');
    }
}

E nella view Blade clear-cache.blade.php:

<x-filament::page>
    <x-filament::button wire:click="clear" color="danger">
        Cancella cache
    </x-filament::button>
</x-filament::page>

Abbiamo usato <x-filament::page> come layout della pagina (fornisce padding e stile standard) e un componente button di Filament legato al metodo clear tramite Livewire (wire:click).

Questa pagina apparirà nel menu (per default Filament mette tutte le Pages personalizzate non di resource nel menu). Possiamo personalizzare navigationLabel, etc., come sopra per darle nome e icona. Ora cliccando il pulsante si esegue il metodo clear(), si chiama Artisan command e si mostra una notifica (Filament ha sistema di toast notifications integrato: l’evento browser notify è catturato e mostra un toast).

Quindi Pages ti danno la flessibilità di aggiungere ciò che non rientra in un CRUD: report, form particolari (es: cambia la password admin, anche se potresti farlo via resource User), integrazioni con API esterne, etc.

Widgets (Widget e Dashboard)

Widgets in Filament sono mini-componenti visivi, solitamente usati nella Dashboard o nelle pagine delle resource per mostrare informazioni correlate. Esempi: un widget “Stats Overview” che mostra 3 box (totale utenti, visite oggi, vendite mese corrente) (Filament Crash-Course: Create a Customizable Admin Panel in Minutes | Tighten); oppure un grafico lineare delle vendite mensili; oppure un elenco di ultime attività.

Filament di default include nella dashboard un placeholder per widget di “Stats” e “Charts” (in Filament v2 c’era un Filament\Widgets\AccountWidget per mostrare info utente loggato, e un Filament\Widgets\FilamentInfoWidget – in Filament 3 potrebbero essere plugin separati).

Puoi creare un widget con:

php artisan make:filament-widget NomeWidget --chart --stats

Ci sono tipi diversi: Table Widget, Form Widget, Chart Widget, Stats Overview Widget. Il flag --chart indica un widget Chart (se hai preparato library JS, Filament usa Chartisan/Tom Schlick by default). --stats crea uno StatsOverview widget (insieme di statistiche).

Un widget di tipo stat è tipicamente un riquadro con icona, numero e label. Ad esempio, un widget per contare gli utenti:

class UsersCountWidget extends StatsOverviewWidget {
    protected function getCards(): array {
        return [
            StatsOverviewWidget\Card::make('Utenti registrati', User::count())
                ->description('Totali')->icon('heroicon-o-users'),
        ];
    }
}

Puoi restituire più Card per avere più box in fila.

Un widget di tipo tabella potrebbe elencare gli ultimi X record di qualcosa:

class RecentPosts extends TableWidget {
    protected function getTableQuery(): Builder {
        return Post::latest()->limit(10);
    }
    protected function getTableColumns(): array {
        return [
            TextColumn::make('title')->limit(50),
            TextColumn::make('created_at')->since(),
        ];
    }
}

Questa è una definizione rapida: Filament si occuperà di renderizzare la tabella in dashboard.

Una volta creato un widget, devi indicare a Filament dove mostrarlo. Per la Dashboard globale, in Filament 3 c’è un concetto di DashboardPage in AdminPanelProvider – in Filament 2 c’era un metodo in PanelProvider per registrare widget di dashboard. Probabilmente in Filament 3: Nel AdminPanelProvider generato, troverai qualcosa come:

->widgets([
    // ... puoi aggiungere:
    UsersCountWidget::class,
    RecentPosts::class,
])

Oppure definire un Dashboard page che implementi getWidgets().

Per aggiungere widget nelle pagine di Resource, Filament offre nella classe Resource Page (es. ListRecords) i metodi getHeaderWidgets() e getFooterWidgets(). Ad esempio, potresti voler in cima alla pagina ListArticles mostrare un contatore:

class ListArticles extends ListRecords {    protected function getHeaderWidgets(): array {        return [ StatsArticlesOverview::class ];    }}

dove StatsArticlesOverview è un tuo StatsOverviewWidget (o un widget predisposto come “ArticleStats”).

I widget arricchiscono l’esperienza admin fornendo informazioni chiave a colpo d’occhio. La dashboard iniziale del panel può mostrare vari widget (es. totali, grafici di trend), offrendo subito dati utili all’admin.

Esempi pratici CRUD con Filament

Per fissare le idee, facciamo un esempio completo: supponiamo di avere un’app di gestione biblioteca con modelli Book, Author, Genre. Vogliamo un admin panel per CRUD di questi.

1. Creare le Resources

  • php artisan make:filament-resource Author
  • php artisan make:filament-resource Genre
  • php artisan make:filament-resource Book (e magari lo facciamo --generate per far fill automatico, opzione --generate genera schema in base a colonne).

Filament genera i file. Personalizziamo: AuthorResource: campi nome, data di nascita; GenreResource: nome, descrizione; BookResource: titolo, autore (relazione Author), generi (molti a molti, relazione BelongsToMany con Genre), anno pubblicazione.

In BookResource::form:

TextInput::make('title')->required()->maxLength(200),
Select::make('author_id')
    ->relationship('author', 'name')->required(),
MultiSelect::make('genres')
    ->relationship('genres', 'name'), // per BelongsToMany
TextInput::make('year')->numeric()->minValue(1000)->maxValue(date('Y')),
Textarea::make('summary')->columnSpan('full'),

In table:

TextColumn::make('title')->limit(50)->searchable(),
TextColumn::make('author.name')->label('Author')->sortable(),
TagsColumn::make('genres.name'), // Filament ha TagsColumn per BTM showing
TextColumn::make('year'),

Aggiungiamo filtri se vogliamo (per anno, per genere).

GenreResource e AuthorResource saranno più semplici (solo campi diretti). Il bello: Filament capisce le relazioni e genererà nel form di Book un <select> per autore e un controllo multiselect per generi. La TagsColumn in tabella Book mostrerà i generi associati come tag.

2. Creare un utente admin e testare

Accedendo al panel, vedremo nel menu qualcosa tipo:

  • Library (se mettiamo $navigationGroup = ‘Library’ su tutte e tre per raggrupparli)
    • Authors
    • Books
    • Genres

Possiamo provare a creare un Author, poi un Genre, poi un Book assegnandogli quell’autore e genere. Filament gestisce:

  • Validazione (se provi a creare Book senza titolo, appare errore inline).
  • Relazioni: i dropdown ti permettono di creare author al volo se non esiste (Filament ha il concetto di modal create in relationship fields se lo abiliti).
  • Ritorno alla lista con notifica di successo automaticamente (“created successfully”).
  • Pulsanti di azione in tabella (modifica, cancella) già pronti.
  • Ordinamento e paginazione out of the box.
  • Ricerca full-text per i campi marcati searchable (nel nostro caso titolo).
  • Filtri come predisposti (es. potremmo aggiungere un filtro per genere nella lista di Book).

In pochissimo tempo, abbiamo un pannello completo per gestire il contenuto della biblioteca, che altrimenti avrebbe richiesto di creare blade, controller, ecc., per ogni entità.

3. Aggiungere una Dashboard personalizzata

Magari vogliamo che la homepage admin mostri qualche statistica: numero di libri, numero di autori, ecc. Creiamo un Widget:

php artisan make:filament-widget LibraryStats --stats

In LibraryStats widget:

protected function getCards(): array {
    return [
        Card::make('Books', Book::count()),
        Card::make('Authors', Author::count()),
        Card::make('Genres', Genre::count()),
    ];
}

Registra questo widget nella dashboard (AdminPanelProvider):

->widgets([
    LibraryStats::class,
])

Ora entrando in /admin vedremo tre card con i conteggi.

4. Autorizzazioni (facoltativo)

Se ad esempio vogliamo che solo admin possano gestire libri e generi, potremmo:

  • Impostare in ciascuna Resource i metodi statici canViewAny, canCreate, etc., usando magari i Gate di Laravel.
  • Oppure integrare Spatie Permissions come vedremo, per ruoli/permessi granulari e usare Filament Shield plugin per generare permessi per ciascuna risorsa automaticamente.

Nel contesto CRUD base, Filament di default permette l’accesso a tutti gli utenti FilamentUser. Quindi se il login è limitato agli admin, e tutti gli admin possono fare tutto, potrebbe bastare così. Se vuoi ruoli “editor” che possono solo leggere ma non cancellare, allora entrano in gioco i permessi.

Integrazione con Spatie Laravel-Permission

Per gestire ruoli e permessi nell’app Laravel, un pacchetto diffuso è spatie/laravel-permission. Permette di definire ruoli, assegnare permessi granulari a ruoli e utenti, e verificarli facilmente. Filament di per sé non obbliga ad usarlo, ma c’è uno scenario comune: limitare le sezioni del panel in base ai permessi dell’utente. Ad esempio, ruolo “Editor” può gestire Post ma non Users; ruolo “Admin” può tutto.

Spatie/Permission funziona creando tabelle roles, permissions e model_has_roles, etc. (tramite migrations incluse nel pacchetto). Una volta configurato (aggiunto trait HasRoles al modello User, eseguito le migrazioni) si possono creare ruoli e permessi.

Filament Shield è un plugin di Filament che automatizza l’integrazione con laravel-permission. I suoi vantaggi:

  • Genera in automatico i permessi per ciascuna Resource Filament (tipicamente permessi del tipo view_any, view, create, update, delete per ogni modello).
  • Fornisce un’interfaccia UI dentro Filament per gestire ruoli e permessi (un vero CRUD di Role e Permission).
  • Ha opzioni per creare un ruolo “Super Admin” con tutti i permessi di default.

Per usarlo:

composer require bezhansalleh/filament-shield

Poi:

php artisan vendor:publish --tag=filament-shield-config
php artisan shield:install

Nei config pubblicati potrai definire il nome del ruolo admin, se generare permessi per Page/Widget oltre che Resources, ecc. Importante: aggiungi il trait HasRoles di Spatie al modello User. Poi registra il plugin nello AdminPanelProvider:

->plugins([
    \BezhanSalleh\FilamentShield\FilamentShieldPlugin::make(),
])

A questo punto, Filament Shield durante l’installazione avrà generato permessi per tutte le risorse esistenti e creato un ruolo “Super Admin” con tutti i permessi. Nella sidebar Filament apparirà la voce per gestire Roles & Permissions (Shield plugin aggiunge i suoi Resources).

Ora, potrai assegnare ruoli agli utenti (magari tramite Filament stesso se crei una Resource User, oppure via tinker/seed). Un utente senza permessi appropriati non vedrà nel menu le sezioni non autorizzate e se provasse URL diretti riceverà un 403. Shield di default considera Super Admin chi ha un determinato ruolo (configurato, tipicamente “super_admin”) e gli bypassa i controlli.

Senza Shield, potresti implementare tu manualmente i check di canView etc., usando i Gate di Spatie (es. return auth()->user()->can('view_any Article')). Ma perché farlo a mano se Shield lo fa?

Flusso tipico con Shield:

  • Installi spatie/permission e shield.
  • Esegui migrate per le tabelle roles/permissions.
  • Shield genera i permessi base: ad es. view_any_article, view_article, create_article, update_article, delete_article per la resource Article, e analoghi per le altre resources e anche per Pages (se abiliti).
  • Nel panel Filament, vai nella sezione “Shield > Roles”: vedrai un ruolo Super Admin già creato con tutti i toggles permessi attivi. Puoi creare un ruolo “Editor” e selezionare magari solo permessi view/update per Article, senza delete.
  • Assegna ruoli agli utenti: Filament Shield fornisce (se vuoi) anche un mezzo rapido, oppure via Tinker: $user->assignRole('Editor').
  • Filament automaticamente integrerà questi permessi: nasconderà i bottoni che l’utente non può usare, proteggerà l’accesso alle route corrispondenti (questo perché Shield durante install modifica le Resource adding authorizeResource calls o simili, oppure usa policies dietro le quinte).
  • Se l’utente “Editor” logga sul panel, vedrà solo le voci che ha permesso (es. Article, ma non Users se esiste quella resource). E se prova a cancellare un Article, il bottone magari non appare affatto, oppure se appare e ci clicca verrà bloccato.

Spatie Laravel-Permission è molto potente: puoi definire permessi a grana fine (anche definire permesso custom come “publish_article” e poi proteggere un campo o un’azione condizionalmente). Filament Shield mappa i comuni CRUD, e puoi estenderlo per includere permessi di Pages/Widget.

Va detto che Filament Shield è un plugin di terze parti (anche se molto usato). In alternativa, potresti scrivere tu un Filament plugin per gestire ruoli, ma non ne vale la pena vista la qualità di Shield.

Integrazione passo-per-passo sintetica:

  1. Installare e configurare laravel-permission: composer require spatie/laravel-permission, eseguire php artisan vendor:publish --tag="permission-migrations" e php artisan migrate, poi php artisan vendor:publish --tag="permission-config". Aggiungi HasRoles al model User.
  2. Installare Filament Shield: come sopra. Registra il plugin e installalo con shield:install.
  3. Controllare permessi generati: apri config/filament-shield.php se esiste, per opzioni.
  4. Usare l’interfaccia: da Filament, crea ruoli e assegna permessi. Ad esempio:
    • Crea un ruolo “Admin” e usa “Select All” per dargli tutti i permessi su tutte le resources (in realtà potresti riutilizzare Super Admin esistente, ma magari vuoi un nome diverso).
    • Crea un ruolo “Staff” e dagliene solo alcuni (es. può gestire Books ma non Users).
  5. Assegna ruoli: Filament Shield può generare anche un Resource User per gestire utenti e assegnare ruoli via UI. C’è un comando shield:generate per generare resource per Role, Permission e User se volessi. Se no, assegna via code.

Ora la sicurezza è in atto: Filament integrato con Spatie/Permission tramite Shield significa che l’accesso alle varie funzioni del panel è regolato dai ruoli/permessi.

Ad esempio, Filament Shield internamente definisce policies per le Resource Eloquent:

function viewAny(User $user) { return $user->can('view_any_article'); }
function create(User $user) { return $user->can('create_article'); }
...

Queste policy vengono applicate alle actions Filament. Quindi senza quell’autorizzazione, Filament nega l’azione.

Il vantaggio è che puoi amministrare i permessi tramite interfaccia, senza toccare codice. Un amministratore “super admin” potrebbe entrare e creare nuovi ruoli e assegnare permessi on the fly.

In conclusione, l’integrazione con Spatie Laravel-Permission permette un controllo completo su chi può fare cosa nell’admin panel, indispensabile in contesti multi-utente aziendali. Filament Shield rende questa integrazione quasi plug-and-play, aggiungendo al tuo panel anche la gestione di utenti/ruoli/permessi stessa come parte dell’admin (così l’admin principale può creare altri admin con restrizioni, ecc.).


Conclusione su Filament: Abbiamo visto cos’è Filament e come, una volta installato, possiamo:

  • Creare CRUD resource velocemente per gestire i modelli dell’app.
  • Aggiungere pagine personalizzate per funzionalità extra (con pochi passi e coerenza visiva).
  • Inserire widget informativi nelle dashboard o ovunque serva per arricchire l’esperienza amministrativa.
  • Infine, come integrare un sistema di ruoli e permessi tramite Spatie, supportato da plugin Filament per interfacciarlo facilmente, in modo da poter distribuire l’accesso al panel in sicurezza.

Filament è un ottimo complemento di Laravel: invece di scrivere un backend admin da zero, ti fornisce un’impalcatura robusta e moderna su cui costruire, così da risparmiare tempo e avere subito un risultato professionale. È particolarmente utile in applicazioni dove c’è bisogno di un back-office (es. siti gestiti da redattori, e-commerce per gestire prodotti/ordini, CRM interno, ecc.). Considera che tutto questo convive con la tua app Laravel normale: puoi avere sia pagine web pubbliche (Blade, API, ecc.) sia il /admin Filament, sullo stesso progetto, senza problemi.

Riferimenti