composer 的事件监听靠 composer.json 中的 scripts 字段实现,通过严格匹配官方定义的事件名(如 post-install-cmd)触发对应脚本执行,本质是静态脚本执行模型而非运行时事件监听。

Composer 的事件监听靠什么实现?
Composer 本身不提供“插件 SDK”或注册式事件 API,所有生命周期钩子都通过 composer.json 中的 scripts 字段触发。它不是监听器模型,而是脚本执行模型——每个事件名(如 post-install-cmd)对应一个可执行命令列表。
- 脚本本质是 shell 命令或 PHP 函数调用,由 Composer 自己解析并执行
- 所有事件名必须严格匹配官方定义,大小写敏感,拼错就静默失效
- 不支持运行时动态注册,也不支持条件跳过(除非脚本自己判断)
常见错误现象:post-update-cmd 没执行?检查是否写成 post-update 或 post-update-command;或者脚本返回非零退出码导致后续中断。
怎么写一个能被 post-autoload-dump 触发的 PHP 函数?
函数必须满足两个硬性条件:可被自动加载、签名符合 Composer 调用约定(接受 $event 参数)。
<?php // src/ScriptHandler.php
namespace MyVendor;
<p>use Composer\Script\Event;<p>class ScriptHandler
{
public static function onAutoloadDump(Event $event)
{
$vendorDir = $event->getComposer()->getConfig()->get('vendor-dir');
file_put_contents("$vendorDir/../generated-by-composer.txt", "dumped at " . date('c'));
}
}</p>
- 必须声明为
public static,Composer 只认静态方法 - 参数类型提示
Event不是必须的,但强烈建议加,否则 IDE 和类型检查失效 - 方法名任意,但调用时要完整写成
MyVendor\ScriptHandler::onAutoloadDump - 如果类不在 autoload 配置中(如
"psr-4": {"MyVendor\": "src/"}),函数永远不会被找到
哪些事件在 composer install 时实际会触发?
不是所有文档列出的事件都会跑,触发取决于操作上下文和本地状态:
工作流任务拆解与模型编排技能。使用场景:(1) 收到复杂工作任务需要拆解为可执行步骤,(2) 需要为不同步骤选择最合适的模型,(3) 需要跟踪工作流进度和模型使用情况,(4) 长时间任务卡住需要问题诊断和解决方案。
-
pre-install-cmd和post-install-cmd总是触发(只要没加--no-scripts) -
pre-autoload-dump和post-autoload-dump仅当 autoloader 确实被重建时才触发(例如composer.lock变更、PHP 版本变化、autoload配置变动) -
post-root-package-install只在根项目(即你当前composer.json所在项目)安装时触发,不会在依赖包内部触发
容易踩的坑:以为 post-package-install 能监听每个依赖安装,其实它已被弃用且从 Composer 2.0 起完全不触发;替代方案是用 post-autoload-dump + 扫描 vendor/ 目录,但那是权宜之计,不是真正监听。
为什么自定义脚本在 GitHub Actions 里不执行?
最常见原因是 CI 环境默认加了 --no-scripts(尤其使用 composer install --no-interaction 时),或者用了 composer install --no-dev 导致开发专用脚本被跳过。
- 检查 CI 日志里是否出现
Skipping script提示 - 显式启用脚本:改用
composer install --no-interaction(不带--no-scripts) - 若脚本只用于开发,应放在
require-dev下,并确保 CI 安装了 dev 依赖 - 脚本中调用的外部命令(如
php-cs-fixer)需提前安装,不能指望 Composer 自动装完再调
性能影响:每个脚本都是新进程,频繁 IO 或网络请求(比如 post-install 后 curl 推送通知)会拖慢整体安装速度,尤其在 monorepo 场景下要格外注意。
Composer 的事件机制本质是 shell 层的钩子,不是运行时事件总线。它的“监听”是单向、不可取消、无传播链的。想做复杂流程控制(比如拦截 pre-update 并校验依赖许可证),得自己在脚本里实现逻辑,而不是寄希望于某个钩子能阻止后续动作。










