> ## 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 10 旧应用结构迁移到 Laravel 11 之后 Slim Application Skeleton 的完整步骤。

## 前言

本指南讲解如何将 Laravel 10 之前的旧应用结构（拥有 `Kernel` 类以及多个 Service Provider 的结构）迁移到 Laravel 11 之后的 Slim Application Skeleton。

<Warning>
  **官方文档并不推荐进行此迁移。**

  * Laravel 10 的旧结构在 Laravel 11 之后仍然**照常运行**。截至包含 Laravel 13 的当前版本，都没有终止支持的计划。
  * 迁移**完全是可选的**，既不是强制也不是推荐。
  * 如果你没有深入理解新旧结构的差异，强烈建议不要进行迁移。这是面向精通框架内部的开发者的工作。
  * 迁移前**务必做好备份**，并确保所有测试都能通过。
</Warning>

## 何时需要迁移

以下情况下可以考虑迁移：

* 希望让项目符合最新的标准结构，方便新成员对照官方文档
* 希望与 Laravel 11 之后新建的包或 Starter Kit 保持结构一致
* 希望减少沿袭自旧结构的配置文件和类，让代码库更简洁

## 前置条件

本指南假设：

* **Laravel 版本升级已完成**（`laravel/framework ^11.0` 及以上）
* 现有测试全部通过
* 已理解 Laravel 11 之后的应用结构（参考[Laravel 11 之后的应用结构](/zh/advanced/app-structure)）

## 迁移示例

下面演示如何在保留 Breeze（Blade 栈）的前提下，仅将 Laravel 10 + Breeze 项目的应用结构迁移到新结构。

