
# 升级指南

- 从 12.x 升级到 13.0
    - 利用人工智能进行升级

<a name="high-impact-changes"></a>
## 高影响变更

<div class="content-list" markdown="1">

- 更新依赖项
- 更新 Laravel 安装程序
- 请求伪造防护

</div>

<a name="medium-impact-changes"></a>
## 中等影响变更

<div class="content-list" markdown="1">

- 缓存 serializable_classes 配置项
- 在 MySQL 或 MariaDB 中使用数据库的 upsert 方法

</div>

<a name="low-impact-changes"></a>
## 低影响变更

<div class="content-list" markdown="1">

- 缓存前缀与会话 Cookie 名称
- 集合模型序列化恢复预加载关联
- Container::call 与可空类默认值
- 域名路由注册优先级
- JobAttempted 事件异常负载
- 管理器的 extend 回调绑定
- 支持使用 JOIN、ORDER BY 和 LIMIT 执行 DELETE 操作
- 分页 Bootstrap 视图名称
- 多态中间表名称生成规则
- QueueBusy 事件属性重命名
- 测试中自动重置 Str Factories

</div>

<a name="upgrade-13.0"></a>
## 从 12.x 升级到 13.0

#### 预计升级时间：10 分钟

> [!NOTE]
> 我们尝试记录每一个可能的重大变更。由于这些变更中的一些位于框架的偏僻部分，只有一部分变更可能实际影响您的应用程序。想节省时间吗？您可以使用 [Laravel Shift](https://laravelshift.com) 来帮助自动化您的应用程序升级。Shift 是一个由社区维护的服务，可以自动化完成 Laravel 的升级过程。

<a name="upgrading-using-ai"></a>
### 使用 AI 进行升级

您可以使用 [Laravel Boost](https://github.com/laravel/boost) 来完成自动化升级。 Boost 是一个官方 MCP 服务器，旨在为您的 AI 助手提供引导式升级提示 —— 将它安装到任意 Laravel 12 应用后，在 Claude Code、Cursor、OpenCode、Gemini 或 VS Code 中使用 /upgrade-laravel-v13 命令，即可开始升级至 Laravel 13。该命令要求 Laravel Boost 版本为 ^2.0。



<a name="updating-dependencies"></a>

### 更新依赖项

**影响可能性：高**

您需要在应用的 `composer.json` 文件中更新以下依赖项：

<div class="content-list" markdown="1">

-   `laravel/framework` 更新至 `^13.0`
-   `laravel/boost` 更新至 `^2.0`
-   `laravel/tinker` 更新至 `^3.0`
-   `phpunit/phpunit` 更新至 `^12.0`
-   `pestphp/pest` 更新至 `^4.0`

</div>

<a name="updating-the-laravel-installer"></a>
### 更新 Laravel 安装器

如果您正在使用 Laravel 安装器 CLI工具来创建新的 Laravel 应用，请更新您的安装器以兼容 Laravel 13.x。

如果您通过 `composer global require` 安装了 Laravel 安装器，可以使用 `composer global update` 进行更新：

```shell
composer global update laravel/installer
```

或者，如果您使用的是 [Laravel Herd](https://herd.laravel.com) 附带的 Laravel 安装器，请将 Herd 更新至最新的版本。

<a name="cache"></a>
### 缓存

<a name="cache-prefixes-and-session-cookie-names"></a>
#### 缓存前缀与会话 Cookie 名称

**影响可能性：低**

Laravel 默认的缓存和 Redis 键前缀现在使用连字符后缀。

在大多数应用中，此更改不会生效，因为应用级配置文件已经定义了这些值。它主要影响那些在应用配置值缺失时依赖框架级回退配置的应用。

```php
// 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::slug((string) env('APP_NAME', 'laravel')).'-session';
```



要保留先前的行为，请在环境中显式配置 `CACHE_PREFIX`、`REDIS_PREFIX` 和 `SESSION_COOKIE`。

<a name="store-and-repository-contracts-touch"></a>

#### `Store` 和 `Repository` 契约：`touch`

**影响可能性：极低**

缓存契约现在包含一个用于延长项 TTL（生存时间）的 `touch` 方法。如果您维护了自定义的缓存存储实现，请添加此方法：

```php
// Illuminate\Contracts\Cache\Store
public function touch($key, $seconds);
```

<a name="cache-serializable_classes-configuration"></a>
#### 缓存 `serializable_classes` 配置

**影响可能性：中等**

默认应用的 `cache` 配置现在包含一个设置为 `false` 的 `serializable_classes` 选项。此配置增强了缓存反序列化行为的安全性，有助于在您的应用的 `APP_KEY` 泄露时防止 PHP 反序列化利用链攻击。如果您的应用有意在缓存中存储 PHP 对象，则应显式列出允许反序列化的类：

```php
'serializable_classes' => [
    App\Data\CachedDashboardStats::class,
    App\Support\CachedPricingSnapshot::class,
],
```

如果您的应用之前依赖反序列化任意缓存对象，则需要将该用法迁移到显式的类允许列表或非对象缓存负载（例如数组）中。

<a name="container"></a>
### 容器

<a name="containercall-and-nullable-class-defaults"></a>

#### `Container::call` 和可为空类默认值

**影响可能性：低**

`Container::call` 现在在没有绑定时会尊重可为空的类参数默认值，这与 Laravel 12 中引入的构造函数注入行为一致：

```php
$container->call(function (?Carbon $date = null) {
    return $date;
});

// Laravel <= 12.x: Carbon 实例
// Laravel >= 13.x: null
```

如果你的方法调用注入逻辑依赖于先前行为，则可能需要更新它。

<a name="contracts"></a>
### 契约

<a name="dispatcher-contract-dispatchafterresponse"></a>

#### `Dispatcher` 契约：`dispatchAfterResponse`

**影响可能性：极低**



`Illuminate\Contracts\Bus\Dispatcher` 契约现在包含了 `dispatchAfterResponse($command, $handler = null)` 方法。

如果您维护了自定义的调度器，请将此方法添加到您的类中。


<a name="responsefactory-contract-eventstream"></a>
#### `ResponseFactory` 契约：`eventStream`

**影响可能性：极低**

`Illuminate\Contracts\Routing\ResponseFactory` 契约现在包含了一个 `eventStream` 方法签名。

如果您维护了该契约的自定义实现，应添加此方法。

<a name="mustverifyemail-contract-markemailasunverified"></a>

#### `MustVerifyEmail` 契约：`markEmailAsUnverified`

**影响可能性：极低**

`Illuminate\Contracts\Auth\MustVerifyEmail` 契约现在包含 `markEmailAsUnverified()` 方法。

如果您提供了该契约的自定义实现，请添加此方法以保持兼容性。

<a name="database"></a>
### 数据库

<a name="database-upsert-mariadb-mysql"></a>

#### 使用 MySQL 或 MariaDB 进行数据库 `upsert` 操作

**影响可能性：中等**

Laravel 现在会验证调用方是否提供了非空的 `uniqueBy` 值，并在值为空时抛出 `InvalidArgumentException`，而不是生成无效的 SQL 语句。

尽管 MariaDB 和 MySQL 数据库驱动会忽略 `uniqueBy` 值并始终使用表的主键和唯一索引来检测已存在的记录，但验证规则依然适用。如果 `uniqueBy` 为空，将抛出 `InvalidArgumentException`。

<a name="mysql-delete-queries-with-join-order-by-and-limit"></a>

#### 带有 `JOIN`、`ORDER BY` 和 `LIMIT` 的 MySQL `DELETE` 查询

**影响可能性：低**

Laravel 现在会在 MySQL 语法中完整编译包含 `ORDER BY` 和 `LIMIT` 的 `DELETE ... JOIN` 查询。

在之前的版本中，对于带连接的删除操作，`ORDER BY` / `LIMIT` 子句可能会被静默忽略。在 Laravel 13 中，这些子句会被包含在生成的 SQL 中。因此，不支持该语法的数据库引擎（如标准 MySQL / MariaDB 变体）现在可能会抛出 `QueryException`，而不是执行无限制的删除。


<a name="eloquent"></a>
### Eloquent

<a name="model-booting-and-nested-instantiation"></a>
#### 模型启动与嵌套实例化

**影响可能性：极低**

现在，在模型仍在启动过程中创建新的模型实例将被禁止，并会抛出 `LogicException`。

这会影响在模型 `boot` 方法或 trait `boot*` 方法中实例化模型的代码：

```php
protected static function boot()
{
    parent::boot();

    // 启动期间不再允许这样做...
    (new static())->getTable();
}
```

请将这类逻辑移出启动周期，以避免嵌套启动。

<a name="polymorphic-pivot-table-name-generation"></a>
#### 多态中间表名称生成

**影响可能性：低**

当使用自定义的中间模型类为多态中间模型推断表名时，Laravel 现在会生成复数形式的表名。

如果您的应用依赖于之前多态中间表推断出的单数表名，并且使用了自定义的中间类，您应在中间模型中显式定义表名。

<a name="collection-model-serialization-restores-eager-loaded-relations"></a>
#### 集合模型序列化恢复预加载关联

**影响可能性：低**

当 Eloquent 模型集合被序列化并恢复时（例如在队列任务中），集合中模型的预加载关联现在也会被恢复。

如果您的代码依赖于反序列化后关联关系不存在的行为，您可能需要调整相关逻辑。

<a name="http-client"></a>
### HTTP 客户端

<a name="http-client-response-throw-and-throwif-signatures"></a>

#### HTTP 客户端 `Response::throw` 与 `throwIf` 方法签名

**影响可能性：极低**

HTTP 客户端响应的方法现在在方法签名中声明了其回调参数：

```php
public function throw($callback = null);
public function throwIf($condition, $callback = null);
```

如果您在自定义响应类中重写了这些方法，请确保您的方法签名与其保持兼容。

<a name="notifications"></a>
### 通知

<a name="default-password-reset-subject"></a>
#### 默认密码重置邮件主题

**影响可能性：极低**

Laravel 的默认密码重置邮件主题已更改：

```text
// Laravel <= 12.x
重置密码通知

// Laravel >= 13.x
重置您的密码
```
如果您的测试、断言或翻译覆盖依赖于之前的默认字符串，请相应更新它们。

<a name="queued-notifications-and-missing-models"></a>
#### 队列通知与缺失模型

**影响可能性：极低**

队列通知现在会尊重通知类上定义的 `#[DeleteWhenMissingModels]` 属性和 `$deleteWhenMissingModels` 属性。

在先前版本中，在你期望它们被删除的情况下，缺失模型仍可能导致队列通知任务失败。

<a name="queue"></a>
### 队列

<a name="jobattempted-event-exception-payload"></a>
#### `JobAttempted` 事件异常负载

**影响可能性：低**

`Illuminate\Queue\Events\JobAttempted` 事件现在通过 `$exception` 属性公开异常对象（或 `null`），取代了之前的布尔类型 `$exceptionOccurred` 属性：

```php
// Laravel <= 12.x
$event->exceptionOccurred;

// Laravel >= 13.x
$event->exception;
```
如果您监听此事件，请相应更新您的监听器代码。

<a name="queuebusy-event-property-rename"></a>
#### `QueueBusy` 事件属性重命名

**影响可能性：低**

`Illuminate\Queue\Events\QueueBusy` 事件的 `$connection` 属性已重命名为 `$connectionName`，以与其他队列事件保持一致。

如果您的监听器引用了 `$connection`，请将其更新为 `$connectionName`。

<a name="queue-contract-method-additions"></a>
#### `Queue` 契约方法添加

**影响可能性：极低**

`Illuminate\Contracts\Queue\Queue` 契约现在包含了之前在文档块（docblock）中声明的队列大小检查方法。

如果您维护了该契约的自定义队列驱动实现，请为以下方法添加实现：

<div class="content-list" markdown="1">

- `pendingSize`
- `delayedSize`
- `reservedSize`
- `creationTimeOfOldestPendingJob`

</div>



<a name="routing"></a>
### 路由

<a name="domain-route-registration-precedence"></a>
#### 域名路由注册优先级

**影响可能性：低**

带有显式域名的路由现在在路由匹配中优先于非域名路由。

这使得即使非域名路由更早注册，通配子域名路由能够保持一致的行为。如果您的应用依赖于域名路由与非域名路由之间原有的注册优先级，请检查路由匹配行为。

<a name="scheduling"></a>
### 任务调度

<a name="withscheduling-registration-timing"></a>
#### `withScheduling` 注册时机

**影响可能性：极低**

通过 `ApplicationBuilder::withScheduling()` 注册的调度任务现在会延迟到 `Schedule` 被解析时才执行。

如果您的应用依赖于在引导阶段立即执行调度注册，您可能需要调整相关逻辑。

<a name="security"></a>
### 安全

<a name="request-forgery-protection"></a>

#### 请求伪造保护

**影响可能性：高**

Laravel 的 CSRF 中间件已从 `VerifyCsrfToken` 重命名为 `PreventRequestForgery`，并且现在包含使用 `Sec-Fetch-Site` 标头进行的请求来源验证。

`VerifyCsrfToken` 和 `ValidateCsrfToken` 仍作为已弃用的别名保留，但直接的引用应更新为 `PreventRequestForgery`，特别是在测试或路由定义中排除中间件时：

```php
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;

// Laravel <= 12.x
->withoutMiddleware([VerifyCsrfToken::class]);

// Laravel >= 13.x
->withoutMiddleware([PreventRequestForgery::class]);
```

中间件配置 API 现在也提供了 `preventRequestForgery(...)` 方法。

<a name="support"></a>
### 支持

<a name="manager-extend-callback-binding"></a>
#### 管理器 `extend` 回调绑定

**影响可能性：低**

通过管理器 `extend` 方法注册的自定义驱动闭包现在会绑定到管理器实例。

如果您之前在这些回调中依赖于另一个绑定对象（例如服务提供者实例）作为这些回调中的 `$this`，您应将这些值通过 `use (...)` 语法捕获到闭包中。


<a name="str-factories-reset-between-tests"></a>
#### 测试间重置 `Str` 工厂

**影响可能性：低**

Laravel 现在在测试拆卸（teardown）时会重置自定义的 `Str` 工厂。

如果您的测试依赖于自定义的 UUID / ULID / 随机字符串工厂在测试方法之间保持持久化，您应在每个相关测试或设置钩子中重新设置它们。

<a name="jsfrom-uses-unescaped-unicode-by-default"></a>
#### `Js::from` 默认使用未转义的 Unicode

**影响可能性：极低**

`Illuminate\Support\Js::from` 现在默认使用 `JSON_UNESCAPED_UNICODE` 选项。

如果您的测试或前端输出比较依赖于已转义的 Unicode 序列（例如 `\u00e8`），请更新您的预期值。

<a name="utilities"></a>
### 工具类

<a name="symfony-polyfill"></a>

#### Symfony PHP 8.5 Polyfill 与全局函数冲突

**影响可能性：低**

Laravel 13 引入了对 `symfony/polyfill-php85` 的依赖。在低于 PHP 8.5 的版本上，此 Polyfill 会定义诸如 `array_first()` 和 `array_last()` 等全局函数——除非这些函数已经在引导过程中更早地定义过。

这些函数可能与传统的辅助函数包（如 `laravel/helpers`）或使用相同名称的自定义全局辅助函数产生冲突。例如，历史版本的 `array_first()` 辅助函数接受一个回调以返回第一个匹配的元素，而 Polyfill 版本仅返回数组的第一个元素。

为避免冲突并确保在不同 PHP 版本间行为一致，您应优先使用 `Illuminate\Support\Arr` 方法：

```php
use Illuminate\Support\Arr;

Arr::first($array, function ($value) {
  return /* 条件 */;
});
```

<a name="views"></a>
### 视图

<a name="pagination-bootstrap-view-names"></a>

#### 分页 Bootstrap 视图名称

**影响可能性：低**

Bootstrap 3 默认的内部分页视图名称现在已明确：

```nothing
// Laravel <= 12.x
pagination::default
pagination::simple-default

// Laravel >= 13.x
pagination::bootstrap-3
pagination::simple-bootstrap-3
```




如果您的应用直接引用了旧的分页视图名称，请更新这些引用。

<a name="miscellaneous"></a>
### 其他事项

我们还建议您查看 `laravel/laravel` 的 [GitHub 仓库](https://github.com/laravel/laravel) 中的变更。虽然许多更改并非必须，但您可能希望将这些文件与您的应用保持同步。其中一些更改已在本升级指南中涵盖，而其他更改（例如配置文件或注释的更新）则未提及。您可以使用 [GitHub 比较工具](https://github.com/laravel/laravel/compare/12.x...13.x) 轻松查看变更，并选择对您重要的更新。

