> ## 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 12 升級到 13 指南

> 說明從 Laravel 12 升級至 Laravel 13 的步驟、破壞性變更、棄用功能與新功能亮點。

## 前言

Laravel 13 於 2026 年 3 月發佈。本指南說明從 Laravel 12.x 升級到 13.x 的步驟。

<Info>
  升級預估所需時間約為 **10 分鐘**。不過破壞性變更對應用程式的影響會因規模與使用的功能而異。
</Info>

### 使用 AI 進行升級

也可以使用 [Laravel Boost](https://github.com/laravel/boost) 自動化升級。Boost 是官方的 MCP server，會向 AI 助理提供逐步的升級提示。安裝到 Laravel 12 應用後，可在 Claude Code、Cursor、OpenCode、Gemini、VS Code 中使用 `/upgrade-laravel-v13` 斜線指令開始升級到 Laravel 13。此指令需要 `laravel/boost ^2.0`。

即使是不支援斜線指令的 AI 工具，也可直接參照 prompt 檔案來執行同樣的升級步驟。請將以下 prompt 直接貼給 AI。

```text prompt theme={null}
請讀取 Laravel Boost 提供的 prompt 進行從 Laravel 12 升級到 13 的作業。
https://raw.githubusercontent.com/laravel/boost/refs/heads/main/src/Mcp/Prompts/UpgradeLaravelv13/upgrade-laravel-v13.blade.php

- 反映 `laravel/laravel` skeleton 的變更點。請確認 13.x 分支，而非 master。
- `database/migrations/*_create_cache_table.php` 不是直接修改，而是建立新的 migration。
- `config/session.php` 的 `serialization` 設為 `php`。`'serialization' => 'php'`
- Laravel 13 中最容易讓應用出錯的變更是快取行為變更，因此搜尋專案內快取的使用位置，若允許沒問題就在 config/cache.php 的 `serializable_classes` 允許以更新。

'serializable_classes' => [
    App\Data\CachedDashboardStats::class,
    App\Support\CachedPricingSnapshot::class,
    Illuminate\Support\Collection::class,
    Illuminate\Database\Eloquent\Collection::class,
],
```

***

## 依影響程度分類的變更

### 影響程度：高

* 更新依賴套件
* 更新 Laravel installer
* 防止請求偽造（CSRF）

### 影響程度：中

* 快取 `serializable_classes` 設定
* Session `serialization` 設定

### 影響程度：低

* 快取前綴與 session cookie 名稱
* Collection model 的序列化
* `Container::call` 與 Nullable 類別預設值
* Domain 路由註冊的優先順序
* `JobAttempted` 事件的例外 payload
* Manager `extend` callback 的 binding
* MySQL `DELETE` query (JOIN / ORDER BY / LIMIT)
* 分頁 Bootstrap view 名稱
* 多型 pivot 資料表名稱的產生
* `QueueBusy` 事件屬性重新命名
* `Str` factory 的測試間重置

***

## 升級步驟

### 更新依賴套件

**影響程度:高**

請更新 `composer.json` 中以下依賴。

```json theme={null}
{
  "require": {
    "laravel/framework": "^13.0",
    "laravel/tinker": "^3.0"
  },
  "require-dev": {
    "phpunit/phpunit": "^12.0",
    "pestphp/pest": "^4.0"
  }
}
```

若有使用 Laravel Boost 也一併更新。

```json theme={null}
{
  "require": {
    "laravel/boost": "^2.0"
  }
}
```

更新後執行以下指令安裝依賴。

```shell theme={null}
composer update
```

***

### 更新 Laravel installer

**影響程度:高**

若使用 Laravel installer CLI 建立新的 Laravel 應用程式，請更新為支援 Laravel 13.x 的版本。

若以 `composer global require` 安裝時:

```shell theme={null}
composer global update laravel/installer
```

若使用 [Laravel Herd](https://herd.laravel.com) 的 bundle 版，請更新 Herd 自身至最新版本。

***

## 破壞性變更 (Breaking Changes)

### 安全性

#### 防止請求偽造

**影響程度:高**

Laravel 的 CSRF middleware 已從 `VerifyCsrfToken` 改名為 `PreventRequestForgery`。另外還新增了使用 `Sec-Fetch-Site` header 驗證請求來源的功能。

`VerifyCsrfToken` 與 `ValidateCsrfToken` 作為棄用的 alias 仍保留，但所有直接引用的位置都需更新為 `PreventRequestForgery`。尤其在測試或路由定義中若有排除 middleware，需要特別注意。

```php theme={null}
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;

// Laravel <= 12.x
->withoutMiddleware([VerifyCsrfToken::class]);

// Laravel >= 13.x
->withoutMiddleware([PreventRequestForgery::class]);
```

middleware 設定 API 也可使用 `preventRequestForgery(...)`。

***

### 快取

#### 快取前綴與 session cookie 名稱

**影響程度:低**

Laravel 的預設快取與 Redis key 前綴改為使用連字號分隔的後綴。另外，預設 session cookie 名稱也改為使用 `Str::snake(...)`。

大部分應用程式會在設定檔明確設定值，因此不會受此變更影響。只有依賴框架 fallback 設定的應用程式會受影響。

```php theme={null}
// Laravel <= 12.x
Str::slug((string) env('APP_NAME', 'laravel'), '_').'_cache_';
Str::slug((string) env('APP_NAME', 'laravel'), '_').'_database_';
Str::slug((string) env('APP_NAME', 'laravel'), '_').'_session';

// Laravel >= 13.x
Str::slug((string) env('APP_NAME', 'laravel')).'-cache-';
Str::slug((string) env('APP_NAME', 'laravel')).'-database-';
Str::snake((string) env('APP_NAME', 'laravel')).'_session';
```

若要維持之前的行為，請在 `.env` 檔明確設定。

```ini theme={null}
CACHE_PREFIX=myapp_cache_
REDIS_PREFIX=myapp_database_
SESSION_COOKIE=myapp_session
```

#### 快取 `serializable_classes` 設定

**影響程度:中**

預設 `cache` 設定新增了 `serializable_classes` 選項，預設為 `false`。這可在 `APP_KEY` 洩漏時防止 PHP 反序列化 gadget chain 攻擊。

若應用程式刻意在快取中儲存 PHP 物件，需要明確列出允許反序列化的類別。

```php theme={null}
// config/cache.php
'serializable_classes' => [
    App\Data\CachedDashboardStats::class,
    App\Support\CachedPricingSnapshot::class,
],
```

若原本是反序列化任意快取物件，需要改為明確的類別允許清單，或改為非物件的快取 payload（如陣列）。

#### Session `serialization` 設定

**影響程度:中**

Laravel 13 的 skeleton（`laravel/laravel`）的 `config/session.php` 中新增了 `'serialization' => 'json'`。不過，框架內部的預設仍是 `php`。

<Warning>
  若用 AI 工具直接套用 skeleton 的變更，可能會將 `'serialization' => 'json'` 加入到 `config/session.php`。此變更會切換 session 序列化方式，因此若應用在 session 中儲存 PHP 物件會發生錯誤。
</Warning>

官方的升級指南沒有記載此設定的變更。這可推測是 **不需要變更** 的意圖。Laravel 中從前一版本升級時偶爾會有不影響升級的變更未在升級指南中記載。數年後升級時若找不到資訊會很困擾，因此非官方的紀錄很重要。

要維持與 Laravel 12 以前相同的行為，請明確設定 `'serialization' => 'php'`。

```php theme={null}
// config/session.php
'serialization' => 'php',
```

若要啟用 JSON 序列化，請事先確認 session 中沒有儲存 PHP 物件。

***

### Container

#### `Container::call` 與 Nullable 類別預設值

**影響程度:低**

`Container::call` 在沒有 binding 時，會尊重 nullable 類別參數的預設值（與 Laravel 12 中對 constructor injection 引入的行為一致）。

```php theme={null}
$container->call(function (?Carbon $date = null) {
    return $date;
});

// Laravel <= 12.x: 回傳 Carbon instance
// Laravel >= 13.x: 回傳 null
```

***

### 資料庫

#### MySQL `DELETE` query

**影響程度:低**

Laravel 現在會以 MySQL 語法 compile 完整的 `DELETE ... JOIN` query（含 `ORDER BY` 與 `LIMIT`）。

之前版本中，帶 JOIN 的 DELETE 有時會忽略 `ORDER BY` / `LIMIT` 子句。Laravel 13 中這些子句會包含在產生的 SQL 中。結果是在不支援此語法的部分資料庫引擎中可能會發生 `QueryException`。

***

### Eloquent

#### 多型 pivot 資料表名稱的產生

**影響程度:低**

推測使用自訂 pivot model 類別的多型 pivot model 的資料表名稱時，Laravel 現在會產生複數形的名稱。

若原本依賴之前的單數形推測名稱，請在 pivot model 中明確定義資料表名稱。

```php theme={null}
class RoleUser extends MorphPivot
{
    protected $table = 'role_user'; // 明確指定
}
```

#### Collection model 的序列化

**影響程度:低**

Eloquent model collection 序列化與復原（如佇列 job）時，會為 model 復原 eager loaded 的 relation。

若程式碼依賴反序列化後 relation 不存在，需要修正。

***

### 佇列

#### `JobAttempted` 事件的例外 payload

**影響程度:低**

`Illuminate\Queue\Events\JobAttempted` 事件會將例外物件（或 `null`）暴露為 `$exception`，取代之前的 boolean `$exceptionOccurred` 屬性。

```php theme={null}
// Laravel <= 12.x
if ($event->exceptionOccurred) {
    // 發生了例外
}

// Laravel >= 13.x
if ($event->exception !== null) {
    // 發生了例外
    $exception = $event->exception;
}
```

#### `QueueBusy` 事件屬性重新命名

**影響程度:低**

`Illuminate\Queue\Events\QueueBusy` 事件的屬性 `$connection` 為了與其他佇列事件保持一致，已改名為 `$connectionName`。

```php theme={null}
// Laravel <= 12.x
$event->connection;

// Laravel >= 13.x
$event->connectionName;
```

***

### 路由

#### Domain 路由註冊的優先順序

**影響程度:低**

在路由匹配時，具明確 domain 的路由現在會優先於非 domain 路由。

如此一來，即使非 domain 路由先註冊，catch-all 子網域路由也能一致地運作。

***

### 支援

#### Manager `extend` callback 的 binding

**影響程度:低**

以 Manager 的 `extend` 方法註冊的自訂 driver closure，現在會 bind 到 manager instance。

若之前在這些 callback 中將其他物件（如 service provider instance）作為 `$this` 參照，需要用 `use (...)` 將值移至 closure capture。

```php theme={null}
// Laravel <= 12.x
Manager::extend('custom', function ($app) {
    return $this->createCustomDriver($app); // $this 為 service provider
});

// Laravel >= 13.x
$provider = $this;
Manager::extend('custom', function ($app) use ($provider) {
    return $provider->createCustomDriver($app);
});
```

#### `Str` factory 的測試間重置

**影響程度:低**

Laravel 會在測試 teardown 時重置自訂 `Str` factory。

若依賴自訂 UUID / ULID / 隨機字串 factory 在測試方法間持續存在，請改為在各相關測試或 setup hook 中設定。

***

### View

#### 分頁 Bootstrap view 名稱

**影響程度:低**

Bootstrap 3 預設的內部分頁 view 名稱已明確化。

```php theme={null}
// Laravel <= 12.x
pagination::default
pagination::simple-default

// Laravel >= 13.x
pagination::bootstrap-3
pagination::simple-bootstrap-3
```

若直接引用舊分頁 view 名稱，請進行更新。

***

## 已棄用的功能

| 功能                                 | 替代方式                         |
| ---------------------------------- | ---------------------------- |
| `VerifyCsrfToken` middleware       | `PreventRequestForgery`      |
| `ValidateCsrfToken` middleware     | `PreventRequestForgery`      |
| `JobAttempted::$exceptionOccurred` | `JobAttempted::$exception`   |
| `QueueBusy::$connection`           | `QueueBusy::$connectionName` |

***

## Contract 新增

**影響程度:非常低**

僅有自訂實作時受影響。

### `Dispatcher` contract

`Illuminate\Contracts\Bus\Dispatcher` contract 新增了 `dispatchAfterResponse($command, $handler = null)` 方法。

### `ResponseFactory` contract

`Illuminate\Contracts\Routing\ResponseFactory` contract 新增了 `eventStream` 簽章。

### `MustVerifyEmail` contract

`Illuminate\Contracts\Auth\MustVerifyEmail` contract 新增了 `markEmailAsUnverified()`。

### `Queue` contract

`Illuminate\Contracts\Queue\Queue` contract 新增了以下佇列大小檢查方法（先前僅以 docblock 宣告）。

* `pendingSize`
* `delayedSize`
* `reservedSize`
* `creationTimeOfOldestPendingJob`

### `Store` / `Repository` contract

Cache contract 新增了用於延長 TTL 的 `touch` 方法。

```php theme={null}
// Illuminate\Contracts\Cache\Store
public function touch($key, $seconds);
```

***

## 新功能亮點

### AI 輔助升級（Laravel Boost）

Laravel Boost 是官方 MCP server。可與 AI editor 連動，透過 `/upgrade-laravel-v13` 指令半自動化升級。

### 透過 `Sec-Fetch-Site` header 驗證請求來源

`PreventRequestForgery` middleware 會使用 `Sec-Fetch-Site` header 進行額外的來源驗證。這強化了 CSRF 保護。

### 安全的快取反序列化

透過 `serializable_classes` 設定，只有允許的類別會被反序列化。強化了對 PHP 反序列化攻擊的安全性。

### SSE（Server-Sent Events）的 `eventStream`

`ResponseFactory` contract 新增了 `eventStream`，改善了對 Server-Sent Events 的支援。

### 佇列可見性提升

透過 `Queue` contract 的 `pendingSize`、`delayedSize`、`reservedSize` 等方法，可以更細緻地監控佇列狀態。

***

## 常見遷移問題與解法

### 問題：與 CSRF 相關的測試失敗

**症狀：** 引用 `VerifyCsrfToken` 的測試因找不到類別而失敗。

**解法：** 將所有引用更新為 `PreventRequestForgery`。

```php theme={null}
// Before
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
->withoutMiddleware([VerifyCsrfToken::class]);

// After
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
->withoutMiddleware([PreventRequestForgery::class]);
```

### 問題：無法從快取恢復物件

**症狀：** 從快取取得的資料變成 `null`，或發生 `UnserializationFailedException`。

**解法：** 在 `config/cache.php` 的 `serializable_classes` 中新增要使用的類別，或將快取的值轉為陣列。

```php theme={null}
'serializable_classes' => [
    App\Models\User::class,
    App\Data\SomeData::class,
],
```

### 問題：`JobAttempted` listener 無法運作

**症狀：** `$event->exceptionOccurred` 為 `null` 或發生未定義錯誤。

**解法：** 改為 `$event->exception !== null`。

```php theme={null}
// Before
if ($event->exceptionOccurred) { ... }

// After
if ($event->exception !== null) { ... }
```

### 問題：Session 無效化

**症狀：** 升級後使用者被登出。

**解法：** 預設 session cookie 名稱已變更。請在 `.env` 明確設定 `SESSION_COOKIE` 以維持之前的值。

```ini theme={null}
SESSION_COOKIE=laravel_session
```

### 問題：找不到快取 key

**症狀：** 升級後 cache miss 增加。

**解法：** 快取前綴已變更。請在 `.env` 設定 `CACHE_PREFIX`，或清除快取。

```shell theme={null}
php artisan cache:clear
```

***

## 參考資料

* [官方升級指南（英文）](https://laravel.com/docs/13.x/upgrade)
* [laravel/laravel repository 差異（12.x → 13.x）](https://github.com/laravel/laravel/compare/12.x...13.x)
* [Laravel Shift](https://laravelshift.com) — 自動化升級的社群服務
* [Laravel Boost](https://github.com/laravel/boost) — 用於 AI 輔助升級的 MCP server


## Related topics

- [從 Laravel 11 升級到 12 指南](/zh-TW/blog/upgrade-11-to-12.md)
- [從 Laravel 8 升級到 9](/zh-TW/blog/upgrade-8-to-9.md)
- [從 Laravel 9 升級到 10](/zh-TW/blog/upgrade-9-to-10.md)
- [從 Laravel 10 升級到 11](/zh-TW/blog/upgrade-10-to-11.md)