<Steps>
  <Step title="替换 bootstrap/app.php">
    旧的 `bootstrap/app.php` 会创建 `$app` 实例并注册 Kernel。我们将它替换为 `Application::configure()` 的链式调用。

    **旧（Laravel 10）：**

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

    **新（Laravel 11 以后）：**

    ```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) {
            //
        })
        ->withExceptions(function (Exceptions $exceptions) {
            //
        })->create();
    ```

    <Info>
      `withMiddleware()` 与 `withExceptions()` 的回调是接收下一步将要删除的 Kernel 文件设置的地方。先保持为空，在后续步骤中再补充。
    </Info>
  </Step>

  <Step title="删除 HTTP Kernel（app/Http/Kernel.php）">
    `app/Http/Kernel.php` 中定义了全局中间件、中间件组以及中间件别名。

    **旧（app/Http/Kernel.php）：**

    ```php theme={null}
    <?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,
            // ...
        ];
    }
    ```

    Laravel 11 中，上述中间件已作为默认值内置于框架中。如果**没有**自定义，可以直接删除 `app/Http/Kernel.php`。

    **如果有自定义**（添加或移除了自定义中间件），请先将其迁移到 `bootstrap/app.php` 的 `withMiddleware()` 后再删除。

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

        // 向 web 组添加中间件
        $middleware->web(append: [
            \App\Http\Middleware\HandleInertiaRequests::class,
        ]);

        // 添加中间件别名
        $middleware->alias([
            'role' => \App\Http\Middleware\CheckRole::class,
        ]);
    })
    ```

    迁移完成后，删除 `app/Http/Kernel.php`。

    <Info>
      在 Laravel 11 中，`TrustProxies`、`EncryptCookies`、`VerifyCsrfToken` 等默认中间件类也可以从 `app/Http/Middleware/` 中删除。这些类已内置在框架侧，如果不需要自定义，文件本身也可以不保留。
    </Info>
  </Step>

  <Step title="删除 Console Kernel（app/Console/Kernel.php）">
    `app/Console/Kernel.php` 负责定义计划任务并自动加载命令。

    **旧（app/Console/Kernel.php）：**

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

    **命令的自动加载**在 Laravel 11 以后由框架自动扫描 `app/Console/Commands/` 目录，因此不再需要写 `$this->load()`。

    **计划任务的定义**可以迁移到 `routes/console.php` 或 `bootstrap/app.php` 的 `withSchedule()`。

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

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

    迁移完成后，删除 `app/Console/Kernel.php`。

    <Info>
      请不要删除 `app/Console/` 目录内的自定义 Artisan 命令文件。命令文件保持不动，仅删除 Kernel 类文件即可。
    </Info>
  </Step>

  <Step title="删除异常处理器（app/Exceptions/Handler.php）">
    `app/Exceptions/Handler.php` 负责异常上报与渲染的配置。

    **旧（app/Exceptions/Handler.php）：**

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

    如果有自定义内容，请先迁移到 `bootstrap/app.php` 的 `withExceptions()` 再删除。

    ```php theme={null}
    use Throwable;
    use Illuminate\Http\Request;

    ->withExceptions(function (Exceptions $exceptions) {
        // 异常上报的自定义
        $exceptions->report(function (Throwable $e) {
            // ...
        });

        // 异常渲染的自定义
        $exceptions->render(function (\App\Exceptions\InvalidOrderException $e, Request $request) {
            return response()->view('errors.invalid-order', status: 500);
        });
    })
    ```

    向 `$dontFlash` 添加过自定义字段的话，也可以按相同方式迁移。

    ```php theme={null}
    ->withExceptions(function (Exceptions $exceptions) {
        $exceptions->dontFlash([
            'my_sensitive_field',
        ]);
    })
    ```

    迁移完成后，删除 `app/Exceptions/Handler.php`。
  </Step>

  <Step title="删除 RouteServiceProvider 并迁移路由注册">
    `app/Providers/RouteServiceProvider.php` 负责路由文件的加载与限流的配置。

    **旧（app/Providers/RouteServiceProvider.php）：**

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

    **路由文件的注册**迁移到 `bootstrap/app.php` 的 `withRouting()`。

    ```php theme={null}
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php', // 使用 API 路由时
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ```

    **限流**迁移到 `AppServiceProvider::boot()`。

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

    如果有地方使用了 `HOME` 常量，请直接替换成 URL 字符串，或将常量迁移到 `AppServiceProvider`。

    迁移完成后，删除 `app/Providers/RouteServiceProvider.php`。
  </Step>

  <Step title="整理 Service Provider">
    Laravel 10 默认提供了 5 个 Service Provider。请将它们整合到 `AppServiceProvider.php` 一个文件中。

    **要删除的 Provider（将内容迁移到 AppServiceProvider 后删除）：**

    | 文件                                           | 迁移目的地                                               |
    | -------------------------------------------- | --------------------------------------------------- |
    | `app/Providers/AuthServiceProvider.php`      | `AppServiceProvider::boot()` 中的 `Gate::policy()` 等  |
    | `app/Providers/BroadcastServiceProvider.php` | 若使用广播，则 `bootstrap/app.php` 的 `withBroadcasting()`  |
    | `app/Providers/EventServiceProvider.php`     | `AppServiceProvider::boot()` 中的 `Event::listen()` 等 |
    | `app/Providers/RouteServiceProvider.php`     | 已在上一步处理                                             |

    **迁移旧 app/Providers/AuthServiceProvider.php 内容的示例：**

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

    ```php theme={null}
    // 新: app/Providers/AppServiceProvider.php
    // 如果遵循 Laravel 的标准命名约定，会自动检测，无需此步骤
    use Illuminate\Support\Facades\Gate;

    class AppServiceProvider extends ServiceProvider
    {
        public function boot(): void
        {
            Gate::policy(Post::class, PostPolicy::class);
        }
    }
    ```

    删除不再需要的 Provider 文件后，删除 `config/app.php` 中的 `providers` 数组。

    ```php theme={null}
    // config/app.php 的 providers 数组
    // 全部删除即可
    'providers' => ServiceProvider::defaultProviders()->merge([
    App\Providers\AppServiceProvider::class,
    // 已删除的 Provider 也从这里移除
    // App\Providers\AuthServiceProvider::class, // 删除
    // App\Providers\EventServiceProvider::class, // 删除
    // App\Providers\RouteServiceProvider::class, // 删除
    ])->toArray(),
    ```

    接着创建 `bootstrap/providers.php` 以适配新结构。

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

    // bootstrap/providers.php
    return [
        App\Providers\AppServiceProvider::class,
    ];
    ```

    <Info>
      当 `bootstrap/providers.php` 存在时，Laravel 会将其作为 Provider 列表优先加载。
    </Info>
  </Step>

  <Step title="更新 Controller 基类">
    Laravel 10 的 `Controller` 基类使用了 `AuthorizesRequests` 和 `ValidatesRequests` trait。Laravel 11 的新基类是不包含这些 trait 的简单抽象类。

    **旧（Laravel 10）：**

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

    **新（Laravel 11 以后）：**

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

    namespace App\Http\Controllers;

    abstract class Controller
    {
        //
    }
    ```

    trait 提供的功能按以下方式替代：

    | 旧方式（通过 trait）                           | 新方式                                                                          |
    | --------------------------------------- | ---------------------------------------------------------------------------- |
    | `$this->validate($request, $rules)`     | `$request->validate($rules)`                                                 |
    | `$this->authorize('update', $post)`     | `Gate::authorize('update', $post)`                                           |
    | `$this->authorizeResource(Post::class)` | Laravel 11 中没有简单的替代方式，可保留 `App\Http\Controllers\Controller` 为 Laravel 10 的形式 |

    如果现有 Controller 使用了 trait 的方法，可以选择修改每个 Controller，或者在 `Controller` 基类中保留这些 trait。不必一次性全部改完也能运行。

    <Warning>
      如果 Breeze 或 Jetstream 生成的认证 Controller 使用了 `$this->validate()` 或 `$this->authorize()`，请在修改基类之前务必进行动作验证。
    </Warning>
  </Step>

  <Step title="删除不需要的 config 文件">
    `config/cors.php`、`config/hashing.php`、`config/view.php` 等未从默认值修改的文件可以删除。有修改的文件请保留。
  </Step>

  <Step title="更新 public/index.php">
    由于随新结构做了改动，需要整体重写。

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

  <Step title="更新 artisan">
    artisan 文件也同样需要全部重写。

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

  <Step title="更新 tests/TestCase.php">
    由于不再需要 `CreatesApplication` trait，需要做调整。
    `tests/CreatesApplication.php` 可以删除。

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

    namespace Tests;

    use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

    abstract class TestCase extends BaseTestCase
    {
        //
    }
    ```
  </Step>

  <Step title="更新 .env、.env.example、phpunit.xml">
    `CACHE_DRIVER` 变为 `CACHE_STORE` 等，条目也有增删，如有需要请调整。
    这类改动涉及 config 文件和生产环境，请谨慎处理。
    不必强行跟进。
  </Step>

  <Step title="验证">
    迁移完成后，按以下顺序进行验证。

    ```shell theme={null}
    # 清除配置缓存
    php artisan config:clear
    php artisan route:clear
    php artisan cache:clear

    # 确认路由是否正确注册
    php artisan route:list

    # 运行测试
    php artisan test
    ```

    遇到问题时，请从备份中恢复被删除的文件并查看错误信息。
  </Step>
