Composer插件在CI中必须按版本分环境执行,因v1与v2插件API互不兼容:激活签名、事件类路径(Composer\Script\Event vs Composer\Plugin\Capability\CommandProvider)、capability机制均不同;单一环境测试无法覆盖跨版本Fatal error或命令漏注册问题。

Composer 插件在 CI 里跑兼容性,不是“装一次就完事”,而是必须按 Composer 版本分环境执行,否则 Plugin X is not compatible with Composer 2 这类错误会直接中断流水线。
为什么不能只在单一 PHP 环境里测插件兼容性
Composer 1 和 2 的插件 API 是互不兼容的:激活方法签名不同、事件类路径变了(Composer\Script\Event vs Composer\Plugin\Capability\CommandProvider)、capability 注册机制在 v1 下根本不存在。CI 里如果只用一个 composer install 命令,它只会按当前 Composer 版本加载对应插件逻辑——你永远不知道 v1 下会不会 fatal error,v2 下会不会漏注册命令。
- CI 默认环境往往固定为最新 Composer,漏掉老项目场景
-
"composer-plugin-api": "^1.0 || ^2.0"在composer.json中是无效声明,v2 会拒绝安装,v1 会解析失败 - 插件类里若提前
new了 v2 特有类(如MyCommandProvider),v1 环境下直接Fatal error: Class 'Composer\Plugin\Capability\CommandProvider' not found
在 GitHub Actions 中分版本测试插件
核心是用 matrix 策略启动两套独立 job,分别指定 Composer 版本,并确保插件代码运行时能安全降级。
- 在
.github/workflows/ci.yml中定义 matrix:strategy: matrix: composer: [1, 2] php: ['7.4', '8.1'] - 每个 job 安装指定 Composer:
- name: Install Composer ${{ matrix.composer }} run: | if [ ${{ matrix.composer }} = "1" ]; then curl -sS https://getcomposer.org/installer | php -- --version=1.10.22 else curl -sS https://getcomposer.org/installer | php fi - 插件主类中必须用
class_exists()判断环境:public function activate(Composer $composer, IOInterface $io) { if (class_exists('Composer\Script\Event')) { // v1 路径:监听旧事件 $composer->getEventDispatcher()->addListener('pre-install-cmd', [$this, 'onPreInstall']); } else { // v2 路径:注册 capability $composer->getPluginManager()->addCapability($this, new MyCommandProvider()); } }
本地验证插件是否真能跨版本运行
别只信 CI 日志,本地快速验证更可靠:用 phpbrew 或 docker run 启两个容器,一个装 Composer 1,一个装 Composer 2,然后手动触发插件激活流程。
- 进 Composer 1 容器后执行:
php -d extension=apcu.so vendor/bin/composer install --no-dev
,观察是否报Class 'Composer\Plugin\Capability\...错误 - 进 Composer 2 容器后执行:
php -d extension=apcu.so vendor/bin/composer list | grep my-command
,确认自定义命令是否出现在输出中 - 所有对
Package::getNames()、Link等内部类的调用,都得包裹class_exists(),否则 v1/v2 切换时必崩
真正难的不是写两套逻辑,而是在 activate 入口之外的所有地方——比如构造函数、静态初始化块、事件回调里——彻底杜绝任何未经检测的 v2 特有类引用。这点一旦漏掉,CI 就会变成“偶发失败”的黑洞。


















