跳转到主要内容

前言

Laravel 9 于 2022 年 2 月 8 日发布。本文整理从 Laravel 8.x 升级到 9.x 的步骤,以及影响面较大的变更点。
预计升级时间约 30 分钟。如果你自定义了邮件发送、文件存储、Custom Cast,或者覆写了框架的核心类,工作量可能更多。

用 Laravel Shift 自动升级

也可以用 Laravel Shift 自动化升级。Shift 会协助更新 composer.json 和配置文件,可以作为你审阅 diff 的起点。

按影响面分级的变更

影响度:高

  • 依赖更新
  • 迁移到 Flysystem 3.x
  • 迁移到 Symfony Mailer

影响度:中

  • BelongsToManyfirstOrNew / firstOrCreate / updateOrCreate
  • Custom Cast 与 null 的行为
  • HTTP Client 的默认超时
  • 新增 PHP Return Type
  • Postgres schema 配置改名
  • 废弃 assertDeleted 方法
  • lang 目录的移动
  • 密码规则变更
  • when / unless 方法变更
  • 校验中未验证数组键的处理

升级步骤

更新依赖

影响度:高 Laravel 9 需要 PHP 8.0.2 或以上。首先检查 composer.json 的依赖。
{
  "require": {
    "php": "^8.0.2",
    "laravel/framework": "^9.0",
    "spatie/laravel-ignition": "^1.0"
  },
  "require-dev": {
    "nunomaduro/collision": "^6.1"
  }
}
依应用情况还需以下更新:
  • 删除 facade/ignition,改为 spatie/laravel-ignition:^1.0
  • 使用 pusher/pusher-php-server 时升级到 ^5.0
  • 确认所使用的第三方包已适配 Laravel 9
  • 使用 Vonage 通知通道时请另外查阅其升级指南
之后执行:
composer update

PHP 版本要求

影响度:高 Laravel 9 必须 PHP 8.0.2 及以上。请在 CI、本地开发和生产环境中都统一 PHP 版本之后再升级。

迁移到 Symfony Mailer

影响度:高 Laravel 9 的一大变化是把 2021 年 12 月已终止维护的 SwiftMailer 换成了 Symfony Mailer。只使用 Mail::to()->send() 的应用影响很小,但直接使用 SwiftMailer 低层 API 的地方需要检查。

驱动依赖

# 使用 Mailgun 时才需要添加
composer require symfony/mailgun-mailer symfony/http-client

# 使用 Postmark 时,删掉 SwiftMailer 相关包并替换
composer remove wildbit/swiftmailer-postmark
composer require symfony/postmark-mailer symfony/http-client

withSwiftMessagewithSymfonyMessage

// Laravel 8.x:基于 SwiftMailer
$this->withSwiftMessage(function ($message) {
    $message->getHeaders()->addTextHeader('Custom-Header', 'Header Value');
});

// Laravel 9.x:基于 Symfony Mailer
use Symfony\Component\Mime\Email;

$this->withSymfonyMessage(function (Email $message) {
    $message->getHeaders()->addTextHeader('Custom-Header', 'Header Value');
});
Illuminate\Mail\Mailersendhtmlrawplain 现在返回 Illuminate\Mail\SentMessage 而不是 voidMessageSent 事件的 message 属性也从 Swift_Message 变成了 Symfony\Component\Mime\Email

重新检查 SMTP 配置

Symfony Mailer 废弃了 SMTP 的 stream 选项,支持的配置项被移到顶层。
return [
    'mailers' => [
        'smtp' => [
            // Laravel 8.x 的写法
            'stream' => [
                'ssl' => [
                    'verify_peer' => false,
                ],
            ],

            // Laravel 9.x 的写法
            'verify_peer' => false,
        ],
    ],
];
不再需要显式设置 auth_mode。相较发送后再回收无效邮件地址,推荐在发送前完成校验。

迁移到 Flysystem 3.x

影响度:高 Laravel 9 把 Storage facade 内部实现从 Flysystem 1.x 升到了 3.x。文件操作方法尽量保持了兼容,但异常、返回值、适配器注册周围有差异。

需要额外安装的驱动

# Amazon S3 驱动
composer require -W league/flysystem-aws-s3-v3 "^3.0"

# FTP 驱动
composer require league/flysystem-ftp "^3.0"

# SFTP 驱动
composer require league/flysystem-sftp-v3 "^3.0"

Storage 的主要行为变化

  • put / write / writeStream 默认会覆盖已有文件
  • 写入失败时返回 false 而不是抛异常
  • 读取不存在的文件返回 null 而不是异常
  • 对不存在文件调用 delete 会返回 true
  • cached adapter 已被移除,可以从 disk 配置中删除 cache
如果想像以前那样在写入失败时抛异常,请设置 throw 选项。
return [
    'disks' => [
        'public' => [
            'driver' => 'local',
            // 仅当想在写入失败时抛异常时才启用
            'throw' => true,
        ],
    ],
];
如果注册了自定义 filesystem 驱动,请把 Storage::extend() 的回调改为直接返回 Illuminate\Filesystem\FilesystemAdapter

