跳转到主要内容

前言

Laravel 13 于 2026 年 3 月发布。本指南讲解从 Laravel 12.x 升级到 13.x 的操作步骤。
预计升级时间约 10 分钟。当然,破坏性变更对应用的影响会因规模和所用功能而异。

使用 AI 升级

也可以用 Laravel Boost 自动化升级。Boost 是官方的 MCP 服务器,能为 AI 助手提供分步的升级提示。装到 Laravel 12 应用后,可以在 Claude Code、Cursor、OpenCode、Gemini、VS Code 中通过 /upgrade-laravel-v13 斜杠命令开始升级到 Laravel 13。该命令需要 laravel/boost ^2.0 即便你使用的 AI 工具不支持斜杠命令,也可以直接引用提示词文件,执行相同的升级步骤。把下面的 prompt 原样贴给 AI 即可。
提示词
请读取 Laravel Boost 提供的提示词,完成从 Laravel 12 到 13 的升级工作。
https://raw.githubusercontent.com/laravel/boost/refs/heads/main/src/Mcp/Prompts/UpgradeLaravelv13/upgrade-laravel-v13.blade.php

- 反映 `laravel/laravel` 骨架的变更。请参考 13.x 分支,而不是 master。
- `database/migrations/*_create_cache_table.php` 不要直接改,而是创建新的迁移。
- 把 `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 名
  • 集合模型的序列化
  • Container::call 与 Nullable 类的默认值
  • 域名路由注册的优先级
  • JobAttempted 事件的异常载荷
  • Manager extend 回调的绑定
  • MySQL DELETE 查询(JOIN / ORDER BY / LIMIT)
  • 分页 Bootstrap 视图名
  • 多态 pivot 表名的生成
  • QueueBusy 事件的属性名变更
  • Str factory 在测试间的重置

升级步骤

更新依赖

影响度:高 请更新 composer.json 中的以下依赖。
{
  "require": {
    "laravel/framework": "^13.0",
    "laravel/tinker": "^3.0"
  },
  "require-dev": {
    "phpunit/phpunit": "^12.0",
    "pestphp/pest": "^4.0"
  }
}
在使用 Laravel Boost 时也一并更新。
{
  "require": {
    "laravel/boost": "^2.0"
  }
}
改完之后执行:
composer update

更新 Laravel installer

影响度:高 如果你用 Laravel installer CLI 创建新项目,请把它升到兼容 Laravel 13.x 的版本。 composer global require 安装的情况:
composer global update laravel/installer
在使用 Laravel Herd 的捆绑版本时,请把 Herd 本身升到最新版。

破坏性变更(Breaking Changes)

安全

请求伪造防御

影响度:高 Laravel 的 CSRF 中间件从 VerifyCsrfToken 重命名为 PreventRequestForgery。同时新增了基于 Sec-Fetch-Site 头的请求来源校验。 VerifyCsrfTokenValidateCsrfToken 会作为废弃别名保留,但所有直接引用它们的地方都需要改成 PreventRequestForgery。特别是在测试或路由定义中排除该中间件时要留意。
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(...)

缓存

影响度:低 Laravel 默认的缓存及 Redis 键前缀现在使用连字符风格的后缀。默认的 session Cookie 名也改为使用 Str::snake(...) 由于大多数应用都会在配置里显式设定值,所以只有依赖框架回退默认值的应用会受影响。
// 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 里显式设置。
CACHE_PREFIX=myapp_cache_
REDIS_PREFIX=myapp_database_
SESSION_COOKIE=myapp_session

缓存 serializable_classes 设置

影响度:中 默认的 cache 配置新增了 serializable_classes 选项,默认为 false。这样即使 APP_KEY 泄露,也能防御 PHP 反序列化 gadget 链攻击。 如果你的应用确实在缓存里存 PHP 对象,需要显式把允许反序列化的类列出来。
// config/cache.php
'serializable_classes' => [
    App\Data\CachedDashboardStats::class,
    App\Support\CachedPricingSnapshot::class,
],
如果之前是让任意缓存对象反序列化,请改为显式许可列表,或者迁移到非对象的缓存载荷(例如数组)。

Session serialization 设置

影响度:中 Laravel 13 的骨架(laravel/laravel)在 config/session.php 里加入了 'serialization' => 'json'。但框架内部的默认值仍然是 php
用 AI 工具原样应用骨架变更时,config/session.php 里可能会写入 'serialization' => 'json'。这个改动会切换 session 的序列化方式,会让在 session 中存 PHP 对象的应用出错。
官方升级指南没有提到这个改动。这大概意味着 不必更改。Laravel 有时在“上一个大版本”升级到当前版本的指南中不会记录这类变化。数年后再升级时,如果没有信息会很麻烦,所以民间记录也很重要。 要保持与 Laravel 12 及以前一致的行为,请显式设置 'serialization' => 'php'
// config/session.php
'serialization' => 'php',
如果想启用 JSON 序列化,请事先确认 session 里没有存 PHP 对象。

容器

Container::call 与 Nullable 类的默认值

影响度:低 Container::call 在没有绑定时会尊重 nullable 类参数的默认值(和 Laravel 12 中构造注入的行为一致)。
$container->call(function (?Carbon $date = null) {
    return $date;
});

// Laravel <= 12.x: 返回一个 Carbon 实例
// Laravel >= 13.x: 返回 null

数据库

MySQL DELETE 查询

影响度:低 Laravel 现在会为 MySQL 语法编译完整的、带 ORDER BYLIMITDELETE ... JOIN 查询。 以前带 JOIN 的 DELETE 里 ORDER BY / LIMIT 子句会被忽略。Laravel 13 会把这些子句写进生成的 SQL 中。这在不支持该语法的数据库引擎上可能引发 QueryException

Eloquent

多态 pivot 表名的生成

影响度:低 使用自定义 pivot 模型类的多态 pivot 模型在推断表名时,Laravel 会生成复数形式。 如果依赖此前推断出的单数形式表名,请在 pivot 模型里显式定义表名。
class RoleUser extends MorphPivot
{
    protected $table = 'role_user'; // 显式指定
}

集合模型的序列化

影响度:低 Eloquent 模型集合被序列化和恢复(比如推入队列的作业)时,会为模型恢复被 eager 加载的关联。 如果你有代码依赖“反序列化后关联不存在”这一点,需要修改。

队列

JobAttempted 事件的异常载荷

影响度:低 Illuminate\Queue\Events\JobAttempted 事件用 $exception(异常对象或 null)取代了原先的 boolean 属性 $exceptionOccurred
// Laravel <= 12.x
if ($event->exceptionOccurred) {
    // 发生了异常
}

// Laravel >= 13.x
if ($event->exception !== null) {
    // 发生了异常
    $exception = $event->exception;
}

QueueBusy 事件的属性名变更

影响度:低 为了和其他队列事件保持一致,Illuminate\Queue\Events\QueueBusy 事件的属性 $connection 重命名为 $connectionName
// Laravel <= 12.x
$event->connection;

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

路由

域名路由注册的优先级

影响度:低 有明确域名的路由在匹配时会优先于无域名路由。 这样即便无域名路由先注册,catchall 的子域名路由也能一致工作。

支持

Manager extend 回调的绑定

影响度:低 通过 Manager 的 extend 方法注册的自定义驱动闭包会被绑定到 manager 实例上。 如果之前这些回调里的 $this 引用了别的对象(例如服务提供者实例),需要用 use (...) 把它捕获到闭包里。
// Laravel <= 12.x
Manager::extend('custom', function ($app) {
    return $this->createCustomDriver($app); // $this 是服务提供者
});

// 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 钩子中重新配置。

视图

分页 Bootstrap 视图名

影响度:低 Bootstrap 3 默认的内置分页视图名变得更明确。
// Laravel <= 12.x
pagination::default
pagination::simple-default

// Laravel >= 13.x
pagination::bootstrap-3
pagination::simple-bootstrap-3
如果直接引用了旧的分页视图名,请更新。

已废弃的功能

功能替代
VerifyCsrfToken 中间件PreventRequestForgery
ValidateCsrfToken 中间件PreventRequestForgery
JobAttempted::$exceptionOccurredJobAttempted::$exception
QueueBusy::$connectionQueueBusy::$connectionName

契约新增

影响度:非常低 只对有自定义实现的场景有影响。

Dispatcher 契约

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

ResponseFactory 契约

Illuminate\Contracts\Routing\ResponseFactory 契约新增了 eventStream 签名。

MustVerifyEmail 契约

Illuminate\Contracts\Auth\MustVerifyEmail 契约新增了 markEmailAsUnverified()

Queue 契约

Illuminate\Contracts\Queue\Queue 契约新增了以下队列体积检查方法(以前只在 docblock 中声明):
  • pendingSize
  • delayedSize
  • reservedSize
  • creationTimeOfOldestPendingJob

Store / Repository 契约

缓存契约新增了用于延长 TTL 的 touch 方法。
// Illuminate\Contracts\Cache\Store
public function touch($key, $seconds);

新功能亮点

AI 辅助升级(Laravel Boost)

Laravel Boost 是官方 MCP 服务器。它与 AI 编辑器联动,通过 /upgrade-laravel-v13 命令半自动完成升级。

通过 Sec-Fetch-Site 头做来源校验

PreventRequestForgery 中间件增加了基于 Sec-Fetch-Site 头的来源校验,加固了 CSRF 保护。

安全的缓存反序列化

通过 serializable_classes 设置,只有列入许可的类才会被反序列化。对 PHP 反序列化攻击的安全性得到提升。

SSE(Server-Sent Events)的 eventStream

ResponseFactory 契约新增了 eventStream,改进了 Server-Sent Events 支持。

队列可观测性提升

Queue 契约的 pendingSizedelayedSizereservedSize 等方法让你可以更细粒度地监控队列状态。

常见迁移问题与解决方案

问题:CSRF 相关测试失败

**症状:**引用 VerifyCsrfToken 的测试因“找不到类”而失败。 **解决:**把所有引用都改为 PreventRequestForgery
// Before
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
->withoutMiddleware([VerifyCsrfToken::class]);

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

问题:缓存中的对象无法恢复

**症状:**从缓存取到的数据变成 null,或抛 UnserializationFailedException **解决:**在 config/cache.phpserializable_classes 中加入要使用的类,或把缓存的值转成数组。
'serializable_classes' => [
    App\Models\User::class,
    App\Data\SomeData::class,
],

问题:JobAttempted 监听器不工作

症状:$event->exceptionOccurred 变成 null 或报未定义错误。 **解决:**改成 $event->exception !== null
// Before
if ($event->exceptionOccurred) { ... }

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

问题:session 失效

**症状:**升级后用户被强制登出。 **解决:**默认的 session Cookie 名变了。在 .env 里显式设置 SESSION_COOKIE 以保留原值。
SESSION_COOKIE=laravel_session

问题:找不到缓存键

**症状:**升级后缓存 miss 增多。 **解决:**缓存前缀变了。在 .env 里设置 CACHE_PREFIX,或清理缓存。
php artisan cache:clear

参考资料

最后修改于 2026年7月13日