Zum Hauptinhalt springen

Vergleich mit Laravel 10 und älter

Mit Laravel 11 wurde die Anwendungsstruktur als „Slim Application Skeleton” grundlegend überarbeitet. Die größte Änderung: verstreute Einstellungen werden nun an einer einzigen Stelle in bootstrap/app.php gebündelt.
AspektLaravel 10 und älterLaravel 11 und höher
HTTP-Kernelapp/Http/Kernel.phpEntfällt (in das Framework integriert)
Console-Kernelapp/Console/Kernel.phpEntfällt (in routes/console.php verschoben)
Exception-Handlerapp/Exceptions/Handler.phpEntfällt (in bootstrap/app.php gebündelt)
Service Provider5 DateienNur noch AppServiceProvider.php
Routen-Dateienweb.php / api.php standardmäßigNur web.php standard, api.php per Opt-in
BootstrapEinstellungen auf mehrere Dateien verteiltIn bootstrap/app.php gebündelt
Standard-DBMySQL/PostgreSQLSQLite
Diese Änderung betrifft neue Projekte. Bestehende Laravel-10-Anwendungen laufen nach einem Upgrade in ihrer alten Struktur weiter.

Neue Verzeichnis- und Dateistruktur

Struktur des Skeletons

laravel-app/
├── app/
│   ├── Http/
│   │   └── Controllers/
│   ├── Models/
│   │   └── User.php
│   └── Providers/
│       └── AppServiceProvider.php
├── bootstrap/
│   ├── app.php          ← Zentraler Punkt der App-Konfiguration
│   ├── cache/
│   └── providers.php    ← Liste der Service Provider
├── config/
├── database/
├── public/
│   └── index.php        ← Einstiegspunkt
├── resources/
├── routes/
│   ├── web.php          ← Web-Routen (Standard)
│   └── console.php      ← Artisan-Commands und Scheduler
├── storage/
└── tests/

bootstrap/app.php — Zentrum der Konfiguration

<?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): void {
        //
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();
Mit dieser einen Datei konfigurieren Sie Routing, Middleware und Exception-Handling. Was in Laravel 10 auf app/Http/Kernel.php, app/Console/Kernel.php und app/Exceptions/Handler.php verteilt war, ist hier zusammengefasst.

bootstrap/providers.php — Liste der Service Provider

<?php

use App\Providers\AppServiceProvider;

return [
    AppServiceProvider::class,
];
Diese Datei registriert die Service Provider. In Laravel 10 stand das im providers-Array von config/app.php; jetzt ist es in bootstrap/providers.php getrennt. Standardmäßig ist nur der AppServiceProvider eingetragen.
Beim composer require eines Pakets aktualisiert dieses ggf. bootstrap/providers.php automatisch. config/app.php wird zwar noch gelesen, aber Neuregistrierungen erfolgen bevorzugt in bootstrap/providers.php.

Änderungen am routes/-Verzeichnis

routes/
├── web.php      ← Web-Routen (standardmäßig geladen)
└── console.php  ← Artisan-Commands und Scheduler-Definitionen
api.php und channels.php sind standardmäßig nicht vorhanden. Erzeugen Sie sie bei Bedarf per Artisan.
# API-Routen ergänzen (api.php + Sanctum installieren)
php artisan install:api

# Broadcasting ergänzen (channels.php + Reverb installieren)
php artisan install:broadcasting
In routes/console.php können Sie auch Scheduler-Aufgaben definieren.
<?php

use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schedule;

Artisan::command('inspire', function () {
    $this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');

Schedule::command('emails:send')->daily();

Entfallene Dateien

Der HTTP-Kernel ist in Illuminate\Foundation\Http\Kernel im Framework integriert. Middleware-Anpassungen erfolgen über withMiddleware() in bootstrap/app.php.
// Laravel 10: app/Http/Kernel.php
protected $middleware = [
    \Illuminate\Http\Middleware\TrustProxies::class,
    // ...
];

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        // ...
    ],
];
// Laravel 11+: bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->web(append: [
        EnsureUserIsSubscribed::class,
    ]);

    $middleware->validateCsrfTokens(except: ['stripe/*']);
})
Die beiden Aufgaben des Console-Kernels sind getrennt: Artisan-Commands liegen in app/Console/Commands/ und werden automatisch erkannt, Scheduler-Definitionen stehen in routes/console.php.
// Laravel 10: app/Console/Kernel.php
protected function schedule(Schedule $schedule): void
{
    $schedule->command('emails:send')->daily();
}
// Laravel 11+: routes/console.php
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')->daily();
Der Exception-Handler ist in Illuminate\Foundation\Exceptions\Handler im Framework integriert. Anpassungen erfolgen über withExceptions() in bootstrap/app.php.
// Laravel 10: app/Exceptions/Handler.php
public function register(): void
{
    $this->reportable(function (InvalidOrderException $e) {
        // ...
    });
}
// Laravel 11+: bootstrap/app.php
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->report(function (InvalidOrderException $e) {
        // ...
    });
})

