Passer au contenu principal

Comparaison avec Laravel 10 et antérieur

Laravel 11 introduit le « Slim Application Skeleton », une refonte majeure de la structure d’application. Le changement le plus important est la suppression de la dispersion de la configuration au profit d’une centralisation dans un seul endroit : bootstrap/app.php.
ÉlémentLaravel 10 et antérieurLaravel 11+
Kernel HTTPapp/Http/Kernel.phpSupprimé (intégré au framework)
Kernel Consoleapp/Console/Kernel.phpSupprimé (déplacé dans routes/console.php)
Gestionnaire d’exceptionsapp/Exceptions/Handler.phpSupprimé (centralisé dans bootstrap/app.php)
Service providers5 fichiersUn seul fichier AppServiceProvider.php
Fichiers de routesweb.php / api.php par défautSeul web.php par défaut, api.php en opt-in
BootstrapConfiguration dispersée dans plusieurs fichiersCentralisée dans bootstrap/app.php
Base de données par défautMySQL/PostgreSQLSQLite
Ces changements concernent les nouveaux projets. Les applications Laravel 10 existantes mises à niveau continuent de fonctionner avec l’ancienne structure telle quelle.

Nouvelle structure de répertoires et de fichiers

Structure du squelette

laravel-app/
├── app/
│   ├── Http/
│   │   └── Controllers/
│   ├── Models/
│   │   └── User.php
│   └── Providers/
│       └── AppServiceProvider.php
├── bootstrap/
│   ├── app.php          ← Cœur de la configuration de l'application
│   ├── cache/
│   └── providers.php    ← Liste des service providers
├── config/
├── database/
├── public/
│   └── index.php        ← Point d'entrée
├── resources/
├── routes/
│   ├── web.php          ← Routes web (par défaut)
│   └── console.php      ← Commandes Artisan et tâches planifiées
├── storage/
└── tests/

bootstrap/app.php — le cœur de la configuration de l’application

<?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();
Ce seul fichier permet de configurer le routage, les middlewares et la gestion des exceptions. La configuration qui était dispersée dans app/Http/Kernel.php, app/Console/Kernel.php et app/Exceptions/Handler.php sous Laravel 10 est ici centralisée.

bootstrap/providers.php — liste des service providers

<?php

use App\Providers\AppServiceProvider;

return [
    AppServiceProvider::class,
];
Ce fichier est l’endroit où enregistrer les service providers. Sous Laravel 10, ils étaient déclarés dans le tableau providers de config/app.php, mais ils sont désormais séparés dans bootstrap/providers.php. Par défaut sous Laravel 11, seul AppServiceProvider est présent.
Lorsque vous installez un package avec composer require, celui-ci peut mettre à jour automatiquement bootstrap/providers.php. config/app.php n’est pas devenu obsolète, mais l’emplacement recommandé pour les nouveaux enregistrements est désormais bootstrap/providers.php.

Changements dans le répertoire routes/

routes/
├── web.php      ← Routes web (chargées par défaut)
└── console.php  ← Définition des commandes Artisan et des tâches planifiées
api.php et channels.php ne sont plus présents par défaut. Générez-les si nécessaire avec les commandes Artisan.
# Ajouter les routes API (installe api.php + Sanctum)
php artisan install:api

# Ajouter le broadcasting (installe channels.php + Reverb, etc.)
php artisan install:broadcasting
routes/console.php permet également de définir les tâches planifiées.
<?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();

Fichiers supprimés

Le kernel HTTP a été intégré dans Illuminate\Foundation\Http\Kernel au sein du framework. La personnalisation des middlewares se fait via withMiddleware() dans bootstrap/app.php.
// Laravel 10 et antérieur : 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/*']);
})
Les deux responsabilités du kernel Console ont été séparées. Les commandes Artisan sont placées dans app/Console/Commands/ et détectées automatiquement, et les tâches planifiées sont écrites dans routes/console.php.
// Laravel 10 et antérieur : 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();
Le gestionnaire d’exceptions a été intégré dans Illuminate\Foundation\Exceptions\Handler au sein du framework. La personnalisation se fait via withExceptions() dans bootstrap/app.php.
// Laravel 10 et antérieur : 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) {
        // ...
    });
})

Fonctionnement de Application::configure()

Implémentation interne dans le framework

Application::configure() est une méthode statique de Illuminate\Foundation\Application.
// Extrait de 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();
}
Cette méthode effectue les opérations suivantes :
  1. Détermine le répertoire racine de l’application à partir de basePath
  2. Crée une instance de Application
  3. L’enveloppe dans ApplicationBuilder et applique les paramètres par défaut
  4. Retourne l’instance de ApplicationBuilder
