不能缓存 vendor/ 目录,因其 autoloader 硬编码本地绝对路径,github actions 每次运行环境路径不同导致 require 失败;应只缓存 ~/.composer/cache,key 用 runner.os-php-版本-composer-lock 哈希组合。

直接缓存 vendor/ 目录在 GitHub Actions 中几乎总是错的,会引发 Class not found 或 require(): failed to open stream 错误;正确做法是只缓存 ~/.composer/cache,并用 composer.lock 哈希 + PHP 版本构建 key。
为什么不能缓存 vendor/ 目录
Composer 生成的 autoloader(比如 vendor/composer/autoload_classmap.php)里硬编码了本地绝对路径。GitHub Actions 每次运行都在全新虚拟机上,路径(如 /home/runner/work/myapp/myapp)必然不同,旧缓存加载后直接报错——不是“没找到类”,而是“路径写死导致 require 失败”。
即使看起来能跑通,只要项目含 post-install-cmd 脚本、platform config 或扩展依赖,就可能因环境差异 silently fail。
正确的 cache key 构建方式
key 必须同时反映两个事实:依赖内容是否变更、PHP 运行时是否兼容。只靠 composer.lock 哈希不够,因为同一 lock 文件在 PHP 8.1 和 8.3 下可能安装不同 dist 分支或跳过不兼容包。
key: ${{ runner.os }}-php-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}-
hashFiles('**/composer.lock')覆盖子目录(如api/composer.lock),但若项目有多个 lock 文件,需拆成独立缓存步骤 - 别用
${{ runner.os }}单独做后缀——Linux/macOS 下~/.composer/cache结构一致,没必要区分 - 绝对不要用
composer.json的哈希,它不决定实际安装结果,composer.lock才是唯一真相
workflow 中 cache action 的位置与参数
actions/cache@v4 必须放在 composer install 之前,且中间不能插入任何可能修改 ~/.composer/cache 的操作(比如提前运行 composer config)。
-
path: ~/.composer/cache—— 这是 Composer 默认缓存路径,别改成./cache或vendor/ -
restore-keys推荐设为:${{ runner.os }}-php-${{ matrix.php-version }}-composer-,用于 lock 文件微调后的部分命中 - 必须先执行
shivammathur/setup-php或actions/setup-php,否则~/.composer/cache可能不存在或权限异常 -
composer install参数推荐:--no-interaction --prefer-dist --optimize-autoloader,避免交互、优先用压缩包、生成高效 autoloader
多 PHP 版本矩阵下的典型配置片段
以下是从真实 CI 工作流中截取的最小可用结构,已剔除无关步骤:
- name: Checkout code uses: actions/checkout@v4
name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} tools: composer
name: Cache Composer dependencies uses: actions/cache@v4 with: path: ~/.composer/cache key: ${{ runner.os }}-php-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: | ${{ runner.os }}-php-${{ matrix.php-version }}-composer-
-
name: Install dependencies run: composer install --no-interaction --prefer-dist --optimize-autoloader
注意:如果项目用 monorepo 结构且含多个
composer.lock,hashFiles('**/composer.lock')会合并所有文件哈希——这通常是你想要的;但若各子包需隔离缓存,就得手动指定路径如hashFiles('packages/foo/composer.lock')并配多个 cache 步骤。











