缓存 vendor/ 目录多数时候是错的,因 composer autoloader 硬编码绝对路径导致 ci 重建后类加载失败;应缓存 ~/.composer/cache/ 并用 composer.lock hash 与 php 版本构建 key。

为什么 actions/cache 缓存 vendor/ 多数时候是错的
直接缓存 vendor/ 目录看似最直觉,但实际会频繁失效甚至引发运行时错误。Composer 的 autoloader 生成路径(如 vendor/composer/autoload_classmap.php)里硬编码了本地绝对路径,CI 环境重建后路径变化,导致类加载失败——错误信息通常是 Class not found 或 require(): failed to open stream。
真正该缓存的是 Composer 自身的下载产物和构建中间态:
-
~/.composer/cache/:含已下载的包 zip、dist tarball、metadata(含哈希校验) -
vendor/不缓存,每次用composer install --no-interaction --prefer-dist重建 - 缓存 key 必须包含
composer.lock的 hash,否则 lock 文件一变,旧缓存就成毒药
正确的 cache key 构建方式:锁定 composer.lock + PHP 版本
key 不光要反映依赖内容,还得绑定 PHP 大版本,因为不同 PHP 版本下 composer install 可能启用不同 dist 分支或跳过不兼容包。
gitHub Actions 中推荐这样写 key:
composer-${{ hashFiles('**/composer.lock') }}-php-${{ matrix.php-version }}
注意点:
-
hashFiles()必须显式指定路径,**/composer.lock能覆盖子目录(如api/composer.lock),但若项目有多个 lock 文件,得手动拆开缓存 - 别用
${{ runner.os }}做 key 后缀——linux/macos 下vendor/结构一致,没必要区分 - 避免用
composer.jsonhash:它不决定实际安装内容,lock才是唯一真相
workflow 中 cache action 的典型位置与参数
actions/cache@v4 必须放在 composer install 之前,且紧挨着 composer install 步骤,中间不能插其他可能修改缓存目录的操作。
关键配置项:
-
path: ~/.composer/cache:这是 Composer 默认缓存路径,不要改成vendor/或./cache -
key按上一节构造,restore-keys可加一个宽松 fallback,比如composer-${{ hashFiles('**/composer.lock') }}-,用于 lock 文件微调后的部分命中 - PHP 必须先 setup(用
shivammathur/setup-php或actions/setup-php),否则~/.composer/cache可能不存在或权限异常
示例片段:
- name: Cache Composer dependencies uses: actions/cache@v4 with: path: ~/.composer/cache key: composer-${{ hashFiles('**/composer.lock') }}-php-${{ matrix.php-version }}
遇到缓存未命中却没报错?检查这三处
缓存“静默失效”比报错更麻烦,常见于:
-
composer.lock文件被 git 忽略或未提交——hashFiles()返回空字符串,所有 key 都变成composer--php-8.2,全项目共用一个缓存,互相污染 - workflow 中用了
composer update:它会改写lock文件,导致后续install的 key 完全不匹配,必须确保 CI 只跑install - PHP 版本 matrix 写成
8.x这种模糊值:matrix.php-version实际解析为8.2.12,但 key 里写的是8.x,hash 对不上
调试技巧:在 cache 步骤后加一行 ls -la ~/.composer/cache/files/,看目录是否真有内容;再加 cat composer.lock | sha256sum 对照 key 中的 hash 值。
缓存逻辑本身简单,但 key 的稳定性、lock 文件的权威性、以及 PHP 环境的一致性,三者缺一不可。少一个,缓存就从加速器变成定时雷。