> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Laravel 11 之后的应用结构

> 详细讲解 Laravel 11 引入的 Slim Application Skeleton 的全貌，以及从 Application::configure() 到 ApplicationBuilder 的内部实现。

## 与 Laravel 10 及之前的对比

Laravel 11 以「Slim Application Skeleton」为名，对应用结构进行了大规模改造。最大的变化是消除了配置的分散，将其汇总到 `bootstrap/app.php` 一个位置。

| 项目               | Laravel 10 及之前               | Laravel 11 之后                    |
| ---------------- | ---------------------------- | -------------------------------- |
| HTTP Kernel      | `app/Http/Kernel.php`        | 废弃（整合进框架内部）                      |
| Console Kernel   | `app/Console/Kernel.php`     | 废弃（迁移到 `routes/console.php`）     |
| 异常处理器            | `app/Exceptions/Handler.php` | 废弃（汇总到 `bootstrap/app.php`）      |
| Service Provider | 5 个文件                        | 仅 `AppServiceProvider.php` 一个    |
| 路由文件             | 默认包含 `web.php` / `api.php`   | 默认仅 `web.php`，`api.php` 需 opt-in |
| Bootstrap        | 配置分散在多个文件                    | 汇总到 `bootstrap/app.php`          |
| 默认数据库            | MySQL/PostgreSQL             | SQLite                           |

<Info>
  该变更**面向新项目**。已有的 Laravel 10 应用即使升级到新版本，旧结构仍可继续正常运行。
</Info>

## 新的目录与文件结构

### Skeleton 的目录构成

```
laravel-app/
├── app/
│   ├── Http/
│   │   └── Controllers/
│   ├── Models/
│   │   └── User.php
│   └── Providers/
│       └── AppServiceProvider.php
├── bootstrap/
│   ├── app.php          ← 应用配置的中心
│   ├── cache/
│   └── providers.php    ← Service Provider 列表
├── config/
├── database/
├── public/
│   └── index.php        ← 入口
├── resources/
├── routes/
│   ├── web.php          ← Web 路由（默认）
│   └── console.php      ← Artisan 命令与计划任务
├── storage/
└── tests/
```

### `bootstrap/app.php` — 应用配置的中心

```php theme={null}
<?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();
```

仅通过这一个文件，就可以配置路由、中间件与异常处理。Laravel 10 及之前分散在 `app/Http/Kernel.php`、`app/Console/Kernel.php`、`app/Exceptions/Handler.php` 三处的配置，都被汇总到这里。

### `bootstrap/providers.php` — Service Provider 列表

```php theme={null}
<?php

use App\Providers\AppServiceProvider;

return [
    AppServiceProvider::class,
];
```

该文件是 Service Provider 的注册位置。Laravel 10 中在 `config/app.php` 的 `providers` 数组里定义，现在被分离到了 `bootstrap/providers.php`。Laravel 11 的默认内容只有 `AppServiceProvider`。

<Tip>
  通过 `composer require` 安装包时，该包可能会自动更新 `bootstrap/providers.php`。`config/app.php` 并非不再被引用，但新增注册的推荐位置已变为 `bootstrap/providers.php`。
</Tip>

### `routes/` 目录的变化

```
routes/
├── web.php      ← Web 路由（默认加载）
└── console.php  ← 定义 Artisan 命令与计划任务
```

`api.php` 与 `channels.php` 默认不存在。有需要时可以用 Artisan 命令生成。

```shell theme={null}
# 添加 API 路由（安装 api.php 与 Sanctum）
php artisan install:api

# 添加广播（安装 channels.php 与 Reverb 等）
php artisan install:broadcasting
```

`routes/console.php` 中也可以定义计划任务。

```php theme={null}
<?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();
```

### 被废弃的文件