</Steps>

## 迁移后的文件结构

迁移后的项目结构大致如下（展示相对旧结构的变化）。

```
app/
├── Console/
│   └── Commands/          ← 自定义命令保持不变
│   （Kernel.php 已删除）
├── Exceptions/
│   （Handler.php 已删除）
├── Http/
│   ├── Controllers/
│   │   └── Controller.php ← 改为简洁的抽象类
│   └── Middleware/        ← 自定义中间件保持不变
│   （Kernel.php 已删除）
├── Models/
└── Providers/
    └── AppServiceProvider.php ← 统一到 1 个文件
    （AuthServiceProvider.php 已删除）
    （BroadcastServiceProvider.php 已删除）
    （EventServiceProvider.php 已删除）
    （RouteServiceProvider.php 已删除）
bootstrap/
├── app.php                ← 替换为 Application::configure() 形式
└── providers.php          ← 新建
routes/
├── web.php
├── api.php                ← 仅在需要时保留
└── console.php            ← 计划任务定义也写在这里
```

## 小结

| 迁移内容                | 旧位置                                      | 新位置                                        |
| ------------------- | ---------------------------------------- | ------------------------------------------ |
| HTTP 中间件            | `app/Http/Kernel.php`                    | `bootstrap/app.php` 的 `withMiddleware()`   |
| 异常处理                | `app/Exceptions/Handler.php`             | `bootstrap/app.php` 的 `withExceptions()`   |
| 计划任务定义              | `app/Console/Kernel.php`                 | `routes/console.php`                       |
| 路由注册                | `app/Providers/RouteServiceProvider.php` | `bootstrap/app.php` 的 `withRouting()`      |
| Service Provider 列表 | `config/app.php` 的 `providers` 数组        | `bootstrap/providers.php`                  |
| Policy 注册           | `app/Providers/AuthServiceProvider.php`  | 自动注册。或在 `AppServiceProvider::boot()` 中手动注册 |
| 事件监听器               | `app/Providers/EventServiceProvider.php` | 自动注册。或在 `AppServiceProvider::boot()` 中手动注册 |

## 参考链接

* [Laravel 11 之后的应用结构](/zh/advanced/app-structure)
* [新应用结构 FAQ](/zh/advanced/app-structure-faq)
* [官方升级指南（11.x）](https://github.com/laravel/docs/blob/11.x/upgrade.md)
* [laravel/laravel 仓库 10.x → 11.x 差异](https://github.com/laravel/laravel/compare/10.x...11.x)
* [Laravel 10 的 Controller.php](https://github.com/laravel/laravel/blob/10.x/app/Http/Controllers/Controller.php)
* [Laravel 11 的 Controller.php](https://github.com/laravel/laravel/blob/11.x/app/Http/Controllers/Controller.php)


## Related topics

- [Laravel 11 之后新应用结构 FAQ](/zh/advanced/app-structure-faq.md)
- [Laravel 11 之后的应用结构](/zh/advanced/app-structure.md)
- [数据库迁移](/zh/migrations.md)
- [目录结构](/zh/directory-structure.md)
- [包的 CHANGELOG 与发布管理](/zh/advanced/package-changelog.md)
