ci中必须用composer install而非update,需确保每个子包有composer.lock并执行install --no-interaction --prefer-dist;缓存仅限~/.composer/cache;私有包需配置ssh认证;通过composer_root_version和composer_dev_mode控制多包行为;install后须运行check-platform-reqs。

CI里必须用composer install,不是update
多包项目(monorepo 或多个独立 composer 包)的 CI 流水线一旦误用 composer update,会立刻破坏版本一致性——哪怕只在一个子包里运行,也可能升级共享依赖(如 psr/log),导致其他包的测试意外失败。
真正安全的做法是:每个包目录下都确保存在 composer.lock,且 CI 脚本统一执行 composer install --no-interaction --prefer-dist。Git 提交前可用 composer install --dry-run 检查 lock 是否过期,避免人肉疏漏。
- 若子包未提交
composer.lock,install会退化为update,直接失去可重现性 - 禁止在 CI 中动态生成 lock 文件(如先
require再update),这等于把版本决策权交给 CI 机器 - 多包场景下,建议在根目录加校验脚本:
find packages/ -name 'composer.lock' -exec composer validate --no-check-publish {} \;
缓存不能只靠 vendor/,必须用 ~/.composer/cache
缓存 vendor/ 看似省事,但在多包 CI 中极易引发冲突:不同包可能用不同 PHP 版本构建,opcache 编译产物混在一起会导致 Class not found 随机报错;更糟的是,缓存 vendor/ 后跳过 composer install,等于绕过对 composer.lock 的最终校验。
正确做法是只缓存 Composer 自身下载层:~/.composer/cache(Linux/macOS)或 %APPDATA%\Composer\cache(Windows)。它只存 zip/dist 归档,与 PHP 环境无关,多包共用也完全安全。
- GitHub Actions 示例 key:
composer-${{ hashFiles('**/composer.lock') }}-${{ runner.os }}-php-${{ matrix.php-version }} - GitLab CI 中不要写
cache: paths: [vendor/],改用~/.composer/cache/files/ - 缓存命中后仍需执行
composer install——这是唯一能验证 lock 文件完整性、触发post-install-cmd的环节
私有包拉取失败?CI 环境要配 SSH Agent 和部署密钥
多包项目常依赖内部私有包(如 git@gitlab.internal:php/auth-sdk.git),CI 中 composer install 报 Project not found or you don't have access,基本是因为没配置 SSH 认证。
不能靠 git config --global url."git@gitlab.internal:".insteadOf "https://gitlab.internal/" 这类 hack,它不解决权限问题;也不能把私钥硬编码进脚本——既不安全又难轮换。
- GitLab CI:在
.gitlab-ci.yml中启用ssh-agent,用SSH_PRIVATE_KEY变量注入私钥,再ssh-add - GitHub Actions:用
webfactory/ssh-agentaction,配合 secrets.SSH_PRIVATE_KEY - 验证是否生效:
ssh -T git@gitlab.internal必须返回 welcome 信息,否则composer install一定会卡在 clone 步骤
多包共用一套 CI 脚本?用 COMPOSER_ROOT_VERSION 和 COMPOSER_DEV_MODE 控制行为
当多个包共用同一套 CI 配置(比如通过 matrix 构建不同 PHP 版本),脚本里不能写死路径或硬编码命令。Composer 提供了两个关键环境变量来解耦:
-
COMPOSER_ROOT_VERSION:设为dev-main或1.2.3,让composer install在无 tag 时也能解析版本约束,避免因版本号缺失导致 require 失败 -
COMPOSER_DEV_MODE=0:在部署到生产环境的 job 中禁用require-dev,但测试 job 必须保持COMPOSER_DEV_MODE=1(否则 PHPUnit 不会被装) - 所有自定义命令应走
composer run-script,而非直接调vendor/bin/phpunit——因为 vendor 路径可能被--no-bin-links改变
最易被忽略的是平台检查:composer check-platform-reqs 必须放在 install 之后、test 之前。多包项目中,某个子包可能要求 ext-gmp,而 CI 镜像默认没装,不提前发现就会在测试阶段才爆错,浪费整条流水线时间。