BelongsToManyfirstOrNew / firstOrCreate / updateOrCreate

影响度:中 在 Laravel 8 中,这些方法的第一个参数(属性数组)是和中间表比较的。Laravel 9 改为和关联模型的表比较。
// 会用 name 到关联模型的表里查找并更新
$user->roles()->updateOrCreate([
    'name' => 'Administrator',
]);
同时,firstOrCreate 也支持第二个参数 $values,与其他关联的行为对齐。
// 只有新建时才合并 created_by
$user->roles()->firstOrCreate([
    'name' => 'Administrator',
], [
    'created_by' => $user->id,
]);

Custom Cast 与 null

影响度:中 Laravel 9 中,即便把 null 赋给要 cast 的字段,custom cast 的 set 方法也会被调用。没有考虑 null 的 cast 升级后可能抛异常。
public function set($model, $key, $value, $attributes)
{
    // Laravel 9 中可能会传入 null
    if ($value === null) {
        return [
            'address_line_one' => null,
            'address_line_two' => null,
        ];
    }

    if (! $value instanceof AddressModel) {
        throw new InvalidArgumentException('请传入 AddressModel。');
    }

    return [
        'address_line_one' => $value->lineOne,
        'address_line_two' => $value->lineTwo,
    ];
}

HTTP Client 的默认超时

影响度:中 HTTP Client 的默认超时变为 30 秒。以前有可能无限等待。
use Illuminate\Support\Facades\Http;

// 只对需要更长时间的 API 调用单独延长超时
$response = Http::timeout(120)->get('https://example.com/api/status');

新增 PHP Return Type

影响度:中 为了对齐 PHP 本身和 Symfony 的要求,Laravel 9 给各个核心类加上了返回值类型。如果你继承了 Laravel 的核心类并覆写 offsetGetoffsetSetjsonSerializeopenread 等,请给自己的实现也加上同样的返回值类型。
// 覆写核心类时,请对齐返回值类型
public function offsetGet($key): mixed
{
    return parent::offsetGet($key);
}

Postgres schema 配置改名

影响度:中 在 Postgres 连接中设置搜索路径时,请把 config/database.php 的键从 schema 改成 search_path
return [
    'pgsql' => [
        // Laravel 8.x
        'schema' => 'public',

        // Laravel 9.x
        'search_path' => 'public',
    ],
];

assertDeleted 改为 assertModelMissing

影响度:中 原本用于断言模型删除的 assertDeleted 请替换成 assertModelMissing
// Before
$this->assertDeleted($user);

// After
$this->assertModelMissing($user);

lang 目录移动

影响度:中 Laravel 9 新项目里,语言文件的位置从 resources/lang 改到了项目根目录下的 lang。已有应用只是继续运行的话影响不大,但当你要贴近新骨架结构、或包中发布翻译文件时需要检查。
// 不用固定路径,用 langPath() 决定发布位置
$this->publishes([
    __DIR__.'/../lang' => app()->langPath('vendor/package-name'),
]);

密码规则变更

影响度:中 用于校验和当前登录用户密码一致的 password 规则改名为 current_password
// Before
'password' => ['required', 'password'],

// After
'password' => ['required', 'current_password'],

when / unless 方法变更

影响度:中 在 Laravel 8 中,给 whenunless 传闭包时,闭包本身会被当作 truthy,可能触发非预期分支。Laravel 9 中会执行闭包,并把返回值作为条件。
$collection->when(function ($collection) {
    // 这里返回的值会被当作条件
    return false;
}, function ($collection) {
    // 因为返回了 false,这里不会执行
    $collection->merge([1, 2, 3]);
});

未校验数组键的处理

影响度:中 Laravel 9 中,validated() 返回的数组会始终排除未校验的数组键。想保留 Laravel 8 的兼容行为时,请显式调用 includeUnvalidatedArrayKeys()
use Illuminate\Support\Facades\Validator;

public function boot()
{
    // 只在想回到 Laravel 8 行为时启用
    Validator::includeUnvalidatedArrayKeys();
}

总结

Laravel 8 到 9 的升级重点是:升到 PHP 8.0.2、迁移到 Symfony Mailer、适配 Flysystem 3.x。先检查邮件发送、存储、Custom Cast、测试辅助器,能减少升级后的问题。
变更点影响度应对
PHP 8.0.2 / laravel/framework:^9.0更新 composer.json 与运行环境
迁移到 Symfony Mailer检查 SwiftMailer 相关 API 与 mail 配置
Flysystem 3.x检查 Storage 的返回值、异常与驱动依赖
BelongsToMany 的 upsert 系方法注意查询对象已变为关联模型的表
Custom Cast 与 null修复 set() 收到 null 时会崩的地方
HTTP Client 30 秒超时只对必要请求显式设置 timeout()
废弃 assertDeleted替换为 assertModelMissing()
lang 目录移动固定路径引用改用 app()->langPath()
password 规则变更改成 current_password
排除未校验数组键只在需要时使用 includeUnvalidatedArrayKeys()

参考资料

最后修改于 2026年7月13日