<AccordionGroup>
  <Accordion title="废弃 app/Http/Kernel.php">
    HTTP Kernel 被整合进框架内部的 `Illuminate\Foundation\Http\Kernel`。中间件的自定义在 `bootstrap/app.php` 的 `withMiddleware()` 中完成。

    ```php theme={null}
    // Laravel 10 及之前: app/Http/Kernel.php
    protected $middleware = [
        \Illuminate\Http\Middleware\TrustProxies::class,
        // ...
    ];

    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            // ...
        ],
    ];
    ```

    ```php theme={null}
    // Laravel 11 之后: bootstrap/app.php
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->web(append: [
            EnsureUserIsSubscribed::class,
        ]);

        $middleware->validateCsrfTokens(except: ['stripe/*']);
    })
    ```
  </Accordion>

  <Accordion title="废弃 app/Console/Kernel.php">
    Console Kernel 的两项职责被拆分。Artisan 命令放在 `app/Console/Commands/` 中会被自动发现，计划任务写在 `routes/console.php` 中。

    ```php theme={null}
    // Laravel 10 及之前: app/Console/Kernel.php
    protected function schedule(Schedule $schedule): void
    {
        $schedule->command('emails:send')->daily();
    }
    ```

    ```php theme={null}
    // Laravel 11 之后: routes/console.php
    use Illuminate\Support\Facades\Schedule;

    Schedule::command('emails:send')->daily();
    ```
  </Accordion>

  <Accordion title="废弃 app/Exceptions/Handler.php">
    异常处理器被整合进框架内部的 `Illuminate\Foundation\Exceptions\Handler`。自定义在 `bootstrap/app.php` 的 `withExceptions()` 中完成。

    ```php theme={null}
    // Laravel 10 及之前: app/Exceptions/Handler.php
    public function register(): void
    {
        $this->reportable(function (InvalidOrderException $e) {
            // ...
        });
    }
    ```

    ```php theme={null}
    // Laravel 11 之后: bootstrap/app.php
    ->withExceptions(function (Exceptions $exceptions) {
        $exceptions->report(function (InvalidOrderException $e) {
            // ...
        });
    })
    ```
  </Accordion>
</AccordionGroup>

## `Application::configure()` 的机制

### 框架内部的实现

`Application::configure()` 是 `Illuminate\Foundation\Application` 上的静态方法。

```php theme={null}
// 摘自 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();
}
```

该方法执行了以下处理：

1. 从 `basePath` 确定应用根目录
2. 创建 `Application` 实例
3. 用 `ApplicationBuilder` 包装并应用默认配置
4. 返回 `ApplicationBuilder` 实例

关键在于，`configure()` 内部**已经调用了 `withKernels()` / `withEvents()` / `withCommands()` / `withProviders()`**。你不需要在 `bootstrap/app.php` 中再次调用这些方法。

### 通过链式方法进行配置

```php theme={null}
Application::configure(basePath: dirname(__DIR__))  // 返回 ApplicationBuilder
    ->withRouting(...)       // 配置路由，返回 $this
    ->withMiddleware(...)    // 配置中间件，返回 $this
    ->withExceptions(...)    // 配置异常处理，返回 $this
    ->create();              // 返回 Application 实例
```

调用 `create()` 时会从 `ApplicationBuilder` 取出 `Application` 实例，`bootstrap/app.php` 最终 `return` 的正是这个 `Application` 实例。

## 请求到应用启动的流程

```mermaid theme={null}
flowchart TD
    A["public/index.php<br>入口"] --> B["require bootstrap/app.php<br>获取 Application"]
    B --> C["Application::configure()<br>创建 ApplicationBuilder"]
    C --> D["withKernels()<br>注册 HTTP/Console Kernel"]
    D --> E["withEvents()<br>启用事件发现"]
    E --> F["withCommands()<br>配置 Artisan 命令路径"]
    F --> G["withProviders()<br>加载 bootstrap/providers.php"]
    G --> H["withRouting()<br>配置路由"]
    H --> I["withMiddleware()<br>配置中间件"]
    I --> J["withExceptions()<br>配置异常处理"]
    J --> K["create()<br>返回 Application 实例"]
    K --> L["HTTP Kernel 处理请求<br>中间件 → Router → Controller"]
    L --> M["返回响应"]
```

`public/index.php` 是入口，读取 `bootstrap/app.php` 获得 `Application`。之后 HTTP Kernel 将请求送入中间件管道，路由分发到 Controller。

## `ApplicationBuilder` 主要方法深入

### `withRouting()` — 路由注册的内部处理

```php theme={null}
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
)
```

内部通过 `AppRouteServiceProvider::loadRoutesUsing()` 注册回调，并在应用 booting 时注册 `AppRouteServiceProvider`。

```php theme={null}
// 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);
        }
    };
}
```

