必须缓存 ~/.composer/cache 而非 vendor/,因 vendor 中 autoload 文件硬编码绝对路径,ci 每次路径不同会导致 class not found;~/.composer/cache 存 zip 包与元数据,与环境无关,命中率近 100%。

缓存路径必须是 ~/.composer/cache,不是 vendor/
缓存 vendor/ 看似直接,但会导致 Class not found 或 failed to open stream 错误——因为 autoload 文件硬编码了构建时的绝对路径,而 GitHub Actions 每次 runner 的路径都不同。真正该缓存的是 Composer 下载和解压产物,即 ~/.composer/cache 目录(默认位置)。这个目录只存 zip 包和元数据,与 PHP 版本、OS、路径无关,只要 composer.lock 不变,命中率接近 100%。
-
~/.composer/cache/files是实际存放下载包的子目录,actions/cache的path必须精确指定到这一层或其父级~/.composer/cache - 缓存 key 必须包含
hashFiles('**/composer.lock'),否则 lock 文件一改,旧缓存仍被错误复用 - 别用
composer config --global cache-dir改路径——CI 容器生命周期短,配置写进去也读不到;靠环境变量COMPOSER_CACHE_DIR更不可靠,actions/cache默认就认~/.composer/cache
--repository-url 必须显式传参,config -g 几乎无效
GitHub Actions 每次都是全新容器,composer config -g 写入的 ~/.composer/config.json 要么为空,要么权限受限,根本不会被实际请求读取。所谓“全局配置生效”在 CI 里基本是幻觉。唯一可靠方式是把镜像地址直接塞进 composer install 命令行:
- 正确写法:
composer install --repository-url=https://mirrors.aliyun.com/composer/ --no-interaction --prefer-dist --optimize-autoloader - URL 末尾斜杠不能省:
https://mirrors.aliyun.com/composer/✅,https://mirrors.aliyun.com/composer❌(部分 Composer 2.2+ 版本会静默 fallback 回 packagist.org) -
--repository-url=xxx中等号后不能有空格,--repository=https://是无效参数名
验证是否真走镜像:看 -vvv 日志里的 GET 请求域名
别信 composer diagnose 或 composer config -g repo.packagist 的输出——它们只读配置文件,不验证实际网络请求。真正判断依据只有 composer install -vvv 的日志:
- 出现
GET https://mirrors.aliyun.com/composer/p2/xxx/xxx.json才算成功走镜像 - 如果日志里全是
GET https://repo.packagist.org/或GET https://api.github.com/,说明镜像没生效,或composer.lock里还记着旧源地址 - 临时补救无效:改完镜像后,必须删掉
composer.lock和vendor/,再跑composer install(不是update),否则 lock 文件继续指挥 Composer 去 github.com 拉包
缓存 + 镜像 + 参数三者缺一不可
只配镜像不缓存,每次仍要重下 zip;只缓存不换源,缓存拉不到包;漏掉关键参数,缓存可能被跳过或降级为 git clone。CI 构建里这三件事必须同时做:
- 缓存步骤必须放在
setup-php之后、composer install之前 -
composer install必须带:--no-interaction(避免卡住)、--prefer-dist(禁用 git clone)、--optimize-autoloader(生成 classmap)、--no-dev(跳过 dev 依赖) - PHP 版本建议固定 minor 版本(如
8.2),避免 patch 升级导致缓存 key 不匹配
最易被忽略的是 composer.lock 文件本身——它必须提交到仓库,且内容必须反映新镜像源;本地没提交、CI 拉到空 lock 文件,缓存 key 就永远是空字符串,缓存形同虚设。











