Vai al contenuto principale

Introduzione

Questa guida illustra come migrare dalla vecchia struttura applicativa di Laravel 10 e precedenti (con classi Kernel e più service provider) allo Slim Application Skeleton di Laravel 11 e successivi.
La documentazione ufficiale non raccomanda questa migrazione.
  • La vecchia struttura di Laravel 10 continua a funzionare anche su Laravel 11 e versioni successive. Fino alle versioni attuali (Laravel 13 compreso) non c’è alcun piano di dismissione.
  • La migrazione è del tutto opzionale. Non è obbligatoria né raccomandata.
  • Se non conosci a fondo le differenze tra vecchia e nuova struttura, ti sconsigliamo caldamente di migrare. È un’operazione per sviluppatori che conoscono bene il framework internamente.
  • Prima di migrare, fai sempre un backup e verifica che tutti i test siano verdi.

Quando serve migrare

Puoi valutare la migrazione nei casi seguenti.
  • Vuoi allineare il progetto alla struttura standard più recente per facilitare l’onboarding di nuovi membri con la documentazione ufficiale
  • Vuoi mantenere coerenza con pacchetti o starter kit creati in Laravel 11+
  • Vuoi ridurre file e classi ereditati dalla vecchia struttura e semplificare la codebase

Prerequisiti

La guida presuppone questa situazione.
  • L’upgrade di Laravel è già stato completato (laravel/framework ^11.0 o successivi)
  • Tutti i test esistenti passano
  • Hai compreso la struttura applicativa di Laravel 11+ (vedi Struttura dell’applicazione da Laravel 11)

Esempio di migrazione

Ecco la procedura per un progetto creato con Laravel 10 + Breeze (stack Blade) in cui vuoi mantenere Breeze ma migrare solo la struttura applicativa.
1

Sostituire bootstrap/app.php

Il vecchio bootstrap/app.php creava l’istanza $app e registrava i kernel. Sostituiscilo con la chain Application::configure().Vecchio (Laravel 10):
<?php

$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

return $app;
Nuovo (Laravel 11+):
<?php

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',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        //
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();
Le callback di withMiddleware() e withExceptions() sono i posti in cui trasferirai le impostazioni dei file kernel che eliminerai nei prossimi step. Lasciale vuote per ora e le compileremo dopo.
2

Eliminare il kernel HTTP (app/Http/Kernel.php)

In app/Http/Kernel.php erano definiti middleware globali, gruppi di middleware e alias.Vecchio (app/Http/Kernel.php):
<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    protected $middleware = [
        \App\Http\Middleware\TrustProxies::class,
        \Illuminate\Http\Middleware\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];

    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
        'api' => [
            \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

    protected $middlewareAliases = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        // ...
    ];
}
In Laravel 11 i middleware qui sopra sono incorporati nel framework come default. Se non hai personalizzazioni, puoi eliminare direttamente app/Http/Kernel.php.In caso di personalizzazioni (middleware aggiunti o rimossi), trasferisci il tutto in withMiddleware() di bootstrap/app.php prima di eliminare.
->withMiddleware(function (Middleware $middleware) {
    // aggiunta di un middleware globale
    $middleware->append(\App\Http\Middleware\MyCustomMiddleware::class);

    // aggiungi al gruppo web
    $middleware->web(append: [
        \App\Http\Middleware\HandleInertiaRequests::class,
    ]);

    // aggiungi alias
    $middleware->alias([
        'role' => \App\Http\Middleware\CheckRole::class,
    ]);
})
Completato il trasferimento, elimina app/Http/Kernel.php.
In Laravel 11 puoi rimuovere anche le classi middleware di default (TrustProxies, EncryptCookies, VerifyCsrfToken, ecc.) da app/Http/Middleware/. Sono integrate nel framework: se non le personalizzi, il file non serve.
3

Eliminare il kernel della console (app/Console/Kernel.php)

app/Console/Kernel.php gestiva schedule e auto-load dei comandi.Vecchio (app/Console/Kernel.php):
<?php

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');
    }
}
Auto-load dei comandi: da Laravel 11 la directory app/Console/Commands/ viene scansionata automaticamente, quindi $this->load() non serve.Definizione dello schedule: spostala in routes/console.php o in withSchedule() di bootstrap/app.php.
// routes/console.php (Laravel 11+)
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')->daily();
Dopo la migrazione elimina app/Console/Kernel.php.
Non eliminare i file dei comandi Artisan custom nella directory app/Console/. Lascia i comandi al loro posto ed elimina solo il file della classe Kernel.
4

Eliminare l'exception handler (app/Exceptions/Handler.php)

app/Exceptions/Handler.php gestiva le impostazioni di report e rendering delle eccezioni.Vecchio (app/Exceptions/Handler.php):
<?php

