测试环境必须用 composer install --no-dev,而非 update;因 update 会忽略 lock 文件、重新解析依赖图,引入未测版本组合,导致问题不可归因,而 install --no-dev --prefer-dist 才能确保复现精确锁定版本。

测试环境必须用 composer install --no-dev,不是 update
直接在测试环境跑 composer update 等于把开发机的不确定性复制过去——它会忽略 composer.lock,重新解析整个依赖图,可能装上本地没测过的版本组合。CI 流水线里一旦出问题,你根本分不清是代码改错了,还是某包小版本悄悄引入了 BC break。
正确做法是:测试环境只执行 composer install --no-dev --prefer-dist,强制读取当前 composer.lock 中锁定的精确版本(含 commit hash、dist shasum)。这样测试结果才可复现、可归因。
- CI 脚本开头加
composer validate --strict,校验composer.lock和composer.json是否匹配,防止手误提交了不一致的锁文件 - 若需验证升级效果,先在本地改好
composer.json,跑composer update --dry-run看输出,确认只动了目标包及其直系依赖,再提交新 lock 文件 - 禁用
composer update在测试/预发环境的权限,用 CI 配置项或部署脚本硬限制,避免人工误操作
升级前必须对比 composer.lock 差异,而不是只看 outdated
composer outdated 只告诉你“能升”,不告诉你“升了之后哪些包会被连带更新”。比如你只想升 guzzlehttp/guzzle,但实际 composer update guzzlehttp/guzzle --with-dependencies 可能把 psr/http-client 从 v1.0.3 升到 v1.0.4,而后者内部改了 sendRequest() 的返回类型声明——你的代码没报错,但 PHPStan 会立刻标红。
所以每次升级 PR,必须附上 git diff 对比前后的 composer.lock:
- 用
git diff --no-index old.lock new.lock | grep 'version\|source'快速定位跳主版本的包(如"version": "3.0.0"→"version": "4.0.0") - 重点关注
packages块里source的reference字段变化,这是 Git commit hash,能确认是否用了预期分支的代码 - 如果 diff 里出现大量未修改的包版本变动,说明
composer update波及范围失控,得回退并改用--with-dependencies精控
CI 中要拦截高风险包的无约束升级
有些包升级后不报错,但会在运行时静默改变行为:比如 monolog/monolog v2 → v3 后,默认日志格式从 Y-m-d H:i:s 变成 Y-m-d\TH:i:s.uP,监控系统按旧格式解析时间字段就全丢数据;symfony/http-foundation 升级后默认开启 X-Content-Type-Options: nosniff,前端某些老资源加载逻辑直接失败。
CI 流水线应加一道检查,识别这些包是否被宽松约束(如 "^2.0")且未显式锁定:
- 用
composer show monolog/monolog | grep 'versions'检查当前安装版本是否落在composer.json的约束范围内 - 对已知高风险包(
laravel/framework、symfony/*、guzzlehttp/guzzle、doctrine/orm),在 CI 脚本中写死校验逻辑:if [[ $(composer show laravel/framework | grep 'versions' | grep -o '10\.[0-9]\+') ]]; then echo "Laravel 10 detected, check BC breaks"; exit 1; fi - 禁止
composer.json中出现"guzzlehttp/guzzle": "^7"这类无小版本锚点的写法,强制要求写成"^7.5"或"^7.8.0"
PHP 版本升级后,必须重跑 composer update --dry-run
PHP 从 8.2 升到 8.3 后,很多包的 require 字段会突然不匹配:比如某个长期未维护的包写着 "php": "^8.0 || ^8.1",它在 8.2 下还能装,但在 8.3 下直接被 Composer 排除——但它不会出现在 outdated 列表里,因为不是“有新版”,而是“已失效”。
此时 composer update --dry-run 是唯一能暴露问题的命令:
- 它会模拟完整依赖解析,末尾明确提示哪条路径卡住,例如:
Root composer.json requires php ^8.3, but php 8.3.0 is not provided by any installed package - 配合
--ignore-platform-reqs临时绕过检查,只是为了定位问题,绝不能用于生产安装 - 若发现某包因 PHP 约束失效,优先查它是否有替代方案(如换成官方维护的
league/flysystem替代某个废弃的 S3 封装包),而不是降级 PHP
真正麻烦的不是升级动作本身,而是那些没出现在报错信息里的包——它们可能还在 composer.lock 里躺着,但 Composer 已经拒绝加载其任何新版本,下次有人手动 update 就会突然崩掉。


















