必须缓存 ~/.composer/cache 而非 vendor/,因后者会导致 autoloader 错乱、class not found 等问题;key 需绑定 php minor 版本与 composer.lock 哈希,且需满足 --prefer-dist、存在 composer.lock、同 uid 三个前提。

GitLab CI 中不能靠缓存 vendor 目录来“加速”,必须缓存 ~/.composer/cache,且 key 必须绑定 PHP 版本与 composer.lock 内容哈希——否则缓存形同虚设,甚至引发 Class not found。
为什么缓存 vendor/ 是危险操作
很多团队误以为把 vendor/ 打包进 GitLab cache 能省时间,结果在不同 PHP minor 版本(如 8.2.10 → 8.2.12)或不同 composer.lock 下复用,直接导致:
-
Class 'Monolog\Logger' not found—— autoloader classmap 错乱 - 测试通过、部署失败 —— 因符号链接或大小写探测被跳过
- CI 流水线行为漂移 —— 同一 commit 在不同 runner 上表现不一致
根本原因:Composer 的 autoload 机制(尤其 --classmap-authoritative)强依赖 PHP 版本、扩展可用性、文件系统语义。缓存 vendor/ 等于绕过这些约束。
正确缓存路径与 key 构建方式
只缓存 Composer 自身的下载缓存目录:~/.composer/cache,它包含 zip 包、元数据和 VCS 克隆副本,复用安全且收益明确。
key 必须同时满足两个条件:
- 固定 PHP minor 版本(例如
php:8.2-cli),避免 patch 升级触发不兼容 - 使用
hashFiles('**/composer.lock')生成内容指纹,而非时间戳或分支名
GitLab CI 示例片段:
cache:
key: ${CI_JOB_NAME}-${PHP_VERSION}-composer-${CI_COMMIT_REF_SLUG}-${CI_PIPELINE_ID}
paths:
- ~/.composer/cache
before_script:
- composer config --global repo.packagist composer https://mirrors.aliyun.com/composer/
- composer self-update --2
注意:${PHP_VERSION} 需在 job variables 中显式定义为 8.2(非 8.2.12),否则每次 patch 升级都会使缓存失效。
缓存生效的三个硬性前提
即使配置了 cache 块,若以下任一条件不满足,缓存将无法命中或被跳过:
-
composer install必须带--no-interaction --prefer-dist --optimize-autoloader—— 缺少--prefer-dist会退化为源码安装,绕过缓存包 -
composer.lock必须已提交到仓库根目录 —— 没有它,install会自动 fallback 成update,并拒绝使用缓存 - 所有作业必须运行在相同用户 UID 下 ——
~/.composer/cache权限为755,跨 UID 会导致写入失败或拒绝读取
常见现象是日志中出现 Cache not found for input keys,但实际是因上述某条未满足,导致缓存从未写入。
清理旧缓存与防止磁盘爆满
Composer 默认 cache-files-maxsize 为 300MiB,但在高频 CI 场景下极易超限。建议在 before_script 中主动触发垃圾回收:
- composer clear-cache --gc
或在本地全局配置中固化策略(影响所有 CI job):
composer config --global cache-files-maxsize "500MiB" composer config --global cache-files-ttl "2592000"
注意:--gc 不清空全部缓存,只删过期/超限项,不影响后续构建速度;而 clear-cache 全量清空,应仅用于调试或修复损坏缓存。
多包项目(monorepo)中,每个子目录若独立运行 composer install,需确保它们共享同一 ~/.composer/cache,否则重复下载无法避免——这要求所有 job 使用相同 base image 和 user context。