namespace App\Exceptions;

use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;

class Handler extends ExceptionHandler
{
    protected $dontFlash = [
        'current_password',
        'password',
        'password_confirmation',
    ];

    public function register(): void
    {
        $this->reportable(function (Throwable $e) {
            //
        });
    }
}
Se hai personalizzazioni, trasferiscile in withExceptions() di bootstrap/app.php prima di eliminare.
use Throwable;
use Illuminate\Http\Request;

->withExceptions(function (Exceptions $exceptions) {
    // personalizza il report
    $exceptions->report(function (Throwable $e) {
        // ...
    });

    // personalizza il rendering
    $exceptions->render(function (\App\Exceptions\InvalidOrderException $e, Request $request) {
        return response()->view('errors.invalid-order', status: 500);
    });
})
Se avevi aggiunto voci proprie in $dontFlash, puoi trasferirle in modo analogo.
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->dontFlash([
        'my_sensitive_field',
    ]);
})
Completato il trasferimento, elimina app/Exceptions/Handler.php.
5

Eliminare RouteServiceProvider e migrare la registrazione delle rotte

app/Providers/RouteServiceProvider.php caricava i file delle rotte e configurava il rate limit.Vecchio (app/Providers/RouteServiceProvider.php):
<?php

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 = '/home';

    public function boot(): void
    {
        RateLimiter::for('api', function (Request $request) {
            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
        });

        $this->routes(function () {
            Route::middleware('api')
                ->prefix('api')
                ->group(base_path('routes/api.php'));

            Route::middleware('web')
                ->group(base_path('routes/web.php'));
        });
    }
}
Registrazione delle rotte: sposta in withRouting() di bootstrap/app.php.
->withRouting(
    web: __DIR__.'/../routes/web.php',
    api: __DIR__.'/../routes/api.php', // se usi le rotte API
    commands: __DIR__.'/../routes/console.php',
    health: '/up',
)
Rate limit: sposta in AppServiceProvider::boot().
// app/Providers/AppServiceProvider.php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

public function boot(): void
{
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
    });
}
Se utilizzi la costante HOME, sostituiscila con la stringa URL diretta o spostala in AppServiceProvider.Dopo la migrazione elimina app/Providers/RouteServiceProvider.php.
6

Riordinare i service provider

In Laravel 10 c’erano 5 service provider di default. Uniscili in AppServiceProvider.php.Provider da eliminare (dopo aver trasferito i contenuti in AppServiceProvider):
FileDestinazione
app/Providers/AuthServiceProvider.phpGate::policy() ecc. in AppServiceProvider::boot()
app/Providers/BroadcastServiceProvider.phpSe usi il broadcasting: withBroadcasting() in bootstrap/app.php
app/Providers/EventServiceProvider.phpEvent::listen() ecc. in AppServiceProvider::boot()
app/Providers/RouteServiceProvider.phpGià gestito nello step precedente
Esempio di migrazione del contenuto di app/Providers/AuthServiceProvider.php:
// vecchio: app/Providers/AuthServiceProvider.php
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        Post::class => PostPolicy::class,
    ];

    public function boot(): void
    {
        $this->registerPolicies();
    }
}
// nuovo: app/Providers/AppServiceProvider.php
// Se segui la convenzione di naming standard di Laravel, l'auto-detection basta e non serve altro
use Illuminate\Support\Facades\Gate;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Gate::policy(Post::class, PostPolicy::class);
    }
}
Dopo aver eliminato i file dei provider non più necessari, rimuovi anche l’array providers da config/app.php.
// array providers in config/app.php
// puoi eliminarlo del tutto
'providers' => ServiceProvider::defaultProviders()->merge([
App\Providers\AppServiceProvider::class,
// rimuovi anche le voci dei provider eliminati
// App\Providers\AuthServiceProvider::class, // rimuovere
// App\Providers\EventServiceProvider::class, // rimuovere
// App\Providers\RouteServiceProvider::class, // rimuovere
])->toArray(),
Inoltre crea bootstrap/providers.php per la nuova struttura.
<?php

// bootstrap/providers.php
return [
    App\Providers\AppServiceProvider::class,
];
Se bootstrap/providers.php è presente, Laravel privilegia questo file come elenco dei provider.
7

Aggiornare la classe base Controller

In Laravel 10 la classe base Controller usava i trait AuthorizesRequests e ValidatesRequests. La nuova classe base di Laravel 11 è una classe astratta semplice senza questi trait.Vecchio (Laravel 10):
<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;

class Controller extends BaseController
{
    use AuthorizesRequests, ValidatesRequests;
}
Nuovo (Laravel 11+):
<?php

namespace App\Http\Controllers;

