必须用精确版本(如"9.5.27")或^约束(如"^2.8")锁定依赖,禁用dev-和等模糊写法;platform配置须移至config.platform并填完整语义化版本;composer.lock必须提交,ci仅运行install而非update。

composer.json 里怎么写版本号才不容易翻车
模糊约束(比如 *、dev-master)是编译失败的头号推手。Composer 在解析时会取满足约束的最新可用版本,而这个“最新”可能带主版本跃迁(如 guzzlehttp/guzzle 从 7.x 升到 8.x),直接导致 new GuzzleHttp\Client() 报错。
稳妥做法是:用 ^ 锁定兼容范围,且尽量往小写。例如:
-
"monolog/monolog": "^2.8"→ 允许 2.8.0 到 2.999.x,不跨 3.x -
"phpunit/phpunit": "9.5.27"→ 精确锁定,适合 CI 或核心测试依赖 - 避免
"laravel/framework": "^10.0"和"spatie/laravel-backup": "^7.0"同时存在——后者在 Laravel 10 发布初期根本不兼容
platform 配置不是可选项,是必须项
很多人把 "php": "^8.2" 写在 require 里,结果本地 PHP 8.0 就卡死。正确姿势是把它挪进 config.platform,让 Composer “假装”你运行在目标环境上:
"config": {
"platform": {
"php": "8.1.28",
"ext-gd": "8.1.28",
"ext-mbstring": "8.1.28"
}
}
这样既绕过本地 PHP 版本不匹配报错,又保留对扩展的校验(不像 --ignore-platform-reqs 那样全关)。注意:platform.php 值必须是完整语义化版本("8.1.28" ✅,"8.1" ❌)。
composer.lock 不提交 = 每次 install 都是抽奖
没有 composer.lock,composer install 实际等价于 composer update —— 它会重新跑 SAT 求解,选当时镜像里最新的满足约束的版本。昨天能装,今天可能就失败。
团队协作中必须:
- 生成后立即
git add composer.lock - CI 流水线只跑
composer install,禁用update - 升级依赖时,明确执行
composer update vendor/package --with-all-dependencies,而不是随手composer update
否则你会遇到“本地好好的,CI 直接挂”,本质是两台机器解析出了不同依赖树。
CI 构建失败?先看 PHP 版本和扩展是否真一致
--ignore-platform-reqs 是最危险的“止痛药”。它能让 CI 跑通,但掩盖了真实问题:比如 ext-swoole 缺失,安装成功,运行时 Class 'Swoole\Http\Server' 直接炸。
真正可控的做法只有两个:
- 用 Docker 固定 PHP 版本 + 扩展组合,本地和 CI 完全一致
- 在 CI 脚本里显式安装缺失扩展,例如:
sudo apt-get install php-gd php-mbstring,再跑composer install
临时加 --ignore-platform-req=ext-gd 可以,但得确认你的代码确实没调用 GD 相关逻辑——否则上线后图片处理功能静默失效。











