Zum Hauptinhalt springen

Einleitung

Dieser Leitfaden beschreibt, wie Sie von der alten Anwendungsstruktur (mit Kernel-Klassen und mehreren Service Providern) aus Laravel 10 auf die Slim Application Skeleton ab Laravel 11 umsteigen.
Die offizielle Dokumentation empfiehlt diese Migration nicht.
  • Die alte Laravel-10-Struktur läuft in Laravel 11 und höher weiter unverändert. Auch in aktuellen Versionen bis Laravel 13 gibt es keinen End-of-Life-Plan.
  • Die Migration ist vollständig optional — sie ist weder Pflicht noch Empfehlung.
  • Ohne tiefes Verständnis der Unterschiede zwischen alter und neuer Struktur raten wir dringend davon ab. Das ist Arbeit für Entwickler, die die Framework-Interna gut kennen.
  • Sichern Sie das Projekt vor der Migration, und stellen Sie sicher, dass alle Tests grün sind.

Wann eine Migration sinnvoll ist

In folgenden Fällen ist eine Migration überlegenswert:
  • Neue Team-Mitglieder sollen mit der offiziellen Doku leichter arbeiten können — deshalb wollen Sie das Projekt auf die aktuelle Standardstruktur bringen.
  • Sie möchten die Konsistenz zu Paketen oder Starter-Kits wahren, die ab Laravel 11 neu entstanden sind.
  • Sie möchten Konfigurationsdateien und Klassen aus der Alt-Struktur reduzieren und die Codebasis vereinfachen.

Voraussetzungen

Diese Anleitung setzt Folgendes voraus:
  • Der Laravel-Versions-Upgrade ist abgeschlossen (laravel/framework ^11.0 oder höher).
  • Bestehende Tests laufen alle grün.
  • Sie kennen die Anwendungsstruktur ab Laravel 11 (siehe Anwendungsstruktur ab Laravel 11).

Migrationsbeispiel

Wir zeigen, wie ein mit Laravel 10 + Breeze (Blade-Stack) erstelltes Projekt Breeze behält und nur die Anwendungsstruktur migriert.
1

bootstrap/app.php ersetzen

Die alte bootstrap/app.php erzeugte die $app-Instanz und registrierte die Kernels. Ersetzen Sie das durch die Application::configure()-Chain.Alt (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;
Neu (Laravel 11 und höher):
<?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();
Die Callbacks withMiddleware() und withExceptions() sind die neuen Ablageorte für die Konfiguration aus den Kernel-Dateien, die wir in den nächsten Schritten löschen. Lassen Sie sie zunächst leer und ergänzen Sie sie im weiteren Verlauf.
2

HTTP-Kernel (app/Http/Kernel.php) löschen

app/Http/Kernel.php definierte globale Middleware, Middleware-Gruppen und Middleware-Aliase.Alt (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 sind diese Middlewares bereits framework-intern als Defaults hinterlegt. Ohne eigene Anpassungen können Sie app/Http/Kernel.php einfach löschen.Bei eigenen Anpassungen (zusätzliche/ausgeschlossene Middleware) übertragen Sie diese in withMiddleware() in bootstrap/app.php, bevor Sie die Datei entfernen.
->withMiddleware(function (Middleware $middleware) {
    // Globale Middleware hinzufügen
    $middleware->append(\App\Http\Middleware\MyCustomMiddleware::class);

    // Middleware zur web-Gruppe hinzufügen
    $middleware->web(append: [
        \App\Http\Middleware\HandleInertiaRequests::class,
    ]);

    // Middleware-Alias hinzufügen
    $middleware->alias([
        'role' => \App\Http\Middleware\CheckRole::class,
    ]);
})
Nach dem Übertragen löschen Sie app/Http/Kernel.php.
In Laravel 11 können Sie auch Standard-Middleware-Klassen wie TrustProxies, EncryptCookies und VerifyCsrfToken aus app/Http/Middleware/ entfernen. Diese sind ins Framework integriert; ohne Anpassungen sind die Dateien überflüssig.
3

Console-Kernel (app/Console/Kernel.php) löschen

app/Console/Kernel.php war für die Scheduler-Definition und das Autoloading von Commands zuständig.Alt (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');
    }
}
Das Autoloading der Commands ist ab Laravel 11 nicht mehr nötig: app/Console/Commands/ wird automatisch gescannt.Die Scheduler-Definition verschieben Sie nach routes/console.php oder in withSchedule() in bootstrap/app.php.
// routes/console.php (Laravel 11 und höher)
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')->daily();
Nach der Migration löschen Sie app/Console/Kernel.php.
Löschen Sie keine eigenen Artisan-Command-Dateien im Verzeichnis app/Console/. Belassen Sie die Command-Dateien und entfernen Sie nur die Kernel-Klasse.
4

Exception-Handler (app/Exceptions/Handler.php) löschen

app/Exceptions/Handler.php steuerte das Reporten und Rendern von Exceptions.Alt (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) {
            //
        });
    }
}
Bei eigenen Anpassungen übertragen Sie diese in withExceptions() in bootstrap/app.php und löschen dann die Datei.
use Throwable;
use Illuminate\Http\Request;

