关键在于缓存是否真正命中,需用hashFiles('**/composer.lock')生成键、匹配vendor路径、区分dev/prod依赖,并知悉缓存按runner隔离。

GitHub Actions 中缓存 Composer 依赖,关键不是「能不能缓存」,而是「缓存是否真正命中」——多数失败源于 composer.lock 文件未参与缓存键计算,或 PHP 版本/平台配置变动后缓存未失效。
缓存键必须包含 composer.lock 的哈希值
仅用 PHP 版本或 composer install 命令作为缓存键,会导致 lock 文件变更后仍复用旧缓存,安装结果不一致。GitHub 官方 actions/cache 不自动感知 lock 文件变化,需手动构造键。
- 推荐用
hashFiles('**/composer.lock')生成稳定键,它在 lock 文件内容不变时输出相同哈希 - 若项目含多个
composer.lock(如 monorepo),需明确路径,例如hashFiles('packages/*/composer.lock') - 避免用
hashFiles('composer.json')—— JSON 文件不含依赖精确版本,缓存可能命中但装错包
缓存路径要匹配 Composer 的实际安装位置
Composer 默认将包解压到 vendor/,但缓存路径必须与 composer install 运行时的 --working-dir 和 COMPOSER_VENDOR_DIR 一致,否则缓存写入/读取错位。
- 标准 PHP 项目:缓存路径设为
vendor(相对工作目录) - 若自定义了
COMPOSER_VENDOR_DIR,缓存路径必须完全一致,例如./.vendor - 不要缓存整个
vendor/的父目录(如.),会拖慢哈希计算且增加冲突概率
区分开发依赖与生产依赖,按需缓存
--no-dev 和 --with-all-dependencies 等标志会改变 vendor/ 内容,但默认缓存键不体现这些参数,导致缓存污染。
- 在缓存键中显式加入标志标识,例如:
composer-${{ hashFiles('**/composer.lock') }}-prod对应--no-dev - CI 测试阶段需要
phpunit等 dev 依赖,而部署阶段不需要——建议分两个 job,各自独立缓存键 - 慎用
--ignore-platform-reqs:它绕过 PHP 扩展检查,缓存若混用不同平台配置,可能导致运行时报错
name: PHP CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache Composer dependencies
uses: actions/cache@v4
with:
path: vendor
key: composer-${{ hashFiles('**/composer.lock') }}-php-${{ matrix.php-version }}
- name: Install dependencies
run: composer install --no-progress --prefer-dist
env:
COMPOSER_NO_INTERACTION: 1
- name: Run tests
run: vendor/bin/phpunit
strategy:
matrix:
php-version: ['8.2', '8.3']
最易被忽略的一点:GitHub Actions 的缓存是「按 runner 实例隔离」的,同一缓存键在不同 runner 上首次未命中时,会各自生成一份缓存副本。这意味着你无法通过缓存强制统一所有构建环境的 vendor 内容——它只是加速,不是一致性保障。真要确保一致,请始终以 composer.lock 为唯一事实源,并在 CI 中校验 composer validate --strict。










