CI必须用composer install而非update,以确保依赖版本锁定;应缓存~/.composer/cache而非vendor/;CI脚本需用composer run调用scripts定义的命令;生产部署须加--no-dev --optimize-autoloader --classmap-authoritative及check-platform-reqs。

CI里必须用composer install,不是update
因为composer update会主动升级依赖版本,哪怕只是monolog/monolog:2.10.0 → 2.10.1这种patch更新,也可能触发未覆盖的边界行为——测试全过,线上500。
composer install才真正按composer.lock安装,锁定整棵树,让本地、CI、生产三端完全一致。
- CI脚本中一旦误写
composer update,可能悄悄更新require-dev里的phpunit,导致测试套件行为漂移 - 建议在CI开头加校验:
composer validate --no-check-publish,确保composer.json和lock匹配 - Git提交前可用
pre-commit钩子跑composer install --dry-run,提前发现lock是否过期
缓存~/.composer/cache,别碰vendor/
缓存vendor/看似快,实则危险:不同PHP版本或扩展(比如opcache、apcu)会导致autoload编译产物不兼容,引发随机类找不到、Class not found等诡异问题。
真正安全高效的缓存目标是~/.composer/cache——它只存下载好的zip包和dist归档,不带任何环境敏感内容,跨项目、跨PHP版本都能复用。
- GitHub Actions示例:
path: ~/.composer/cache,key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - GitLab CI示例:
cache: paths: - ~/.composer/cache/files/ - 切勿缓存
vendor/后跳过composer install——这会让CI失去对依赖完整性的最终校验权
用composer run调脚本,别硬写vendor/bin/...
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依赖)
生产部署要加--no-dev --optimize-autoloader --classmap-authoritative
生产环境不是“装完就行”,而是要明确裁剪和优化:
-
--no-dev:跳过require-dev里的测试、分析工具,减小体积、规避风险 -
--optimize-autoloader:生成更紧凑的autoload_classmap.php,提升类加载性能 -
--classmap-authoritative:告诉Autoloader“类只在classmap里找”,彻底跳过文件系统扫描,进一步提速 - 部署前务必加
composer check-platform-reqs,确认PHP版本和扩展满足composer.json里的platform约束
这些参数不是可选项,是生产环境的底线配置。漏掉任何一个,都可能让部署后的应用变慢、出错,或者悄悄引入dev依赖污染线上环境。