->withExceptions(function (Exceptions $exceptions) {
    // Anpassung des Exception-Reportings
    $exceptions->report(function (Throwable $e) {
        // ...
    });

    // Anpassung des Renderings
    $exceptions->render(function (\App\Exceptions\InvalidOrderException $e, Request $request) {
        return response()->view('errors.invalid-order', status: 500);
    });
})
Zusätzlich definierte $dontFlash-Einträge lassen sich ebenfalls übertragen.
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->dontFlash([
        'my_sensitive_field',
    ]);
})
Nach dem Übertragen löschen Sie app/Exceptions/Handler.php.
5

RouteServiceProvider entfernen und Routen-Registrierung migrieren

app/Providers/RouteServiceProvider.php hat Routen-Dateien geladen und Rate Limits konfiguriert.Alt (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'));
        });
    }
}
Die Registrierung der Routen-Dateien verschieben Sie nach withRouting() in bootstrap/app.php.
->withRouting(
    web: __DIR__.'/../routes/web.php',
    api: __DIR__.'/../routes/api.php', // wenn API-Routen genutzt werden
    commands: __DIR__.'/../routes/console.php',
    health: '/up',
)
Rate Limits wandern 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());
    });
}
Verwenden Sie die Konstante HOME an anderer Stelle, ersetzen Sie sie durch eine URL-Zeichenkette oder verschieben Sie die Konstante in den AppServiceProvider.Nach der Migration löschen Sie app/Providers/RouteServiceProvider.php.
6

Service Provider aufräumen

In Laravel 10 gab es standardmäßig fünf Service Provider. Fassen Sie diese im AppServiceProvider.php zu einem zusammen.Zu entfernende Provider (Inhalte in AppServiceProvider verschieben, dann löschen):
DateiZiel
app/Providers/AuthServiceProvider.phpGate::policy() u. Ä. in AppServiceProvider::boot()
app/Providers/BroadcastServiceProvider.phpBei Broadcasting: withBroadcasting() in bootstrap/app.php
app/Providers/EventServiceProvider.phpEvent::listen() u. Ä. in AppServiceProvider::boot()
app/Providers/RouteServiceProvider.phpIm vorigen Schritt bereits behandelt
Beispiel: Migration des AuthServiceProvider:
// Alt: 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();
    }
}
// Neu: app/Providers/AppServiceProvider.php
// Wenn Sie die Standard-Namenskonvention einhalten, ist das dank Auto-Discovery überflüssig
use Illuminate\Support\Facades\Gate;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Gate::policy(Post::class, PostPolicy::class);
    }
}
Nachdem Sie die überflüssigen Provider-Dateien gelöscht haben, entfernen Sie das providers-Array aus config/app.php.
// Das providers-Array in config/app.php
// Kann komplett entfernt werden
'providers' => ServiceProvider::defaultProviders()->merge([
App\Providers\AppServiceProvider::class,
// Die Einträge der gelöschten Provider ebenfalls entfernen
// App\Providers\AuthServiceProvider::class, // entfernen
// App\Providers\EventServiceProvider::class, // entfernen
// App\Providers\RouteServiceProvider::class, // entfernen
])->toArray(),
Legen Sie zusätzlich bootstrap/providers.php an, um die neue Struktur zu bedienen.
<?php

// bootstrap/providers.php
return [
    App\Providers\AppServiceProvider::class,
];
Existiert bootstrap/providers.php, verwendet Laravel diese Datei vorrangig als Provider-Liste.
7

Controller-Basisklasse aktualisieren

In Laravel 10 nutzte die Controller-Basisklasse die Traits AuthorizesRequests und ValidatesRequests. Die neue Basisklasse in Laravel 11 ist eine schlichte abstrakte Klasse ohne diese Traits.Alt (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;
}
Neu (Laravel 11 und höher):
<?php

