十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Laravel 9升级指南:核心特性、迁移步骤与排坑实践

Laravel 9升级指南:核心特性、迁移步骤与排坑实践 搞 Laravel 项目这么多年每次大版本发布圈子里总会分成两派一派是“马上尝鲜派”另一派是“等稳定再升派”。到了 9.x 这次情况有点不一样——Laravel 9 在 2022 年 2 月 8 日正式发布官方直接把它定位成了“面向现代 PHP 的版本”最低要求 PHP 8.0底层大规模换装 Symfony 6同时把一年前 PHP 8.1 带来的枚举等新特性真正用进了框架核心。这篇文章我想从一个实际做升级、做维护的开发者角度把 Laravel 9.x 这次升级的核心特性、底层的设计意图、从 8.x 迁到 9.x 的实操步骤还有我在真实项目里踩过、排查过的坑一次性讲清楚。不管你是在评估“要不要升”还是已经升级到一半正在跟报错搏斗这篇应该都能给到一些有用的参考。我自己的经验是9.x 不是一个“多了几个新函数”的小版本它更接近一次“换地基”式的架构升级。如果你只把它当成普通 feature release升级过程中会吃不少亏反过来如果你理解了它背后的取舍升级完你能拿到的不仅是一个新版本还有一整套更现代的 PHP 开发体验。1. 这次升级到底改了啥Laravel 9.x 不是一次“功能上新”而是把地基重做了一遍1.1 为什么说 9.x 是“为现代 PHP 而生”的版本先聊一个很多人忽略的背景Laravel 9 把最低 PHP 版本拉到了 8.0这是一个信号说明框架开始真正以 PHP 8 为基线来写代码了。PHP 8.0 带来了构造函数属性提升、联合类型、match 表达式、nullsafe 操作符8.1 又带来了枚举、readonly 属性、first-class callable。Laravel 9 是第一个能直接吃到这些语言红利的长期支持版本。举个最直观的例子9.x 的迁移文件长这样?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up() { Schema::create(tasks, function (Blueprint $table) { $table-id(); $table-string(title); $table-text(description)-nullable(); $table-timestamps(); }); } public function down() { Schema::dropIfExists(tasks); } };注意看这里没有类名了直接return new class。这是一个“匿名类迁移”的写法也是 Laravel 9 的默认迁移风格。它解决的是我过去经常遇到的一个非常现实的痛点两个开发者同一天各自创建了一个迁移文件结果两个代码分支合到一起时自动生成的迁移类名完全一样于是报Cannot declare class CreateUsersTable because the name is already in use整个php artisan migrate直接挂掉。匿名类迁移从根上杜绝了这个问题因为每个迁移类都是独立的匿名类名字不会再冲突。所以你看9.x 的很多“核心特性”表面上不花哨但每一条都是在解决真实开发里让人头疼的问题。1.2 升级对普通项目的真实影响面升级到 9.x 之前要先明白这次变更会动到哪些地方。根据我拆过的项目影响面主要集中在几块运行环境PHP 必须是 8.0 及以上推荐 8.1Composer 依赖树laravel/framework要升到^9.0同时一大票第三方包的版本也会跟着动文件存储层Flysystem 从 2.x 升级到 3.x属于破坏性变更队列任务分发dispatchNow方法被移除必须改用dispatchSync迁移机制默认迁移格式改成匿名类旧格式还能跑但建议逐步迁移前端构建官方生态从 Laravel Mix/Webpack 逐步转向 Vite。千万别觉得“Laravel 升级就是改一下 composer.json 里的版本号”。这套连带变更如果项目里有老代码、自定义存储驱动、自定义队列逻辑工作量会成倍上涨。后面我会详细讲每一步怎么做。2. 值得重点关注的 6 个核心特性拆解2.1 匿名迁移解决了困扰多年的“同名迁移类冲突”我刚才提到匿名迁移这里展开说说它的价值。在 Laravel 8 及更早的版本里每次执行php artisan make:migration create_tasks_table框架都会生成一个带名字的类class CreateTasksTable extends Migration { // ... }类名是根据迁移文件名自动生成并追加时间戳的理论上不会重复。但我在真实项目里至少遇到过三次类名冲突全是多分支并行开发时合代码合出来的。有一次是在 20 多个迁移文件堆积的老项目里两个同事分别在各自分支建了CreateOrdersTable合并后一执行迁移就直接白屏查了很久才发现是 PHP 类声明冲突而不是 SQL 问题。Laravel 9 把新迁移默认改成匿名类等于彻底把这个坑填平了。这个设计思路值得夸一下它没有通过复杂的命名规则去减少冲突概率而是直接让类“没有名字”从机制上消灭问题。对我们开发者的启示也很直接如果项目已经升到 9.x以后新建迁移直接就用默认的匿名类风格不用再改回老写法。2.2 枚举路由绑定PHP 8.1 枚举进入 Web 层PHP 8.1 的枚举Enum是个好东西但如果没有框架层面的配合你在路由里拿到的大多还是字符串还得自己手动校验、转换。Laravel 9 直接支持了枚举的路由绑定这在使用上非常舒服。假设你有一个文章状态枚举?php namespace App\Enums; enum PostStatus: string { case Draft draft; case Published published; case Archived archived; }路由可以这么写use App\Enums\PostStatus; Route::get(/posts/{status}, function (PostStatus $status) { return match ($status) { PostStatus::Draft 草稿, PostStatus::Published 已发布, PostStatus::Archived 已归档, }; });访问/posts/draftLaravel 会自动把draft字符串转换成PostStatus::Draft访问一个不存在的状态比如/posts/deleted会直接返回 404不需要你手写一堆 if/else 做校验。这个特性真正爽到的地方在控制器里。以前你要写“状态参数是否合法”的校验逻辑现在类型系统自己就把这层干掉了。如果你的项目正在用 PHP 8.1升级到 9.x 后这类代码会简洁很多。2.3 新的 Query Builder 接口契约先行Laravel 9 新增了Illuminate\Contracts\Database\Query\Builder接口并且让核心的查询构建器和 Eloquent Builder 都实现了它。很多业务开发同学看到这种抽象层更新第一反应是“跟我有什么关系”。我的理解是这个接口是给框架生态和包作者的一层“稳定契约”。以前如果你想写一个函数接受任意“能执行查询的东西”很难做类型约束因为项目里直接使用的是具体类Illuminate\Database\Query\Builder。现在可以通过接口来约束意味着第三方包可以更安全地对查询构建器做扩展、替换和测试。对普通业务项目来说你几乎感知不到这个变化但如果你自己维护 composer 包或者正在做一套需要兼容多种查询场景的基础组件这个接口就是 9.x 送给你的礼物。升级后建议检查一下自己的包代码把函数入参类型从具体类改成接口扩展性会好很多。2.4 从 Flysystem 2 到 Flysystem 3文件存储底层变更的连锁反应Flysystem 是 Laravel 文件存储的底层库9.x 把它升到了 3.x。这个升级属于“表面看似小、实际破坏性不小”的类型。最典型的破坏点是Flysystem 3 中适配器Adapter层被隐藏了。以前很多老代码会这么干$adapter Storage::disk(s3)-getAdapter();在 9.x 里这行代码会直接报方法不存在。因为新版的 Filesystem 类不再暴露底层 adapter官方目的是让上层文件操作接口更统一防止开发者依赖到具体存储适配器的实现细节。另一个变化是自定义文件系统驱动的方式变了。以前你可能实现的是某个旧版 AdapterInterface现在需要实现League\Flysystem\FilesystemAdapter接口方法更细返回类型也更明确。如果项目里有对接阿里云 OSS、七牛、MinIO 这种自定义存储驱动的代码升级时这基本是必踩的坑。一个靠谱的操作顺序是先把存储相关的单元测试跑一遍确认哪些接口调用挂了再根据新版接口逐个改不要直接上生产环境试。2.5 Laravel Scout 数据库引擎小项目也能全文检索Laravel Scout 是官方全文搜索组件以前主要配合 Algolia、Meilisearch、Typesense 这类外部搜索引擎使用。但外部服务需要注册账号、引入 SDK、维护索引对很多中小项目来说有点“杀鸡用牛刀”。Laravel 9 给 Scout 加了一个数据库驱动只要在模型里引入 Searchable再把SCOUT_DRIVER配成database就可以用$posts Post::search(关键字)-get();平时我会把这种驱动用在两类场景里一类是后台管理系统的简单搜索数据量在几万条以内没必要上外置搜索引擎另一类是项目早期快速验证搜索功能的交互逻辑等数据量上来再平滑切换到 Meilisearch。这个 feature 本身不复杂但扩展了 Scout 的适用范围对预算有限的小团队特别友好。2.6 周边工具链的“重磅”升级除了 Laravel 框架本体9.x 发布周期里还有几个官方工具值得关注。其中最实用的是laravel/pint一个基于 PHP-CS-Fixer 的代码风格修复工具内置 Laravel 官方代码规范安装后直接composer require laravel/pint --dev ./vendor/bin/pint就能把项目代码格式化到 Laravel 官方风格。我接手老项目时第一步就是跑一遍 pint再配合 git diff 看代码差异整顿代码风格效率奇高。另外 Laravel 9 的生态里Vite 开始取代 Laravel Mix 成为默认前端构建工具。严格说 Vite 支持是在 9.19 版本才默认铺开的但整个 9.x 生命周期里Breeze 和 Jetstream 这类脚手架项目已经全面转向 Vite。如果你在升级框架的同时想顺手把 Mix 换成 Vite注意静态资源路径、热更新端口、代理配置这几个点都要重新调。3. 从 8.x 升级到 9.x 的实操记录步骤与踩坑3.1 升级前需要确认的环境底线动手之前把环境先摸清楚不然升到一半会因为 PHP 版本不够直接卡死。我建议按这个顺序检查php -v composer --version composer show laravel/framework确认三点PHP 版本是否在 8.0 及以上Composer 是否 2.x当前 laravel/framework 是不是 8.x。如果 PHP 还是 7.4就别硬升级先解决运行环境。数据库这边也看了一眼Laravel 9 支持的数据库版本比 8 略高一点。MySQL 5.7 还能用但官方推荐 MySQL 8.0MariaDB 建议 10.3PostgreSQL 建议 12。如果你的项目还在 MySQL 5.6 这种老古董上跑升级框架前先规划数据库升级不然后面 InnoDB 全文索引、JSON 查询这些新特性都用不顺。3.2 一步步执行依赖升级环境确认没问题后开始改依赖。推荐直接在项目根目录执行composer require laravel/framework:^9.0 --with-all-dependencies注意一定要加--with-all-dependenciesComposer 会把其它相关依赖比如laravel/ui、laravel/sanctum、nunomaduro/collision等一起解析到兼容 9.x 的版本减少很多手动调整的麻烦。执行过程中Composer 会报哪些包需要升级、哪些包当前版本不兼容。这一步通常会牵扯出 PHP 8.0/8.1 的兼容性问题比如老包用了each()、create_function()之类的 PHP 7 函数。另一个常见问题是一些第三方 Laravel 包还没发布兼容 9.x 的版本这时候要么等官方更新要么用 fork 分支临时顶一下要么换替代包。我的习惯是在升级前先去 Packagist 查一遍核心依赖的兼容状态避免升到一半发现某个包没有 9.x 版本。依赖都解析通过后再把config/app.php、config/auth.php等核心配置文件和 9.x 的默认版本对比一下看有没有新增的配置项。最稳妥的办法是先把laravel/framework升上去跑一遍php artisan about看版本号和环境信息再根据输出微调环境变量。3.3 代码层面最常踩的 5 个不兼容点根据我升级过的几个项目下面这些不兼容点出场率最高一条条对号入座检查。第一dispatchNow被移除。老代码里常见的dispatchNow(new SomeJob());在 9.x 里已经不认识了必须改成dispatchSync(new SomeJob());全局dispatchNow函数也一样去全局搜一下替换掉。我有个老项目里这种调用有 30 多处当时就是全局搜索dispatchNow逐行改掉。第二getAdapter()不可用。前面说过 Flysystem 3 隐藏了 adapter老代码里所有Storage::disk(...)-getAdapter()都要重构。通常的做法是改用高层次的存储方法比如Storage::disk(custom)-put()、Storage::disk(custom)-readStream()尽量避免直接接触底层 adapter。如果确有必要可以通过自定义驱动的方式注入自己管理 adapter 实例。第三自定义 Flysystem 驱动的接口变了。原来实现League\Flysystem\AdapterInterface的类需要改成实现League\Flysystem\FilesystemAdapter。这个接口的方法更细包括fileExists、writeStream、readStream、deleteDirectory、createDirectory、setVisibility、getVisibility等返回类型也更严格。改完后记得把所有存储相关的测试跑一遍文件上传、下载、删除、目录遍历这些最容易漏。第四PHP 8.x 的弃用提示。Laravel 9 在 PHP 8.0/8.1 下运行没问题但老代码里经常有动态属性、each()这类写法PHP 8 开始会产生大量Deprecated警告。这些警告不会直接中断程序但会污染日志影响问题排查。升级时顺手把日志里的 deprecation 清理一遍对后续维护帮助很大。第五第三方包 config 文件发布。升级后重新执行php artisan vendor:publish --tagconfig --force把第三方包的最新配置文件发布到项目里。这一步很容易漏漏了之后可能出现“配置项不存在”或者包行为异常的问题。3.4 schema:dump 带来的迁移性能红利升级到 9.x 后我强烈建议试一下schema:dump这是提升部署效率的神器。php artisan schema:dump这个命令会把当前数据库的结构快照导出到database/schema/目录下。之后执行php artisan migrate时Laravel 会优先加载 schema 文件再执行那些还没有跑过的增量迁移而不是从第一个迁移文件慢慢执行到最后一个。对老项目来说这个优化太香了。我之前有个项目积累了 200 多个迁移文件在 CI 环境里跑migrate要耗 3 分钟以上做了schema:dump之后压缩到 30 秒以内。注意一点schema:dump导出的结构是“执行时刻的数据库状态”所以在生产环境跑之前最好由维护方先从一份干净的、执行完所有迁移的测试库里生成 dump避免把开发环境里临时改的表结构带进去。如果迁移文件里掺了数据填充或者存储过程schema:dump默认只导出表结构数据填充还得用 seeder 解决。这块结合项目实际情况取舍但大多数项目用了之后部署提速非常明显。4. 升级后常见问题与排查清单4.1 问题速查表下面是我在 Laravel 9.x 升级和日常使用中遇到问题的整理按频率从高到低排列问题现象可能原因解决方案执行migrate报类名冲突老迁移文件仍是具名类且多人并行创建同名迁移新迁移统一使用匿名类老迁移建议重构为匿名类storage相关操作报方法不存在Flysystem 3 不暴露getAdapter()重构为高层存储 API或按新版接口实现自定义驱动dispatchNow调用报错Laravel 9 移除了该方法全局替换为dispatchSync升级后某第三方包异常包版本不兼容 9.x 或配置未重新发布升级包到兼容版本重新vendor:publish页面出现大量 deprecation 日志项目代码使用了 PHP 8 弃用特性根据日志逐项修改重启 PHP-FPM前端资源构建失败Mix/Webpack 升级后不兼容 Node 高版本迁移到 Vite使用新的构建脚本迁移时提示 schema 文件与迁移文件不一致schema:dump生成的快照字段与当前迁移不一致用干净的测试库重新生成 schema dumpRedis 缓存或队列报连接错误phpredis 扩展版本太老升级 phpredis 扩展到支持 PHP 8 的版本4.2 我的两个排查心得第一个心得升完级之后优先跑测试而不是手动点页面。项目里只要写了一定量的功能测试升级后立刻跑一遍php artisan test很多不兼容问题会直接暴露出来比如 response 结构变化、session 驱动差异、队列同步执行的行为变化。我之前有一次就是升级后没跑测试结果登录功能是坏的排查了半天才发现是 session 的加密方式变了。测试套件在这里的价值是能帮你把隐性破坏快速定位到具体模块。第二个心得遇到诡异的报错先把缓存清一遍。Laravel 9 的缓存系统对 config、route、view 都有缓存升级后旧缓存经常会把老代码的类映射、路由表残留在里面。我处理过一个案例升级完一个路由怎么都访问不到查路由列表又是存在的最后发现是 route cache 没清。所以升级后立刻跑php artisan optimize:clear再继续排错能省掉很多冤枉时间。另外再分享一个 Laravel 9 里很实用的小技巧php artisan route:list --json。升级后路由数量多、检查路由是否正常时这个命令可以直接输出 JSON 格式的路由表配合 jq 或者自己写脚本做路由检查非常方便比原来纯文本输出好解析太多了。这也是 9.x 在开发体验上一个很实用的细节改进。最后再聊一下我个人的真实体会Laravel 9.x 这轮升级最值得的不是某个具体的新函数而是整个技术栈的“地基现代化”。如果你还在 8.x 甚至更老的版本上而且 PHP 版本也有条件升到 8.1我建议认真规划一次升级。升的过程中可能会烦会遇到 Flysystem、队列、第三方包各种不兼容但升完之后你会发现项目在性能、代码可维护性、生态兼容性上都往前迈了一大步。尤其是那些计划长期维护的项目停在老版本上越久后面升级的墙就越高。选一个业务相对平稳的窗口期配好测试把 9.x 一次升到位这笔技术债还得很值。
返回列表