github actions 必须同时缓存 composer 全局缓存目录和 vendor 目录,因依赖安装分下载解压(全局缓存)与软链接复制(vendor)两阶段;仅缓存其一将导致重复耗时操作。

GitHub Actions 默认不缓存 vendor 目录和 Composer 的全局缓存(COMPOSER_CACHE_DIR),每次运行都会重新下载依赖,导致 PHP 项目 CI 时间普遍偏长。正确配置缓存能将安装时间从 2–5 分钟压到 20–40 秒,但直接套用文档示例容易因路径、key 冲突或权限问题失效。
为什么 actions/cache 要分两层缓存
Composer 依赖安装实际包含两个独立环节:一是下载并解压包到全局缓存(默认 ~/.composer/cache),二是将所需版本软链接/复制进项目 vendor/。只缓存其中一者,另一者仍要重复执行——比如只缓 vendor,下次 composer install 仍会重下所有 zip;只缓全局 cache,vendor 重建又慢。
所以必须同时缓存:
-
~/.composer/cache(Linux/macOS)或%USERPROFILE%\AppData\Roaming\Composer\Cache(Windows) -
vendor目录(注意:不能缓存整个项目根目录,否则可能污染composer.lock或生成文件)
composer install 前必须加 composer clear-cache 吗
不需要,反而有害。CI 环境是干净的 runner,不存在本地残留缓存污染问题;且 clear-cache 会清空刚 restore 进来的缓存,让后续 install 变成全量下载。
真正要确保的是:
- 使用
--no-interaction --prefer-dist --optimize-autoloader参数,避免交互卡住、强制走压缩包安装、生成高效 autoloader - 在
composer install前设置COMPOSER_CACHE_DIR环境变量,与缓存 action 中指定的路径严格一致 - 检查
composer.json和composer.lock是否都纳入缓存 key 计算(否则 lock 更新后仍命中旧缓存)
缓存 key 写错的三个高频坑
缓存失效最常见原因不是配置语法错,而是 key 设计没覆盖真实变化维度:
工作流任务拆解与模型编排技能。使用场景:(1) 收到复杂工作任务需要拆解为可执行步骤,(2) 需要为不同步骤选择最合适的模型,(3) 需要跟踪工作流进度和模型使用情况,(4) 长时间任务卡住需要问题诊断和解决方案。
- 只用
composer.lockhash,但没包含 PHP 版本 —— 某些包(如ext-protobuf)编译结果随 PHP minor 版本变,导致 vendor 内二进制不兼容 - key 中硬编码了
ubuntu-latest,但 workflow 实际跑在ubuntu-22.04上,缓存完全不命中 - 用
hashFiles('**/composer.lock')却忘了composer.json也影响依赖解析(例如config.platform修改会影响可安装版本)
推荐 key 模板:composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}-${{ hashFiles('**/composer.json') }}-php-${{ matrix.php-version }}
Windows runner 上缓存总失败?检查路径权限和斜杠
GitHub Actions Windows runner 默认以 NT AUTHORITY\SYSTEM 权限运行,对 %USERPROFILE% 下某些子目录(如 AppData\Roaming)写入受限,导致 actions/cache restore 失败或 composer install 写缓存报错。
解决方法只有两个有效选项:
- 改用 Linux runner(更稳定,PHP 生态 CI 主流选择)
- 若必须用 Windows,则显式设置缓存路径为项目内可写目录:
COMPOSER_CACHE_DIR: ${{ github.workspace }}/composer-cache
并在 cache action 中对应改为缓存该路径
注意:Windows 路径分隔符必须用 /(不是 \),YAML 中反斜杠会被转义,actions/cache 无法识别 C:\foo 类路径。
缓存不是开箱即用的开关,关键在于理解 Composer 两阶段安装机制,并让 key 精确反映「什么变了就该失效」——比如 PHP 版本、lock 文件、平台配置、OS 类型,缺一不可。漏掉任意一个,都可能让缓存「成功命中却构建出错」,这种问题在线上环境极难排查。