Wie Application::configure() funktioniert

Implementierung im Framework

Application::configure() ist eine statische Methode von Illuminate\Foundation\Application.
// Aus Illuminate\Foundation\Application
public static function configure(?string $basePath = null)
{
    $basePath = match (true) {
        is_string($basePath) => $basePath,
        default => static::inferBasePath(),
    };

    return (new Configuration\ApplicationBuilder(new static($basePath)))
        ->withKernels()
        ->withEvents()
        ->withCommands()
        ->withProviders();
}
Die Methode:
  1. Bestimmt aus basePath das Wurzelverzeichnis der Anwendung.
  2. Erzeugt eine Application-Instanz.
  3. Verpackt sie in einem ApplicationBuilder und wendet Standardeinstellungen an.
  4. Gibt den ApplicationBuilder zurück.
Wichtig: In configure() werden bereits withKernels(), withEvents(), withCommands() und withProviders() aufgerufen — in bootstrap/app.php müssen Sie sie nicht erneut aufrufen.

Konfiguration per Method-Chain

Application::configure(basePath: dirname(__DIR__))  // liefert ApplicationBuilder
    ->withRouting(...)       // konfiguriert Routing, gibt $this zurück
    ->withMiddleware(...)    // konfiguriert Middleware, gibt $this zurück
    ->withExceptions(...)    // konfiguriert Exception-Handling, gibt $this zurück
    ->create();              // gibt die Application-Instanz zurück
create() extrahiert aus dem ApplicationBuilder die Application; genau diese Instanz gibt bootstrap/app.php per return zurück.

Ablauf vom Request bis zum App-Start

public/index.php ist der Einstiegspunkt; die Datei lädt bootstrap/app.php und erhält die Application. Danach führt der HTTP-Kernel den Request durch die Middleware-Pipeline, der Router dispatched an den Controller.

Wichtige Methoden des ApplicationBuilder im Detail

withRouting() — interne Routen-Registrierung

public function withRouting(
    ?Closure $using = null,
    array|string|null $web = null,
    array|string|null $api = null,
    ?string $commands = null,
    ?string $channels = null,
    ?string $pages = null,
    ?string $health = null,
    string $apiPrefix = 'api',
    ?callable $then = null
)
Intern wird an AppRouteServiceProvider::loadRoutesUsing() ein Callback registriert; beim Booting wird der AppRouteServiceProvider eingehängt.
// Vereinfachte interne Verarbeitung von withRouting()
protected function buildRoutingCallback(...)
{
    return function () use ($web, $api, $pages, $health, $apiPrefix, $then) {
        if (is_string($api) || is_array($api)) {
            Route::middleware('api')->prefix($apiPrefix)->group($api);
        }

        if (is_string($health)) {
            Route::get($health, function () {
                Event::dispatch(new DiagnosingHealth);
                return response(View::file(...), status: 200);
            });
        }

        if (is_string($web) || is_array($web)) {
            Route::middleware('web')->group($web);
        }

        if (is_callable($then)) {
            $then($this->app);
        }
    };
}
Wichtige Punkte:
  • Für api-Routen werden automatisch die Middleware-Gruppe api und der Prefix /api angewandt.
  • Ein String an health registriert einen Health-Check-Endpoint (Default /up) automatisch.
  • Der health-Pfad ist im Wartungsmodus ausgeschlossen (PreventRequestsDuringMaintenance::except()).
  • api wird vor web registriert. Bei identischen Pfaden hat api Vorrang.
  • Ein String an pages aktiviert das Routing von Laravel Folio.

withMiddleware() — Middleware anpassen

public function withMiddleware(?callable $callback = null)
{
    $this->app->afterResolving(HttpKernel::class, function ($kernel) use ($callback) {
        $middleware = (new Middleware)
            ->redirectGuestsTo(fn () => route('login'));

        if (! is_null($callback)) {
            $callback($middleware);
        }

        $kernel->setGlobalMiddleware($middleware->getGlobalMiddleware());
        $kernel->setMiddlewareGroups($middleware->getMiddlewareGroups());
        $kernel->setMiddlewareAliases($middleware->getMiddlewareAliases());
        // ...
    });

    return $this;
}
withMiddleware() führt den Callback aus, nachdem der HttpKernel aufgelöst wurde — mittels afterResolving(). Dem Callback wird ein Middleware-Objekt mit vielen Anpassungsmethoden übergeben.
->withMiddleware(function (Middleware $middleware) {
    // Globale Middleware anhängen
    $middleware->append(MyGlobalMiddleware::class);

    // Middleware zur web-Gruppe hinzufügen
    $middleware->web(append: [EnsureUserIsSubscribed::class]);

    // Middleware in der api-Gruppe ersetzen
    $middleware->api(replace: [
        OldMiddleware::class => NewMiddleware::class,
    ]);

    // Pfade vom CSRF-Schutz ausnehmen
    $middleware->validateCsrfTokens(except: ['stripe/*', 'webhook/*']);

    // Weiterleitung für Gäste ändern
    $middleware->redirectGuestsTo('/custom-login');

    // Priorität setzen
    $middleware->priority([
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    ]);
})

