Laravel 升级必须逐主版本进行(如 8→9→10→11),不可跨大版本直连;需严格遵循官方 Upgrade Guide 中的 Breaking Changes,同步更新第三方包兼容性,并手动处理结构变更与废弃 API。

确认当前版本和目标版本是否可直连升级
不能跨大版本跳升——这是最常被忽略的硬约束。比如你用的是 Laravel 8.x,想一步升到 11.x,composer update 很可能卡死或报一堆冲突,因为 v9、v10 中大量废弃项(如 $dates 属性、URL::to() 辅助函数)在 v11 已彻底移除,中间缺失的兼容层根本不存在。
官方只保证相邻主版本间的平滑路径:8 → 9 → 10 → 11。每升一级,都必须跑完对应版本的Upgrade Guide里标为「Breaking Changes」的部分。查当前版本用:php artisan --version;查目标版本 PHP 要求(如 Laravel 11 需 php: "^8.2"),别等 composer update 报错才回头改环境。
- 用
composer outdated laravel/*快速看哪些包还没跟上当前 Laravel 版本 - 第三方包(如
spatie/laravel-permission、laravel/sanctum)必须同步查其 GitHub README 或 packagist 兼容表,很多包在 Laravel 11 发布后 2–4 周才更新适配 - 若项目用了自定义 Artisan 命令或重写了
App\Http\Kernel,v11 的轻量内核结构会直接让它们失效——不是报错,而是静默不注册
备份与分支操作不是流程,是止损底线
升级失败最典型的后果不是代码报错,而是数据库迁移回滚失败、缓存键错乱、队列任务卡死——这些不会立刻暴露,可能上线两小时后才爆发。所以「备份」不是复制一份 zip 就完事。
- Git 提交全部未暂存文件后,建新分支:
git checkout -b upgrade/laravel-11 -
mysqldump或pg_dump导出全库(含 schema + data),别信「本地开发没数据就不用备」——种子数据、配置表(如settings)一旦被迁移脚本误删,恢复成本远超预期 - 把
.env单独另存为.env.backup-20260311,v11 默认启用APP_KEY强校验,旧 key 格式可能触发加密解密失败
执行升级时,composer update 必须带约束参数
直接敲 composer update 是高危操作:它会无差别升级所有依赖,可能把 guzzlehttp/guzzle 升到 8.x(Laravel 11 官方仅测试过 7.5+),或把 phpunit/phpunit 升到 v12(而 v11 的 TestCase 还没适配)。结果就是测试全挂,你还得花半天倒查哪个包惹的祸。
正确做法是锁死非 Laravel 相关包,只动框架生态:
- 先改
composer.json:把"laravel/framework": "^11.0"和配套包(如"laravel/sanctum": "^4.0")写准,PHP 版本行同步改为"php": "^8.2" - 运行:
composer update laravel/framework laravel/sanctum laravel/pint --with-all-dependencies(把实际用到的 Laravel 生态包列全) - 如果提示 conflict,别硬加
--ignore-platform-reqs——去看composer why-not laravel/framework:11.0,定位具体哪个包在拦路
v11 的结构变更必须手动干预,自动工具靠不住
Laravel 11 默认不生成 app/Http/Controllers、app/Http/Middleware 等目录,而是靠匿名函数和属性绑定。但你的老项目不可能重写所有控制器——这时候 laravel/upgrade 包的 php artisan upgrade 命令只能帮你挪文件,没法重构逻辑。
- 运行命令前先装:
composer require laravel/upgrade --dev,再执行php artisan upgrade - 它会自动:把
config/logging.php拆成独立 channel 配置、把AppServiceProvider的register()内容移到bootstrap/app.php、删除已废弃的Illuminate\Support\Facades\URLfacade 引用 - 但它不会碰你写的
PostController@store方法里还用着的Request::segment(1)——这个在 v11 已废弃,得你手动换成request()->route()->getPrefix()或类似逻辑
最易漏的是 cache 和 queue 配置里的驱动类名变更(如 database 驱动现在强制要求 retry_after 字段),不报错,但队列任务会无限重试然后丢弃。










