CI中必须用composer install而非update,因其严格按lock文件安装确保环境一致;应缓存~/.composer/cache而非vendor/;部署时需加--optimize-autoloader和--classmap-authoritative。

composer 在 CI/CD 流水线中不是“装完就跑”,而是必须按规则运行——CI 环境里永远用 composer install,绝不用 composer update;有 composer.lock 才能保证构建可重复;没缓存就等于每次重下几百 MB 依赖。
为什么 CI 中必须用 composer install 而不是 update
因为 composer update 会主动升级依赖版本,哪怕只是 patch 版本(如 monolog/monolog:2.10.0 → 2.10.1),也可能触发未覆盖的边界行为,导致测试通过但线上出错。而 composer install 严格按 composer.lock 安装,锁定全部依赖树,让本地、CI、生产三端完全一致。
- CI 脚本中若误写
composer update,可能悄悄更新 dev 依赖(如 PHPUnit),导致测试套件行为变化 - 建议在 CI 开头加校验:运行
composer validate --no-check-publish确保composer.json和lock文件匹配 - Git 提交前可配置 pre-commit 钩子:
composer install --dry-run检查 lock 是否过期
如何正确缓存 Composer 依赖加速 CI 构建
缓存 vendor/ 目录看似快,实则危险:不同 PHP 版本或扩展差异会导致 opcache 或 apcu 编译产物不兼容,引发随机 autoload 失败。真正安全且高效的缓存目标是 ~/.composer/cache ——它只存下载好的 zip 包和 dist 归档,不包含任何环境敏感内容。
- GitHub Actions 示例(推荐):
- name: Cache Composer packages uses: actions/cache@v3 with: path: ~/.composer/cache key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - GitLab CI 示例:
cache: paths: - ~/.composer/cache/files/ - 切勿缓存
vendor/后跳过composer install:这会让 CI 失去对依赖完整性的最终校验权
CI 中执行测试脚本时怎么调用自定义 scripts
composer.json 里的 "scripts" 不是装饰项,而是 CI 命令标准化的核心。比如你写了 "test": "phpunit --configuration phpunit.xml",CI 就该统一跑 composer test,而不是直接调 phpunit ——这样既解耦了工具路径,又能让所有环境(包括本地开发)共用同一套执行逻辑。
- 支持带参数传递:
composer test -- --filter=TestLogin会透传给 PHPUnit - 避免在 script 中硬编码路径(如
./vendor/bin/phpunit),应依赖 Composer 的 bin 自动发现机制 - 若需区分环境,可用
COMPOSER_DEV_MODE=0控制是否加载require-dev(但通常 CI 应始终启用 dev 依赖)
部署阶段要不要加 --optimize-autoloader 和 --classmap-authoritative
要,而且必须加。尤其在 PHP CLI 环境(如队列任务、定时脚本)或高并发 Web 场景下,不优化自动加载意味着每次请求都要扫描磁盘找类文件,开销巨大。
- 推荐部署命令:
composer install --no-dev --optimize-autoloader --classmap-authoritative --prefer-dist -
--classmap-authoritative表示“类只在 classmap 里找,不再 fallback 到 PSR-4/PSR-0”,大幅提升查找速度,但要求所有类都已收录进 classmap(dump-autoload会自动处理) - 注意:如果项目用了动态类名拼接(如
new $className()且未在 composer.json 的autoload.classmap中显式声明),开启此选项会导致类找不到错误
CI 中最常被忽略的其实是 锁文件的生命周期管理:有人把 composer.lock 当成“生成物”从不提交,有人又把它当成“配置项”频繁手动修改。其实它该是受保护的契约文件——谁改了依赖,谁就要 composer update xxx 并提交新 lock;CI 只负责忠实地执行它。