Le point important est que withKernels() / withEvents() / withCommands() / withProviders() sont déjà appelées à l’intérieur de configure(). Il n’est pas nécessaire de les appeler à nouveau dans bootstrap/app.php.

Le flux de configuration par chaînage de méthodes

Application::configure(basePath: dirname(__DIR__))  // retourne ApplicationBuilder
    ->withRouting(...)       // configure le routage et retourne $this
    ->withMiddleware(...)    // configure les middlewares et retourne $this
    ->withExceptions(...)    // configure la gestion des exceptions et retourne $this
    ->create();              // retourne l'instance Application
L’appel de create() extrait l’instance Application depuis ApplicationBuilder, et c’est cette instance Application que bootstrap/app.php retourne (return).

Flux depuis la requête jusqu’au démarrage de l’application

public/index.php sert de point d’entrée, charge bootstrap/app.php et récupère l’Application. Le HTTP Kernel fait ensuite passer la requête par le pipeline de middlewares, puis le routeur la dispatche vers le contrôleur.

Analyse des principales méthodes de ApplicationBuilder

withRouting() — traitement interne de l’enregistrement du routage

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
)
En interne, un callback est enregistré via AppRouteServiceProvider::loadRoutesUsing(), puis AppRouteServiceProvider est enregistré au moment du booting de l’application.
// Traitement interne de withRouting() (version simplifiée)
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);
        }
    };
}
Points clés :
  • Les routes api reçoivent automatiquement le groupe de middlewares api et le préfixe /api
  • En passant une chaîne à health, un endpoint de health check (par défaut /up) est automatiquement enregistré
  • Le chemin health est exclu même en mode maintenance (configuré via PreventRequestsDuringMaintenance::except())
  • Notez que api est enregistré avant web. Si vous définissez des routes web et API sur le même chemin, la route API est prioritaire
  • En passant une chaîne à pages, le routage Laravel Folio est activé

withMiddleware() — personnalisation des middlewares

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() exécute le callback après que HttpKernel ait été résolu. C’est parce qu’il utilise le hook afterResolving(). L’objet Middleware passé au callback propose de nombreuses méthodes de personnalisation.
->withMiddleware(function (Middleware $middleware) {
    // Ajouter un middleware global
    $middleware->append(MyGlobalMiddleware::class);

    // Ajouter un middleware au groupe web
    $middleware->web(append: [EnsureUserIsSubscribed::class]);

    // Remplacer un middleware dans le groupe api
    $middleware->api(replace: [
        OldMiddleware::class => NewMiddleware::class,
    ]);

    // Configurer les exclusions CSRF
    $middleware->validateCsrfTokens(except: ['stripe/*', 'webhook/*']);

    // Modifier la redirection des utilisateurs non authentifiés
    $middleware->redirectGuestsTo('/custom-login');

    // Définir l'ordre de priorité des middlewares
    $middleware->priority([
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    ]);
})

withExceptions() — configuration de la gestion des exceptions

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() enregistre la classe Handler du framework en tant que singleton, puis configure le callback via afterResolving(). Un objet wrapper Exceptions est passé au callback.
->withExceptions(function (Exceptions $exceptions) {
    // Ne pas reporter certaines exceptions
    $exceptions->dontReport(MissedFlightException::class);

    // Rapport personnalisé pour certaines exceptions
    $exceptions->report(function (InvalidOrderException $e) {
        // Notification Slack par exemple
    });

    // Personnaliser la réponse HTTP pour certaines exceptions
    $exceptions->render(function (NotFoundHttpException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json(['message' => 'Not Found'], 404);
        }
    });

    // Throttling (ne pas reporter la même exception en boucle)
    $exceptions->throttle(function (Throwable $e) {
        return Limit::perMinute(20);
    });
})

withProviders() — enregistrement des service providers

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

    return $this;
}
withProviders() est appelée par défaut à l’intérieur de Application::configure(), donc bootstrap/providers.php est chargé automatiquement. Pour passer des providers supplémentaires, il faut l’appeler explicitement dans bootstrap/app.php.
Application::configure(basePath: dirname(__DIR__))
    ->withProviders([
        // Ajouter des providers en plus du bootstrap/providers.php par défaut
        App\Providers\CustomServiceProvider::class,
    ])
    ->withRouting(...)
    ->create();
Passer withBootstrapProviders: false empêche le chargement de bootstrap/providers.php. Sauf raison particulière, omettez cet argument.

Autres méthodes importantes