**要点：**

* `api` 路由会自动应用 `api` 中间件组与 `/api` 前缀
* 向 `health` 传入字符串会自动注册健康检查端点（默认 `/up`）
* `health` 路径在维护模式下也会被排除（通过 `PreventRequestsDuringMaintenance::except()` 配置）
* 注意 `api` 会先于 `web` 注册。若同一路径同时定义了 Web 和 API 路由，API 路由会优先匹配
* 向 `pages` 传入字符串会启用 [Laravel Folio](https://github.com/laravel/folio) 的路由

### `withMiddleware()` — 中间件自定义

```php theme={null}
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()` 在 `HttpKernel` 被解析**之后**执行回调，这是因为使用了 `afterResolving()` 钩子。传给回调的 `Middleware` 对象提供了丰富的自定义方法。

```php theme={null}
->withMiddleware(function (Middleware $middleware) {
    // 添加全局中间件
    $middleware->append(MyGlobalMiddleware::class);

    // 向 web 组添加中间件
    $middleware->web(append: [EnsureUserIsSubscribed::class]);

    // 替换 api 组中的中间件
    $middleware->api(replace: [
        OldMiddleware::class => NewMiddleware::class,
    ]);

    // 配置 CSRF 排除路径
    $middleware->validateCsrfTokens(except: ['stripe/*', 'webhook/*']);

    // 修改未认证用户的跳转目标
    $middleware->redirectGuestsTo('/custom-login');

    // 配置中间件优先级
    $middleware->priority([
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    ]);
})
```

### `withExceptions()` — 异常处理的配置

```php theme={null}
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()` 首先将框架的 `Handler` 类作为单例注册，然后通过 `afterResolving()` 挂接回调。回调会收到一个 `Exceptions` 包装对象。

```php theme={null}
->withExceptions(function (Exceptions $exceptions) {
    // 不上报特定异常
    $exceptions->dontReport(MissedFlightException::class);

    // 特定异常的自定义上报
    $exceptions->report(function (InvalidOrderException $e) {
        // 通知 Slack 等
    });

    // 自定义特定异常的 HTTP 响应
    $exceptions->render(function (NotFoundHttpException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json(['message' => 'Not Found'], 404);
        }
    });

    // 节流（避免同一异常连续上报）
    $exceptions->throttle(function (Throwable $e) {
        return Limit::perMinute(20);
    });
})
```

### `withProviders()` — Service Provider 注册

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

    return $this;
}
```

由于 `withProviders()` 已在 `Application::configure()` 中被默认调用，`bootstrap/providers.php` 会自动加载。如需追加额外 Provider，需在 `bootstrap/app.php` 中显式调用。

```php theme={null}
Application::configure(basePath: dirname(__DIR__))
    ->withProviders([
        // 除默认的 bootstrap/providers.php 之外再追加 Provider
        App\Providers\CustomServiceProvider::class,
    ])
    ->withRouting(...)
    ->create();
