Composer插件生效需同时满足三点:声明"type": "composer-plugin"、主类实现PluginInterface、autoload正确映射;漏任一条件则静默跳过,无报错提示。

你的插件没被 Composer 加载,大概率不是代码写错了,而是 composer.json 没声明对 "type" 或主类没实现 PluginInterface——Composer 根本不会扫描它。
插件不生效?先检查 composer.json 的三个硬性字段
Composer 只加载同时满足以下三点的包:声明 "type": "composer-plugin"、主类实现 ComposerPluginPluginInterface、autoload 映射能正确定位到该类。漏掉任意一个,就静默跳过,无报错、无日志、无提示。
-
"type": "composer-plugin"必须是字符串字面量,不能是composer_plugin或大小写混用(如Composer-Plugin) -
"require": {"composer-plugin-api": "^2.0"}必须匹配你本地 Composer 版本(2.x 项目不能写^1.0) -
"autoload": {"psr-4": {"MyPlugin\": "src/"}}中的命名空间末尾反斜杠不能漏,且src/Plugin.php里必须写namespace MyPlugin;,否则自动加载失败 - 显式声明
"extra": {"class": "MyPlugin\Plugin"}不再强制,但建议加上,避免 autoload 自动发现失效
监听 post-install-cmd 总不触发?别监听脚本事件
post-install-cmd 是脚本事件(script event),插件无法直接监听它。你真正该用的是插件事件(plugin event),比如 PostInstallEvent,它在 vendor 写入完成、autoload.php 生成前触发。
- 监听方式必须是:
$composer->getEventDispatcher()->addListener('post-install-cmd', [$this, 'onPostInstall'])—— 注意事件名是字符串'post-install-cmd',不是常量ScriptEvents::POST_INSTALL_CMD - 回调函数签名必须严格为:
public function onPostInstall(CommandEvent $event),类型提示缺一不可 - 想在安装后修改自动加载逻辑?改用
PostAutoloadDumpEvent,此时$event->getComposer()->getAutoloadConfig()已可读写 - 不要试图在插件里响应用户自定义的
"scripts"(如"post-install-cmd": "php generate.php"),那是根项目composer.json的职责
读取 extra 配置总是 null?链式取值 + 空判断是刚需
$composer->getPackage()->getExtra() 返回的是根项目 composer.json 里的 extra 字段,但它可能为 null,且嵌套结构需手动展开。子包里的 extra 不会透传给父项目插件。
- 正确写法:
$extra = $composer->getPackage()->getExtra() ?? [];,再取$extra['my-plugin']['enabled'] ?? false - 所有键访问前都得做存在性判断,
$extra['my-plugin'] ?? []比直接$extra['my-plugin']['enabled']安全得多 - 别把路径、密钥等敏感逻辑硬编码进插件,优先走
extra或环境变量(如getenv('MY_PLUGIN_OUTPUT_DIR')) - 插件运行时工作目录不可靠,
getcwd()和硬编码./composer.json都会出错;路径一律通过$composer->getConfig()->get('vendor-dir')或$composer->getPackage()->getAutoload()获取
调试时事件不触发?注意触发时机和缓存机制
post-autoload-dump 这类事件只在实际生成或更新自动加载文件时触发。如果 vendor/autoload.php 已存在且无变更,Composer 默认跳过该事件,导致你误以为监听失效。
- 开发阶段加
--no-cache -v强制刷新并查看完整日志:composer dump-autoload --no-cache -v -
activate()只在插件首次加载时调用一次,不适合放业务逻辑;真正干活的代码必须塞进事件回调里 - 别在
activate()里调用dump-autoload或触发重安装——这会引发递归执行,报Maximum function nesting level - 所有事件监听器共享同一个
$composer实例和内存状态,不能假设每次命令都是干净上下文
最易忽略的一点:插件不是“注册即生效”,而是依赖 Composer 的自动加载和事件分发机制协同工作;任何环节断链(autoload 失败、事件名拼错、参数类型不对),都会导致静默失效,且没有 fallback 提示。











