> ## 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` 類別及多個服務提供者的結構）遷移到 Laravel 11 以後的 Slim Application Skeleton。

<Warning>
  **官方文件並不建議進行此遷移。**

  * Laravel 10 的舊結構在 Laravel 11 以後仍可**原樣運作**。截至包含 Laravel 13 的目前版本，均無終止支援的計畫。
  * 遷移**完全屬於自願**，並非強制或建議。
  * 若未深入理解舊結構與新結構的差異，強烈建議避免進行遷移。此作業適合對框架內部熟悉的開發者。
  * 遷移前**務必備份**，並確認所有測試通過。
</Warning>

## 需要遷移的情境

於下列情形下可考慮進行遷移：

* 為方便新加入的成員對照官方文件，希望將專案結構調整為最新的標準結構
* 希望與 Laravel 11 以後新建立的套件或啟動套件保持整體一致性
* 希望減少源自舊結構的設定檔與類別，使程式碼庫更為單純

## 前置條件

本指南以下列狀態為前提：

* **Laravel 版本升級已完成**（`laravel/framework ^11.0` 以後）
* 既有測試皆能通過
* 已理解 Laravel 11 以後的應用程式結構（請參考[Laravel 11 以後的應用程式結構](/zh-TW/advanced/app-structure)）

## 遷移範例

以 Laravel 10 + Breeze（Blade Stack）建立的專案為例，展示保留 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="整理服務提供者">
    Laravel 10 預設提供 5 個服務提供者。將其統合成 `AppServiceProvider.php`
    單一檔案。

    **要刪除的提供者（先將內容移到 AppServiceProvider 再刪除）：**

    | 檔案                                           | 遷移目的地                                                            |
    | -------------------------------------------- | ---------------------------------------------------------------- |
    | `app/Providers/AuthServiceProvider.php`      | `AppServiceProvider::boot()` 中的 `Gate::policy()` 等               |
    | `app/Providers/BroadcastServiceProvider.php` | 若使用 Broadcasting，則遷移至 `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);
        }
    }
    ```

    刪除不再需要的提供者檔案後，刪除 `config/app.php` 中的 `providers` 陣列。

    ```php theme={null}
    // config/app.php 的 providers 陣列
    // 整段皆可刪除
    'providers' => ServiceProvider::defaultProviders()->merge([
    App\Providers\AppServiceProvider::class,
    // 已刪除的提供者相關條目也一併刪除
    // 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 會優先讀取此檔案作為提供者清單。
    </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 樣式 |

    若既有的控制器有使用 trait 的方法，可以選擇修改每個控制器，或將 trait 保留在 `Controller`
    基底類別中。並不需要一次全部變更也能運作。

    <Warning>
      若由 Breeze 或 Jetstream 產生的驗證控制器有呼叫 `$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()`     |
| 服務提供者清單   | `config/app.php` 的 `providers` 陣列        | `bootstrap/providers.php`                 |
| Policy 註冊 | `app/Providers/AuthServiceProvider.php`  | 自動註冊。或在 `AppServiceProvider::boot()` 手動註冊 |
| 事件監聽器     | `app/Providers/EventServiceProvider.php` | 自動註冊。或在 `AppServiceProvider::boot()` 手動註冊 |

## 參考連結

* [Laravel 11 以後的應用程式結構](/zh-TW/advanced/app-structure)
* [新應用結構 FAQ](/zh-TW/advanced/app-structure-faq)
* [官方升級指南（11.x）](https://github.com/laravel/docs/blob/11.x/upgrade.md)
* [laravel/laravel repository 10.x → 11.x diff](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-TW/advanced/app-structure-faq.md)
- [套件的 CHANGELOG 與發布管理](/zh-TW/advanced/package-changelog.md)
- [Laravel 11 以後的應用程式結構](/zh-TW/advanced/app-structure.md)
- [從 laravel/ui 遷移到 Fortify 的指南](/zh-TW/blog/ui-to-fortify.md)
- [資料庫遷移（Migrations）](/zh-TW/migrations.md)