withExceptions() — Exception-Handling konfigurieren

public function withExceptions(?callable $using = null)
{
    $this->app->singleton(
        \Illuminate\Contracts\Debug\ExceptionHandler::class,
        \Illuminate\Foundation\Exceptions\Handler::class
    );

    if ($using !== null) {
        $this->app->afterResolving(
            \Illuminate\Foundation\Exceptions\Handler::class,
            fn ($handler) => $using(new Exceptions($handler)),
        );
    }

    return $this;
}
withExceptions() registriert die Handler-Klasse als Singleton und setzt den Callback per afterResolving(). Dem Callback wird ein Exceptions-Wrapper übergeben.
->withExceptions(function (Exceptions $exceptions) {
    // Bestimmte Exceptions nicht melden
    $exceptions->dontReport(MissedFlightException::class);

    // Eigenes Reporting für bestimmte Exceptions
    $exceptions->report(function (InvalidOrderException $e) {
        // z. B. Slack benachrichtigen
    });

    // HTTP-Response bestimmter Exceptions anpassen
    $exceptions->render(function (NotFoundHttpException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json(['message' => 'Not Found'], 404);
        }
    });

    // Throttling (nicht dieselbe Exception in Folge melden)
    $exceptions->throttle(function (Throwable $e) {
        return Limit::perMinute(20);
    });
})

withProviders() — Service Provider registrieren

public function withProviders(array $providers = [], bool $withBootstrapProviders = true)
{
    RegisterProviders::merge(
        $providers,
        $withBootstrapProviders
            ? $this->app->getBootstrapProvidersPath()
            : null
    );

    return $this;
}
Da withProviders() innerhalb von Application::configure() automatisch aufgerufen wird, lädt Laravel bootstrap/providers.php von selbst. Für zusätzliche Provider rufen Sie es in bootstrap/app.php explizit auf.
Application::configure(basePath: dirname(__DIR__))
    ->withProviders([
        // Zusätzlich zur bootstrap/providers.php Provider hinzufügen
        App\Providers\CustomServiceProvider::class,
    ])
    ->withRouting(...)
    ->create();
withBootstrapProviders: false überspringt bootstrap/providers.php. Nur mit gutem Grund weglassen.

Weitere wichtige Methoden

MethodeBeschreibung
withKernels()Registriert HTTP-/Console-Kernel als Singleton. Wird von configure() automatisch aufgerufen.
withEvents()Aktiviert Event-Discovery. Wird von configure() automatisch aufgerufen.
withCommands(array $commands)Fügt zusätzliche Artisan-Commands oder Command-Verzeichnisse hinzu.
withSchedule(callable $callback)Scheduler-Definitionen in bootstrap/app.php ablegen.
withBroadcasting(string $channels)Broadcasting-Channels-Datei registrieren.
withBindings(array $bindings)Container-Bindings registrieren.
withSingletons(array $singletons)Singleton-Bindings registrieren.
registered(callable $callback)Callback, nachdem Service Provider registriert wurden.
booting(callable $callback)Callback während des Bootings.
booted(callable $callback)Callback nach dem Booting.
create()Gibt die Application-Instanz zurück.

Designintention: warum es so ist

„Code-First”-Konfiguration

In app/Http/Kernel.php (Laravel 10) wurde Middleware als Array aufgelistet — eher wie eine Config-Datei. Das erschwerte Typunterstützung und IDE-Feedback. In Laravel 11 hat sich das zu einem Callback-Stil geändert: withMiddleware(function (Middleware $middleware) { ... }). So funktioniert Typvervollständigung, und Sie schreiben Verzweigungen oder Schleifen natürlich für dynamische Konfiguration.

Von „Convention over Configuration” zu „Explicit Configuration”

api.php ist Opt-in geworden, damit Anwendungen ohne API-Routen nicht permanent die api-Middleware-Gruppe laden. Was nicht gebraucht wird, existiert per Default nicht.

Nutzung von afterResolving()-Hooks

withMiddleware() und withExceptions() verwenden afterResolving(), um Konfigurationsreihenfolge-Probleme zu vermeiden. Die Methoden am ApplicationBuilder werden vor dem vollständigen Start aufgerufen; die tatsächliche Anwendung erfolgt verzögert beim ersten Auflösen des Kernels.