abstract class Controller
{
    //
}
Sostituzioni per le funzionalità dei trait:
Vecchio metodo (tramite trait)Nuovo metodo
$this->validate($request, $rules)$request->validate($rules)
$this->authorize('update', $post)Gate::authorize('update', $post)
$this->authorizeResource(Post::class)In Laravel 11 non c’è un’alternativa immediata: lascia App\Http\Controllers\Controller come in Laravel 10
Se i controller esistenti usano i metodi dei trait, puoi correggerli uno a uno oppure mantenere i trait sulla classe base Controller. Non è obbligatorio cambiare tutto insieme.
Se i controller di autenticazione generati da Breeze o Jetstream chiamano $this->validate() o $this->authorize(), verifica il funzionamento prima di modificare la classe base.
8

Eliminare i file di config non necessari

File come config/cors.php, config/hashing.php, config/view.php che non hai modificato rispetto al default si possono eliminare. Mantieni quelli che hai personalizzato.
9

Aggiornare public/index.php

Cambia in modo profondo per adeguarsi alla nuova struttura.
<?php

use Illuminate\Foundation\Application;
use Illuminate\Http\Request;

define('LARAVEL_START', microtime(true));

// Determine if the application is in maintenance mode...
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}

// Register the Composer autoloader...
require __DIR__.'/../vendor/autoload.php';

// Bootstrap Laravel and handle the request...
/** @var Application $app */
$app = require_once __DIR__.'/../bootstrap/app.php';

$app->handleRequest(Request::capture());
10

Aggiornare artisan

Riscrivi anche il file artisan.
#!/usr/bin/env php
<?php

use Illuminate\Foundation\Application;
use Symfony\Component\Console\Input\ArgvInput;

define('LARAVEL_START', microtime(true));

// Register the Composer autoloader...
require __DIR__.'/vendor/autoload.php';

// Bootstrap Laravel and handle the command...
/** @var Application $app */
$app = require_once __DIR__.'/bootstrap/app.php';

$status = $app->handleCommand(new ArgvInput);

exit($status);
11

Aggiornare tests/TestCase.php

Il trait CreatesApplication non serve più, quindi modifica il file. Puoi eliminare tests/CreatesApplication.php.
<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    //
}
12

Aggiornare .env, .env.example, phpunit.xml

CACHE_DRIVER è diventato CACHE_STORE e sono state aggiunte voci: modificale se necessario. Queste modifiche toccano file di config e ambienti di produzione, quindi procedi con cautela. Non è obbligatorio adeguarsi a tutti i costi.
13

Verifica del funzionamento

Terminata la migrazione, verifica nell’ordine seguente.
# pulizia delle cache
php artisan config:clear
php artisan route:clear
php artisan cache:clear

# verifica che le rotte siano registrate correttamente
php artisan route:list

# esegui i test
php artisan test
In caso di problemi, ripristina i file eliminati dal backup e leggi i messaggi di errore.

Struttura dei file dopo la migrazione

Dopo la migrazione la struttura del progetto risulta come segue (i punti di cambiamento rispetto alla vecchia struttura sono indicati).
app/
├── Console/
│   └── Commands/          ← i comandi custom restano al loro posto
│   (Kernel.php è stato eliminato)
├── Exceptions/
│   (Handler.php è stato eliminato)
├── Http/
│   ├── Controllers/
│   │   └── Controller.php ← modificata come classe astratta semplice
│   └── Middleware/        ← i middleware custom restano
│   (Kernel.php è stato eliminato)
├── Models/
└── Providers/
    └── AppServiceProvider.php ← unica classe
    (AuthServiceProvider.php eliminato)
    (BroadcastServiceProvider.php eliminato)
    (EventServiceProvider.php eliminato)
    (RouteServiceProvider.php eliminato)
bootstrap/
├── app.php                ← sostituito con Application::configure()
└── providers.php          ← nuovo file
routes/
├── web.php
├── api.php                ← solo se serve
└── console.php            ← anche la definizione dello schedule qui
Elemento migratoVecchia posizioneNuova posizione
Middleware HTTPapp/Http/Kernel.phpwithMiddleware() in bootstrap/app.php
Gestione eccezioniapp/Exceptions/Handler.phpwithExceptions() in bootstrap/app.php
Definizione scheduleapp/Console/Kernel.phproutes/console.php
Registrazione rotteapp/Providers/RouteServiceProvider.phpwithRouting() in bootstrap/app.php
Elenco service providerArray providers in config/app.phpbootstrap/providers.php
Registrazione policyapp/Providers/AuthServiceProvider.phpAuto-registrazione. Manuale in AppServiceProvider::boot()
Listener di eventiapp/Providers/EventServiceProvider.phpAuto-registrazione. Manuale in AppServiceProvider::boot()
Ultima modifica il 13 luglio 2026