Il framework PHP Laravel è noto per la sua eleganza, la sua potenza e la sua continua evoluzione. Con il rilascio di Laravel 11, e il consolidamento di queste scelte in Laravel 12, è stata introdotta una significativa semplificazione della struttura applicativa di default. Questa modernizzazione mira a ridurre il boilerplate, a centralizzare la configurazione e a offrire un'esperienza di sviluppo ancora più fluida. Per le imprese e gli sviluppatori che mantengono applicazioni basate su Laravel 10 (o versioni precedenti come Laravel 9, che condividono una struttura simile), effettuare un refactoring per adottare questo nuovo approccio può portare a benefici tangibili in termini di chiarezza del codice e manutenibilità a lungo termine.
In questo articolo tecnico, ti guiderò attraverso i passaggi chiave per trasformare la struttura di un'applicazione Laravel 10 (che utilizza ancora i file Kernel HTTP e Console, e diversi Service Provider per la configurazione di base) verso l'architettura più snella e programmatica introdotta con Laravel 11 e considerata la norma per Laravel 12. Vedremo come centralizzare la configurazione di routing, middleware, gestione delle eccezioni e altro ancora nel file bootstrap/app.php
.
Se vuoi approfondire, continua a leggere. Se hai una domanda specifica a riguardo di questo articolo, contattami per una consulenza dedicata. Dai anche un'occhiata al mio profilo per capire come posso aiutare concretamente la tua azienda o startup a crescere e a modernizzarsi.
La struttura "classica" di un'applicazione Laravel 10: un breve riepilogo
Prima di addentrarci nel refactoring, ricordiamo brevemente come è tipicamente strutturata la configurazione di base in un'applicazione Laravel 10 (o Laravel 9):
app/Http/Kernel.php
: questo file è responsabile della definizione dei middleware HTTP globali, dei gruppi di middleware (comeweb
eapi
) e degli alias dei middleware (precedentemente noti come$routeMiddleware
).// Esempio parziale di app/Http/Kernel.php (Laravel 10) namespace App\Http; use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel { protected $middleware = [ // \App\Http\Middleware\TrustHosts::class, \App\Http\Middleware\TrustProxies::class, \Illuminate\Http\Middleware\HandleCors::class, // ... altri middleware globali ]; protected $middlewareGroups = [ 'web' => [ \App\Http\Middleware\EncryptCookies::class, // ... altri middleware del gruppo 'web' ], 'api' => [ // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ]; protected $middlewareAliases = [ // In versioni più vecchie era $routeMiddleware 'auth' => \App\Http\Middleware\Authenticate::class, // ... altri alias ]; // ... }
app/Console/Kernel.php
: definisce i comandi Artisan custom dell'applicazione e lo schedule dei task.// Esempio parziale di app/Console/Kernel.php (Laravel 10) namespace App\Console; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule): void { // $schedule->command('inspire')->hourly(); } protected function commands(): void { $this->load(__DIR__.'/Commands'); require base_path('routes/console.php'); } }
app/Providers/RouteServiceProvider.php
: si occupa tradizionalmente di caricare i file delle rotte (routes/web.php
,routes/api.php
), configurare i prefissi per le rotte API, i middleware associati e il rate limiting per le API. Gestisce anche il route model binding implicito e esplicito.// Esempio parziale di app/Providers/RouteServiceProvider.php (Laravel 10) namespace App\Providers; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; use Illuminate\Http\Request; use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Facades\Route; class RouteServiceProvider extends ServiceProvider { public const HOME = '/dashboard'; public function boot(): void { $this->configureRateLimiting(); $this->routes(function () { Route::middleware('api') ->prefix('api') ->group(base_path('routes/api.php')); Route::middleware('web') ->group(base_path('routes/web.php')); }); } protected function configureRateLimiting(): void { RateLimiter::for('api', function (Request $request) { return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); }); } }
app/Providers/AuthServiceProvider.php
: registra le policy di autorizzazione e definisce i Gate per l'applicazione.app/Exceptions/Handler.php
: centralizza la logica di gestione delle eccezioni, permettendo di personalizzare come vengono riportate e renderizzate.
Sebbene questa struttura sia stata efficace per anni, può portare a una certa frammentazione della configurazione di base e a un po' di boilerplate (codice ripetitivo).
Verso Laravel 12: il cuore pulsante in bootstrap/app.php
Laravel 11 ha introdotto un approccio più snello e programmatico, dove gran parte della configurazione dell'applicazione viene centralizzata nel file bootstrap/app.php
. Questo file diventa il vero punto di ingresso per definire come l'applicazione gestisce il routing, i middleware, le eccezioni, e anche i service provider (anche se l'obiettivo è spesso ridurne il numero per le configurazioni di base). Laravel 12 eredita e consolida questa filosofia.
Il nuovo bootstrap/app.php
utilizza un'API fluente sull'oggetto Illuminate\Foundation\Application
per configurare questi aspetti.
Guida pratica al refactoring: da Laravel 10 alla struttura di Laravel 11/12
Vediamo i passaggi per migrare un'applicazione Laravel 10 (o Laravel 9 con struttura simile) alla nuova architettura.
Passo 1: Preparazione e Backup
Prima di qualsiasi refactoring strutturale, è assolutamente fondamentale effettuare un backup completo del tuo progetto (codice e database) e assicurarti che la tua test suite sia robusta e copra le funzionalità critiche. La gestione delle versioni con Git è tua amica: crea un nuovo branch per queste modifiche. Assicurati anche che la tua versione di PHP sia compatibile con Laravel 11/12 (minimo PHP 8.2). Aggiorna la tua dipendenza laravel/framework
nel composer.json
a ^11.0
(o ^12.0
quando disponibile e stabile) e lancia composer update
.
Passo 2: Creazione/Adattamento del nuovo bootstrap/app.php
Se stai aggiornando un progetto Laravel 10, probabilmente dovrai modificare significativamente il tuo bootstrap/app.php
esistente o quasi ricrearlo basandoti sulla struttura di Laravel 11+. Un bootstrap/app.php
minimale in stile Laravel 11/12 potrebbe assomigliare a questo:
// bootstrap/app.php (Laravel 11/12 style)
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php', // Opzionale, se non presente di default in L11+
commands: __DIR__.'/../routes/console.php',
health: '/up', // Health check route di default
)
->withMiddleware(function (Middleware $middleware) {
// Qui configureremo i middleware
})
->withExceptions(function (Exceptions $exceptions) {
// Qui configureremo la gestione delle eccezioni
})
->withProviders([
// App\Providers\FortifyServiceProvider::class, // Esempio provider custom/pacchetto
// App\Providers\PennantServiceProvider::class, // Altro esempio
], true) // Il 'true' forza il caricamento di questi, altrimenti molti sono auto-scoperti
->create();
Nota: In Laravel 11+, i file routes/api.php
e routes/channels.php
non sono più presenti di default. Se la tua applicazione li usa, dovrai specificarli qui o installarli con php artisan install:api
/ php artisan install:broadcasting
.
Passo 3: Migrazione della Configurazione del Routing
Le responsabilità del RouteServiceProvider
(caricare i file di rotte, definire prefissi, middleware per gruppi, rate limiter) si spostano in bootstrap/app.php
tramite il metodo withRouting()
.
Da app/Providers/RouteServiceProvider.php
(Laravel 10): La logica nel metodo boot()
e configureRateLimiting()
:
// In RouteServiceProvider (L10) - parte della logica da migrare
public function boot(): void
{
$this->configureRateLimiting();
$this->routes(function () {
Route::middleware('api')
->prefix('api') // Prefisso standard, ma potrebbe essere custom
->name('api.') // Opzionale, per i nomi delle rotte
->group(base_path('routes/api.php'));
Route::middleware('web')
->group(base_path('routes/web.php'));
});
// Esempio di configurazione Route Model Binding esplicito
// Route::model('user', App\Models\User::class);
// Oppure configurazioni di pattern globali per i parametri di rotta
// Route::pattern('id', '[0-9]+');
}
protected function configureRateLimiting(): void
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
// Altri rate limiter custom...
RateLimiter::for('uploads', function (Request $request) {
return Limit::perMinute(10)->by($request->user()?->id ?: $request->ip());
});
}
A bootstrap/app.php
(Laravel 11/12):
// bootstrap/app.php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route; // Necessario se usi metodi di Route qui
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php', // Assumendo che tu lo usi
apiPrefix: 'api/custom/v1', // Puoi cambiare il prefisso API qui
commands: __DIR__.'/../routes/console.php',
health: '/up',
// Il metodo 'then' viene eseguito DOPO che i file di rotte sono stati caricati
// È un buon posto per configurare il route model binding esplicito o pattern globali
then: function () {
// Esempio: Route model binding esplicito (se ancora necessario)
// Route::model('user', \App\Models\User::class);
// Esempio: Pattern globale per parametri ID
// Route::pattern('id', '[0-9]+');
// Definisci qui i rate limiter
RateLimiter::for('api', function (Request $request) {
// La logica per 'api' ora usa il rate limiter definito nel middleware di throttle
// assegnato al gruppo 'api', quindi questa definizione è più per rate limiter custom
// richiamati esplicitamente con ->middleware('throttle:custom_limiter_name')
return Limit::perMinute(1000)->by($request->user()?->id ?: $request->ip());
});
RateLimiter::for('uploads', function (Request $request) {
return Limit::perMinute(10)->by($request->user()?->id ?: $request->ip());
});
}
)
// ... (withMiddleware, withExceptions) ...
->create();
Nota importante sul Rate Limiting API: In Laravel 11+, il middleware throttle:api
applicato di default al gruppo api
(se usi php artisan install:api
) fa riferimento a un limiter chiamato api
. La sua definizione di default (es. 60 richieste al minuto) è interna al framework. Se vuoi sovrascriverla o personalizzarla, puoi farlo nel then
callback come mostrato, oppure definire limiter con nomi diversi e applicarli specificamente alle tue rotte o gruppi.
Passo 4: Migrazione della Configurazione dei Middleware
La registrazione dei middleware globali, dei gruppi e degli alias si sposta da app/Http/Kernel.php
a bootstrap/app.php
usando il metodo withMiddleware()
.
Da app/Http/Kernel.php
(Laravel 10): Le proprietà $middleware
, $middlewareGroups
, $middlewareAliases
(o $routeMiddleware
).
A bootstrap/app.php
(Laravel 11/12):
// bootstrap/app.php
use App\Http\Middleware\MyCustomGlobalMiddleware;
use App\Http\Middleware\EnsureUserIsAdmin;
use App\Http\Middleware\AnotherWebMiddleware;
use Illuminate\Foundation\Configuration\Middleware; // Importa la classe Middleware
return Application::configure(basePath: dirname(__DIR__))
// ... (withRouting) ...
->withMiddleware(function (Middleware $middleware) {
// Middleware Globali (equivalente di $middleware)
// Vengono eseguiti per ogni richiesta HTTP all'applicazione.
// Alcuni middleware precedentemente globali sono ora gestiti internamente dal framework.
// Aggiungi solo quelli veramente custom e globali.
$middleware->append(MyCustomGlobalMiddleware::class);
// Alias dei Middleware (equivalente di $middlewareAliases o $routeMiddleware)
$middleware->alias([
'is_admin' => EnsureUserIsAdmin::class,
'locale_redirect' => \App\Http\Middleware\LocaleRedirectMiddleware::class,
]);
// Gruppi di Middleware (equivalente di $middlewareGroups)
// Il gruppo 'web' è spesso già preconfigurato con middleware essenziali
// (cookies, session, CSRF, etc.) dal framework stesso.
// Puoi aggiungerne altri o sovrascrivere.
$middleware->group('web', [
AnotherWebMiddleware::class,
// ... altri middleware specifici per il gruppo 'web'
]);
// Il gruppo 'api' è anche preconfigurato se si usa `php artisan install:api`.
// Tipicamente include 'throttle:api' e la gestione delle bindings.
$middleware->group('api', [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, // Se usi Sanctum per SPA
'throttle:api', // Farà riferimento al rate limiter 'api'
\Illuminate\Routing\Middleware\SubstituteBindings::class,
]);
// Puoi anche aggiungere middleware specifici per i comandi console
// $middleware->forCommands([
// \App\Console\Middleware\EnsureDatabaseIsReachable::class,
// ]);
// Per modificare la priorità dei middleware (se necessario, raramente)
// $middleware->priority([
// \Illuminate\Cookie\Middleware\EncryptCookies::class,
// \Illuminate\Session\Middleware\StartSession::class,
// // ...
// ]);
})
// ... (withExceptions) ...
->create();
Laravel 11+ gestisce internamente molti dei middleware che prima erano esplicitamente nel Kernel HTTP (es. EncryptCookies
, StartSession
, VerifyCsrfToken
per il gruppo web
). Devi solo aggiungere/configurare quelli specifici della tua applicazione.
Passo 5: Migrazione della Gestione delle Eccezioni
La personalizzazione del reporting e del rendering delle eccezioni si sposta da app/Exceptions/Handler.php
a bootstrap/app.php
usando il metodo withExceptions()
.
Da app/Exceptions/Handler.php
(Laravel 10): I metodi register()
, o le proprietà $dontReport
, $dontFlash
, e i metodi report()
e render()
.
A bootstrap/app.php
(Laravel 11/12):
// bootstrap/app.php
use Illuminate\Foundation\Configuration\Exceptions; // Importa la classe Exceptions
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use App\Exceptions\CustomApplicationException;
use Throwable;
return Application::configure(basePath: dirname(__DIR__))
// ... (withRouting, withMiddleware) ...
->withExceptions(function (Exceptions $exceptions) {
// Equivalente di $dontReport
$exceptions->dontReport([
CustomApplicationException::class,
]);
// Equivalente di $reportable per report custom
$exceptions->report(function (CustomApplicationException $e) {
// Logica di reporting custom per questa eccezione
Log::channel('custom_errors')->error("Custom App Error: " . $e->getMessage());
});
$exceptions->report(function (Throwable $e) {
// Logica di reporting generica, se non gestita specificamente sopra
if (app()->bound('sentry') && $this->shouldReport($e)) { // Esempio con Sentry
// app('sentry')->captureException($e);
}
})->stop();// Il ->stop() previene il reporting di default di Laravel per questa Throwable
// Equivalente di $renderable per rendering custom
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
if ($request->is('api/*')) {
return response()->json(['message' => 'Risorsa non trovata.'], 404);
}
// Altrimenti, lascia che Laravel gestisca la pagina 404 di default
});
$exceptions->render(function (CustomApplicationException $e, Request $request) {
return response()->view('errors.custom_app_error', ['message' => $e->getMessage()], 500);
});
// Puoi anche definire livelli di log specifici per eccezione
// $exceptions->level(InvalidOrderException::class, LogLevel::CRITICAL);
// Per non inviare certe variabili al sistema di gestione errori (es. Ignition/Flare)
// $exceptions->dontFlash(['password', 'credit_card_number']);
})
->create();
Passo 6: Razionalizzazione dei Service Provider
Con la nuova struttura, molti service provider di default o quelli che servivano principalmente per registrare configurazioni di base (come RouteServiceProvider
o AuthServiceProvider
per le policy) potrebbero non essere più necessari nella loro forma estesa o possono essere eliminati se le loro funzionalità sono ora gestite in bootstrap/app.php
o tramite autodiscovery.
RouteServiceProvider
: le sue responsabilità principali (caricamento rotte, rate limiting) sono ora inbootstrap/app.php ->withRouting()
. Può essere rimosso o svuotato se non ha altra logica custom.AuthServiceProvider
: l'autodiscovery delle policy di Laravel (se le policy sono nella directoryapp/Policies/
e seguono le convenzioni di nomenclatura) spesso elimina la necessità di registrarle manualmente. Se hai Gate custom o altre logiche, queste possono rimanere qui, o essere spostate inAppServiceProvider
o registrate inbootstrap/app.php
(es.Gate::define(...)
nel callbackthen
diwithProviders
o in un provider dedicato se la logica è molta).- Event Discovery: è abilitato di default, quindi
EventServiceProvider
potrebbe non essere necessario solo per mappare eventi e listener se segui le convenzioni.
Rivedi i tuoi service provider custom e quelli di default: se registrano solo configurazioni ora gestibili in bootstrap/app.php
o coperte dall'autodiscovery, considera di semplificarli o rimuoverli.
Passo 7: Rimozione dei File Kernel e Provider Obsoleti
Una volta migrate le configurazioni, puoi eliminare in sicurezza:
app/Http/Kernel.php
app/Console/Kernel.php
app/Providers/RouteServiceProvider.php
(se tutte le sue funzionalità sono state migrate)- Eventualmente altri provider resi obsoleti.
Ricorda di rimuoverli anche dalla sezione providers
del tuo config/app.php
se erano registrati lì e non sono più necessari (anche se con bootstrap/app.php ->withProviders()
questo file di configurazione diventa meno centrale per la registrazione dei provider).
Passo 8: Testing Approfondito
Dopo queste modifiche strutturali, è cruciale eseguire la tua test suite completa per assicurarti che tutto funzioni come previsto: routing, middleware, gestione eccezioni, autorizzazione, ecc. Presta particolare attenzione ai test funzionali/HTTP.
Benefici della Nuova Struttura per le Applicazioni Aziendali
Adottare questa struttura snella per le tue applicazioni Laravel offre diversi vantaggi:
- Maggiore Chiarezza: la configurazione di base dell'applicazione è centralizzata in un unico posto (
bootstrap/app.php
), rendendo più facile capire come l'applicazione è assemblata. - Riduzione del Boilerplate: meno file e meno codice di configurazione da mantenere.
- Configurazione più Programmatica: l'API fluente offre un modo più espressivo e flessibile per definire la configurazione rispetto ai file di classe con proprietà.
- Facilità di Onboarding: i nuovi sviluppatori che conoscono Laravel 11+ si troveranno immediatamente a loro agio.
- Allineamento con il Futuro di Laravel: adottare le nuove convenzioni ti prepara meglio per le future evoluzioni del framework.
Il Ruolo del Consulente Laravel Esperto in Migrazioni Strutturali
Un refactoring di questa portata, sebbene Laravel fornisca un percorso chiaro, può presentare delle sfide in applicazioni aziendali grandi e complesse, con molta logica custom nei Kernel o nei Service Provider. Come senior laravel developer e consulente con una profonda conoscenza dell'architettura di Laravel attraverso le sue versioni, posso aiutare la tua impresa a:
- Valutare la fattibilità e l'impatto del refactoring sul tuo specifico applicativo.
- Pianificare ed eseguire la migrazione in modo sicuro ed efficiente.
- Ristrutturare la logica custom per adattarla al nuovo paradigma.
- Garantire che la test suite sia adeguata per validare le modifiche.
- Formare il tuo team di sviluppo sulle nuove pratiche.
Puoi scoprire di più sulla mia esperienza e sul mio approccio consulenziale visitando la pagina Chi Sono.
Abbracciare l'evoluzione di Laravel non significa solo inseguire l'ultima versione, ma adottare pratiche che rendono le tue applicazioni più moderne, pulite e facili da mantenere nel tempo. La transizione alla nuova struttura applicativa è un passo importante in questa direzione.
Se la tua attività sta considerando di aggiornare una vecchia applicazione Laravel e vuoi assicurarti che il processo sia gestito con la massima competenza e attenzione ai dettagli, non esitare a contattarmi per una discussione approfondita.
Ultima modifica: Giovedì 13 Febbraio 2025, alle 11:18