Praktische Anpassungsbeispiele

API und Web nebeneinander

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
        apiPrefix: 'api/v1',  // Standard /api geändert
    )
    ->withMiddleware(function (Middleware $middleware): void {
        //
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();

Middleware anpassen

->withMiddleware(function (Middleware $middleware) {
    // Authentifizierungs-Check für Web-Routen
    $middleware->web(append: [
        \App\Http\Middleware\EnsureEmailIsVerified::class,
    ]);

    // Bestimmte Middleware für API-Routen entfernen
    $middleware->api(remove: [
        \Illuminate\Session\Middleware\StartSession::class,
    ]);

    // Webhook-Endpoints vom CSRF-Schutz ausnehmen
    $middleware->validateCsrfTokens(except: [
        'webhook/*',
        'stripe/webhook',
    ]);

    // Alias vergeben
    $middleware->alias([
        'subscribed' => \App\Http\Middleware\EnsureUserIsSubscribed::class,
    ]);
})

Scheduler in bootstrap/app.php bündeln

Sie können den Scheduler in routes/console.php beschreiben — oder alles über withSchedule() in bootstrap/app.php bündeln.
use Illuminate\Console\Scheduling\Schedule;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withSchedule(function (Schedule $schedule) {
        $schedule->command('emails:send')->daily();
        $schedule->command('reports:generate')->weeklyOn(1, '8:00');
        $schedule->job(new PruneOldRecords)->daily();
    })
    ->withMiddleware(function (Middleware $middleware): void {
        //
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();

Exception-Handling anpassen

->withExceptions(function (Exceptions $exceptions) {
    // API-Requests immer als JSON beantworten
    $exceptions->render(function (Throwable $e, Request $request) {
        if ($request->is('api/*') || $request->wantsJson()) {
            $status = match (true) {
                $e instanceof NotFoundHttpException => 404,
                $e instanceof AuthenticationException => 401,
                $e instanceof AuthorizationException => 403,
                $e instanceof ValidationException => 422,
                default => 500,
            };

            return response()->json([
                'message' => $e->getMessage(),
            ], $status);
        }
    });

    // Nur in Produktion Slack benachrichtigen
    if (app()->isProduction()) {
        $exceptions->report(function (Throwable $e) {
            app(SlackNotifier::class)->notify($e);
        })->stop();
    }
})

Container-Bindings in bootstrap/app.php verwalten

In kleinen Anwendungen können Sie einfache Bindings statt im AppServiceProvider direkt in bootstrap/app.php beschreiben.
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(...)
    ->withSingletons([
        \App\Contracts\PaymentGateway::class => \App\Services\StripeGateway::class,
        \App\Contracts\MailService::class => \App\Services\SendgridMailService::class,
    ])
    ->withMiddleware(...)
    ->withExceptions(...)
    ->create();

Reihenfolge des Anwendungsstarts

Beim Start der Laravel-Anwendung laufen Service Provider und Application-Hooks in dieser Reihenfolge:
  1. register() aller Service Provider.
  2. registered() der Application.
  3. booting() der Application.
  4. boot() aller Service Provider.
  5. booted() der Application.
Mit folgendem Code im AppServiceProvider lässt sich das nachverfolgen.
class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        info('1. AppServiceProvider register');

        $this->booting(function () {
            info('4. AppServiceProvider booting');
        });

        $this->booted(function () {
            info('5. AppServiceProvider booted');
        });

        $this->app->registered(function () {
            info('2. app registered');
        });

        $this->app->booting(function () {
            info('3. app booting');
        });

        $this->app->booted(function () {
            info('6. AppServiceProvider@register app booted');
        });
    }

    public function boot(): void
    {
        $this->app->booted(function () {
            info('7. AppServiceProvider@boot app booted');
        });
    }
}
registered(), booting() und booted() am ApplicationBuilder registrieren nur Callbacks. Üblicherweise braucht man sie nicht, aber sie erlauben Sonderfälle — etwa Anpassungen am bereits gebooteten Kernel per booted().
// bootstrap/app.php

use Illuminate\Contracts\Http\Kernel;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(...)
    ->withMiddleware(...)
    ->withExceptions(...)
    ->booted(function (Application $app) {
        $kernel = $app->make(Kernel::class);

        //

        $app->instance(Kernel::class, $kernel);
    })
    ->create();

Nächste Schritte

Service Container

Verstehen Sie den Service Container, den der ApplicationBuilder intern nutzt.

FAQ zur neuen App-Struktur

Häufige Fragen und Antworten zur neuen Anwendungsstruktur.
Zuletzt geändert am 13. Juli 2026