namespace App\Http\Controllers;

abstract class Controller
{
    //
}
Die von den Traits gelieferten Funktionen ersetzen Sie wie folgt:
Alte Vorgehensweise (per Trait)Neue Vorgehensweise
$this->validate($request, $rules)$request->validate($rules)
$this->authorize('update', $post)Gate::authorize('update', $post)
$this->authorizeResource(Post::class)Kein einfaches Äquivalent in Laravel 11 — belassen Sie App\Http\Controllers\Controller in Laravel-10-Form
Nutzen bestehende Controller Methoden aus den Traits, können Sie entweder die einzelnen Controller anpassen oder die Traits in der Controller-Basisklasse belassen. Sie müssen nicht alles auf einmal ändern.
Wenn von Breeze oder Jetstream generierte Auth-Controller $this->validate() oder $this->authorize() aufrufen, prüfen Sie das Verhalten unbedingt, bevor Sie die Basisklasse ändern.
8

Überflüssige config-Dateien entfernen

Dateien, die Sie nicht vom Standard abweichend geändert haben — config/cors.php, config/hashing.php, config/view.php u. a. — können Sie entfernen. Angepasste Dateien belassen Sie.
9

public/index.php aktualisieren

Diese Datei wurde für die neue Struktur überarbeitet; ersetzen Sie sie komplett.
<?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

artisan aktualisieren

Ersetzen Sie auch die artisan-Datei vollständig.
#!/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

tests/TestCase.php aktualisieren

Der Trait CreatesApplication wird nicht mehr benötigt. tests/CreatesApplication.php kann gelöscht werden.
<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    //
}
12

.env, .env.example und phpunit.xml aktualisieren

CACHE_DRIVER heißt jetzt CACHE_STORE, und neue Einträge sind hinzugekommen. Passen Sie diese bei Bedarf an. Da diese Änderungen Config-Dateien und die Produktivumgebung betreffen, gehen Sie umsichtig vor. Sie müssen den Änderungen nicht zwanghaft folgen.
13

Funktionscheck

Prüfen Sie das Ergebnis in dieser Reihenfolge.
# Konfigurationscaches leeren
php artisan config:clear
php artisan route:clear
php artisan cache:clear

# Routen prüfen
php artisan route:list

# Tests ausführen
php artisan test
Bei Problemen stellen Sie die gelöschten Dateien aus dem Backup wieder her und prüfen die Fehlermeldungen.

Projektstruktur nach der Migration

Die Struktur nach der Migration sieht so aus (Änderungen zur alten Struktur hervorgehoben):
app/
├── Console/
│   └── Commands/          ← Eigene Commands bleiben erhalten
│   (Kernel.php entfernt)
├── Exceptions/
│   (Handler.php entfernt)
├── Http/
│   ├── Controllers/
│   │   └── Controller.php ← Schlichte abstrakte Klasse
│   └── Middleware/        ← Eigene Middleware bleibt erhalten
│   (Kernel.php entfernt)
├── Models/
└── Providers/
    └── AppServiceProvider.php ← auf 1 Datei zusammengeführt
    (AuthServiceProvider.php entfernt)
    (BroadcastServiceProvider.php entfernt)
    (EventServiceProvider.php entfernt)
    (RouteServiceProvider.php entfernt)
bootstrap/
├── app.php                ← durch Application::configure() ersetzt
└── providers.php          ← neu angelegt
routes/
├── web.php
├── api.php                ← nur bei Bedarf
└── console.php            ← Scheduler-Definitionen hier

Zusammenfassung

Was migriert wirdAlter OrtNeuer Ort
HTTP-Middlewareapp/Http/Kernel.phpwithMiddleware() in bootstrap/app.php
Exception-Handlingapp/Exceptions/Handler.phpwithExceptions() in bootstrap/app.php
Scheduler-Definitionapp/Console/Kernel.phproutes/console.php
Routen-Registrierungapp/Providers/RouteServiceProvider.phpwithRouting() in bootstrap/app.php
Provider-Listeproviders-Array in config/app.phpbootstrap/providers.php
Policy-Registrierungapp/Providers/AuthServiceProvider.phpAutomatische Registrierung; manuell in AppServiceProvider::boot()
Event-Listenerapp/Providers/EventServiceProvider.phpAutomatische Registrierung; manuell in AppServiceProvider::boot()
Zuletzt geändert am 13. Juli 2026