```

<Warning>
  若传入 `withBootstrapProviders: false`，将不再加载 `bootstrap/providers.php`。除非有特殊理由，否则请省略该参数。
</Warning>

### 其他主要方法

| 方法                                   | 说明                                              |
| ------------------------------------ | ----------------------------------------------- |
| `withKernels()`                      | 单例注册 HTTP / Console Kernel。由 `configure()` 自动调用 |
| `withEvents()`                       | 启用事件发现。由 `configure()` 自动调用                     |
| `withCommands(array $commands)`      | 追加注册 Artisan 命令类或目录                             |
| `withSchedule(callable $callback)`   | 在 `bootstrap/app.php` 中定义计划任务                   |
| `withBroadcasting(string $channels)` | 注册广播 channels 文件                                |
| `withBindings(array $bindings)`      | 注册容器绑定                                          |
| `withSingletons(array $singletons)`  | 注册单例绑定                                          |
| `registered(callable $callback)`     | 添加在 Service Provider 注册后执行的回调                   |
| `booting(callable $callback)`        | 添加在 booting 时执行的回调                              |
| `booted(callable $callback)`         | 添加在 booted 后执行的回调                               |
| `create()`                           | 返回 `Application` 实例                             |

## 设计意图：为何这样设计

### 「代码优先」的配置

Laravel 10 及之前的 `app/Http/Kernel.php` 采用以数组列举中间件的写法。这种写法接近配置文件，但难以享受 PHP 的类型系统与 IDE 支持。

Laravel 11 改为 `withMiddleware(function (Middleware $middleware) { ... })` 这样的回调风格。这样能获得类型补全，也可以自然地使用条件分支、循环等逻辑进行动态配置。

### 从「约定优于配置」到「显式配置」

将 `api.php` 改为 opt-in，是为了避免应用不使用 API 路由时仍然始终加载 `api` 中间件组的问题。方针是：默认不存在未使用的功能。

### `afterResolving()` 钩子的活用

`withMiddleware()` 和 `withExceptions()` 使用 `afterResolving()` 是为了避免配置顺序的问题。`ApplicationBuilder` 的方法在应用完全启动之前被调用，而实际处理（对 Kernel 应用配置）延迟到 Kernel 第一次被解析时执行。

## 自定义示例

### API 与 Web 共存

```php theme={null}
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',  // 从默认的 /api 修改
    )
    ->withMiddleware(function (Middleware $middleware): void {
        //
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();
```

### 中间件的自定义

```php theme={null}
->withMiddleware(function (Middleware $middleware) {
    // 为 Web 路由添加认证检查中间件
    $middleware->web(append: [
        \App\Http\Middleware\EnsureEmailIsVerified::class,
    ]);

    // 从 API 路由中移除特定中间件
    $middleware->api(remove: [
        \Illuminate\Session\Middleware\StartSession::class,
    ]);

    // 将 Webhook 端点从 CSRF 排除
    $middleware->validateCsrfTokens(except: [
        'webhook/*',
        'stripe/webhook',
    ]);

    // 为特定中间件设置别名
    $middleware->alias([
        'subscribed' => \App\Http\Middleware\EnsureUserIsSubscribed::class,
    ]);
})
```

### 将计划任务统一到 `bootstrap/app.php`

计划任务可以写在 `routes/console.php` 中，也可以用 `withSchedule()` 统一放到 `bootstrap/app.php`。

```php theme={null}
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();
```

### 异常处理的自定义

```php theme={null}
->withExceptions(function (Exceptions $exceptions) {
    // 对 API 请求始终返回 JSON
    $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);
        }
    });

    // 仅生产环境通知 Slack
    if (app()->isProduction()) {
        $exceptions->report(function (Throwable $e) {
            app(SlackNotifier::class)->notify($e);
        })->stop();
    }
})
```

### 在 `bootstrap/app.php` 中管理容器绑定

对小型应用，简单的绑定也可以写在 `bootstrap/app.php` 而非 `AppServiceProvider` 中。

```php theme={null}
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();
```

## Laravel 启动流程的顺序

Laravel 应用启动时，Service Provider 与 Application 的钩子按以下顺序执行。

1. 所有 ServiceProvider 的 `register()` 执行
2. Application 的 `registered()`
3. Application 的 `booting()`
4. 所有 ServiceProvider 的 `boot()` 执行
5. Application 的 `booted()`

在 `AppServiceProvider` 中添加以下代码即可确认执行顺序。

```php theme={null}
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');
        });
    }
}
```

`ApplicationBuilder` 上的 `registered()`、`booting()`、`booted()` 只是向 Application 注册回调的方法。通常无需使用，但可用于在启动准备完成的 Kernel 上通过 `booted()` 进行特殊变更。

```php theme={null}
// 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();
```

## 下一步

<Card title="服务容器" icon="box" href="/zh/service-container">
  理解 `ApplicationBuilder` 内部所使用的服务容器机制。
</Card>

<Card title="新应用结构 FAQ" icon="circle-question" href="/zh/advanced/app-structure-faq">
  汇总关于新应用结构的常见问题与解答。
</Card>


## Related topics

- [Laravel 11 之后新应用结构 FAQ](/zh/advanced/app-structure-faq.md)
- [旧结构到新结构的迁移指南](/zh/advanced/app-structure-migration.md)
- [目录结构](/zh/directory-structure.md)
- [Laravel 10 升级到 11](/zh/blog/upgrade-10-to-11.md)
- [引擎 API 集成应用开发指南 - VOICEVOX for Laravel](/zh/packages/laravel-voicevox/app-guide.md)
