插件不生效的主因是 composer.json 未声明 "type": "composer-plugin" 或主类未实现 ComposerPluginPluginInterface 接口;必须同时满足类型声明、接口实现(含 activate/deactivate)、正确 autoload 映射及依赖 composer-plugin-api 版本匹配。

你的插件没被加载,大概率不是代码写错了,而是 composer.json 没声明对类型或没实现接口——Composer 根本不会扫描它。
插件不生效?先检查 type 和接口实现
Composer 只加载满足两个硬性条件的包:"type": "composer-plugin" + 类实现了 ComposerPluginPluginInterface。漏掉任意一个,插件就静默失效,连错误提示都没有。
-
composer.json中必须显式写"type": "composer-plugin",不能靠目录名或命名约定 - 主类必须实现
PluginInterface,且包含activate()和deactivate()两个方法(哪怕空着) - 别用匿名类;命名空间和
autoload映射必须匹配,比如"psr-4": {"MyPlugin\": "src/"}对应src/Plugin.php里的namespace MyPlugin; - 依赖项里得有
"composer-plugin-api": "^2.0"(对应 Composer 2.x),版本不匹配会导致类找不到或方法不存在
事件监听总不触发?确认监听的是 Plugin Event 而非 Script Event
post-install-cmd 是脚本事件(script event),插件无法直接监听它;你真正该监听的是 PostInstallEvent 或 ScriptEvents::POST_INSTALL_CMD(后者需通过 EventSubscriberInterface 绑定)。
- 想在安装完成后执行逻辑,用
PostInstallEvent(属于ComposerPluginEvent),它在 vendor 写入完成、autoload.php 生成前触发 - 想响应用户定义的
scripts(如"post-install-cmd": "php generate.php"),插件做不到——那是根项目composer.json的职责,插件只能监听 Composer 自身生命周期事件 - 监听器必须在
activate()里注册:$composer->getEventDispatcher()->addListener('post-install-cmd', [$this, 'onPostInstall']),拼错事件名(比如写成postinstall-cmd)或大小写不对,都不会调用 - 回调函数签名要严格:接收一个
CommandEvent或其子类实例,不能少参数,也不能加默认值
读取 extra 配置总是 null?记得链式取值并做空判断
$composer->getPackage()->getExtra() 返回的是根项目 composer.json 里的 extra 字段,但它可能为 null,且字段嵌套需手动展开。
- 正确写法:
$extra = $composer->getPackage()->getExtra() ?? [];,再取$extra['my-plugin']['enabled'] ?? false - extra 只存在于根项目中;如果插件被其他包依赖,子包里的
extra不会透传给父项目的插件 - 不要在
activate()里直接依赖未定义的 extra 键,否则composer install可能因Fatal error中断 - 敏感配置(如路径、密钥)优先走环境变量,extra 仅作开关或轻量参数
本地测试插件总失败?别用 composer require 直接路径
直接 composer require ../my-plugin 容易因 autoload 未及时刷新或路径解析错误导致类找不到,推荐用 path 仓库方式引入。
- 在测试项目的
composer.json中加repositories:
{
"repositories": [
{
"type": "path",
"url": "../my-composer-plugin"
}
]
}
composer require my-vendor/my-composer-plugin(注意是包名,不是路径)composer dump-autoload,否则新类不生效-v 参数查看详细日志:composer install -v,能看清插件是否被加载、事件是否注册成功最常被忽略的一点:所有事件监听器共享同一个 $composer 实例内存状态,不要假设每次命令都是干净上下文——比如缓存了某个包列表,下次 update 时可能还是旧数据。











