插件无法通过声明“composer-plugin-api”: "^1.0 || ^2.0"实现兼容,必须在代码中用class_exists()和method_exists()运行时探测环境并分路径加载逻辑,否则v1会找不到v2类、v2会拒绝v1API。

插件在 Composer 1 和 2 下直接报错怎么办
不能靠 "composer-plugin-api": "^1.0 || ^2.0" 声明“假装兼容”——Composer 自身校验会拒绝安装:v2 看到 v1 的 API 元素就停,v1 又不认识 v2 的 CommandProvider 类。真实错误通常是:Plugin X is not compatible with Composer 2 或 Class 'Composer\Plugin\Capability\CommandProvider' not found。
根本原因是 activate() 签名、事件类路径(Composer\Script\Event vs Composer\Plugin\Capability\*)、插件激活时机全变了。所谓“兼容”,只能靠代码里运行时探测,不是靠声明扛过去。
如何用 class_exists() 分路径加载逻辑
核心动作是:不在构造函数或静态块里 new 任何 v2 特有类,所有敏感引用都先判断存在性。比如在 activate() 入口处:
public function activate(Composer $composer, IOInterface $io) {
if (class_exists('Composer\Script\Event')) {
// Composer 1:监听旧事件
$composer->getEventDispatcher()->addListener('pre-install-cmd', [$this, 'onPreInstall']);
} else {
// Composer 2:注册 capability
$composer->getPluginManager()->addCapability($this, new MyCommandProvider());
}
}
- 必须用
class_exists('Composer\Script\Event'),而不是version_compare(Composer::VERSION, '2.0.0', '>=')——因为 v2 下该类已移除,直接调用会 fatal error - 所有对
Package::getNames()这类已被删/改名的方法,都要提前method_exists($package, 'getNamesWithStability') - 涉及
Composer\Package\Link等内部类的实例化,一律先class_exists()再操作
composer.json 里 platform 和 plugin-api 版本怎么写
composer.json 的 require 段只能声明一个明确的 composer-plugin-api 版本,否则 Composer 安装阶段就会拦截。你得按目标环境选:
- 要支持 PHP 7.2 + Composer 1 的老项目 → 锁死
"composer-plugin-api": "^1.1.0" - 要用 v2 的
activate(Composer $composer, IOInterface $io)新签名 → 必须升到"composer-plugin-api": "^2.2.0" -
config.platform对插件本身无效——它只影响依赖解析,不改变插件运行时能访问的类和方法
也就是说:声明版本是准入门槛,运行时探测才是实际兼容手段。两者缺一不可。
为什么本地测试总漏掉 v1 环境下的致命错误
最容易被忽略的是 autoload 自动加载路径污染:你在 v2 环境下开发,vendor/autoload.php 已加载了 v2 的 Composer 类库,即使你临时切到 v1 环境运行,PHP 的 autoloader 缓存仍可能让 class_exists() 返回 true,掩盖真实缺失。
复盘时务必做到:
- 在干净容器或全新
vendor/目录下分别跑 v1 和 v2 的composer install - 用
php -d extension=... -c /dev/null启动最小 PHP 环境,避免扩展干扰 - 检查
composer show composer/composer输出的版本,确认不是缓存残留
真正的兼容不是“不报错”,而是“在 v1 下不尝试加载 v2 类,在 v2 下不调用 v1 已废弃方法”——这个边界,只有靠每行 class_exists() 和 method_exists() 守住。


