MéthodeDescription
withKernels()Enregistre les kernels HTTP / Console en singletons. Appelé automatiquement par configure()
withEvents()Active la découverte d’événements. Appelé automatiquement par configure()
withCommands(array $commands)Enregistre des classes de commandes Artisan ou des répertoires supplémentaires
withSchedule(callable $callback)Définit les tâches planifiées dans bootstrap/app.php
withBroadcasting(string $channels)Enregistre le fichier des channels de broadcasting
withBindings(array $bindings)Enregistre des bindings du conteneur
withSingletons(array $singletons)Enregistre des bindings singletons
registered(callable $callback)Ajoute un callback exécuté après l’enregistrement des service providers
booting(callable $callback)Ajoute un callback exécuté au moment du booting
booted(callable $callback)Ajoute un callback exécuté au moment du booted
create()Retourne l’instance de Application

Intention de conception : pourquoi il en est ainsi

Une configuration « code-first »

Sous Laravel 10 et antérieur, app/Http/Kernel.php utilisait un style de listing des middlewares dans des tableaux. C’était proche d’un fichier de configuration, avec pour inconvénient un faible support du système de types PHP et des IDE. Laravel 11 est passé à un style callback withMiddleware(function (Middleware $middleware) { ... }). Cela permet la complétion typée et une écriture naturelle de configurations dynamiques utilisant conditions, boucles et autre logique.

De la « convention plutôt que configuration » à la « configuration explicite »

Rendre api.php opt-in a permis de résoudre le fait que le groupe de middlewares api était toujours chargé même dans les applications n’utilisant pas de routes API. Le principe est : les fonctionnalités non utilisées n’existent pas par défaut.

Utilisation du hook afterResolving()

Utiliser afterResolving() dans withMiddleware() et withExceptions() permet d’éviter les problèmes d’ordre de configuration. Les méthodes de ApplicationBuilder sont appelées avant le démarrage complet de l’application, mais le traitement réel (application des paramètres au kernel) est exécuté de manière différée lors de la première résolution du kernel.

Exemples pratiques de personnalisation

Coexistence d’API et de Web

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',  // change le préfixe par défaut /api
    )
    ->withMiddleware(function (Middleware $middleware): void {
        //
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();

Personnalisation des middlewares

->withMiddleware(function (Middleware $middleware) {
    // Ajouter un middleware de vérification d'authentification aux routes web
    $middleware->web(append: [
        \App\Http\Middleware\EnsureEmailIsVerified::class,
    ]);

    // Exclure certains middlewares des routes API
    $middleware->api(remove: [
        \Illuminate\Session\Middleware\StartSession::class,
    ]);

    // Exclure les endpoints webhook du CSRF
    $middleware->validateCsrfTokens(except: [
        'webhook/*',
        'stripe/webhook',
    ]);

    // Définir un alias pour certains middlewares
    $middleware->alias([
        'subscribed' => \App\Http\Middleware\EnsureUserIsSubscribed::class,
    ]);
})

Centraliser les tâches planifiées dans bootstrap/app.php

Les tâches planifiées peuvent être écrites dans routes/console.php, mais withSchedule() permet de les regrouper dans bootstrap/app.php.
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();

Personnalisation de la gestion des exceptions

->withExceptions(function (Exceptions $exceptions) {
    // Toujours retourner en JSON pour les requêtes API
    $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);
        }
    });

    // Notifier Slack uniquement en production
    if (app()->isProduction()) {
        $exceptions->report(function (Throwable $e) {
            app(SlackNotifier::class)->notify($e);
        })->stop();
    }
})

Gérer les bindings du conteneur dans bootstrap/app.php

Pour une application de petite taille, vous pouvez écrire des bindings simples directement dans bootstrap/app.php plutôt que dans AppServiceProvider.
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();

Ordre du processus de démarrage de Laravel

Au démarrage d’une application Laravel, les service providers et les hooks de l’Application sont exécutés dans l’ordre suivant :
  1. Exécution de register() de tous les ServiceProviders
  2. registered() de l’Application
  3. booting() de l’Application
  4. Exécution de boot() de tous les ServiceProviders
  5. booted() de l’Application
Vous pouvez vérifier cet ordre d’exécution en ajoutant le code suivant à AppServiceProvider.
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');
        });
    }
}
Les méthodes registered(), booting() et booted() de ApplicationBuilder ne font qu’enregistrer des callbacks sur l’Application. Habituellement, il n’est pas nécessaire de les utiliser, mais elles permettent des opérations spéciales comme modifier le Kernel dont la préparation est terminée via 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();

Étapes suivantes

Conteneur de services

Comprenez le fonctionnement du conteneur de services utilisé en interne par ApplicationBuilder.

FAQ nouvelle structure

Réponses aux questions fréquentes sur la nouvelle structure d’application.
Dernière modification le 13 juillet 2026