Composer 不支持 post-package-uninstall 钩子,它既未文档化也不被源码识别;可用的替代钩子仅有 pre-install-cmd、pre-update-cmd 和 post-autoload-dump。

post-package-uninstall 钩子根本不存在
Composer 官方不支持 post-package-uninstall 这个钩子。它既不在文档中,也不在源码里被识别——你配了也不会执行,连报错都不会有,静默失效。
常见错误现象是:在 composer.json 里写了 "post-package-uninstall" 脚本,卸载包后发现什么都没发生;或者误以为 post-install-cmd / post-update-cmd 能捕获单个包的卸载动作,结果完全对不上。
真正能用的、且与“卸载”行为最接近的,只有两个钩子:
-
pre-install-cmd和pre-update-cmd:它们在安装/更新前触发,可用来做清理准备(比如备份旧配置) -
post-autoload-dump:在 autoloader 重建后触发,适合做依赖关系感知的收尾(比如移除已不存在的类缓存)
如何模拟“卸载后清理”的实际效果
核心思路是:不等卸载发生,而是在下次 install/update 前,检查哪些包已被移出 composer.json,再执行对应清理逻辑。
使用场景典型如:某 SDK 包卸载后,需删掉它生成的 config/sdk.php 或清空它的缓存目录。
实操建议如下:
- 把清理逻辑写进
pre-update-cmd脚本,用composer show --installed --no-ansi获取当前已安装包列表 - 同时读取原始
composer.json的require和require-dev,比对哪些包“曾存在但现已缺失” - 注意兼容性:Composer 2.2+ 的
show输出格式稳定,但早期版本字段顺序可能不同,建议用--format=json解析 - 别依赖
composer.lock的packages字段做判断——它包含所有曾经装过的包(包括已卸载但未 clean 的),容易误判
为什么不能监听 vendor 目录删除事件
有人想用 inotify 或文件系统监听 vendor/xxx/package 删除动作来触发脚本,这在技术上可行,但完全不可靠。
原因很实在:
- Composer 卸载时不一定立刻删目录:如果用了
--no-scripts或COMPOSER_NO_INTERACTION=1,vendor可能残留 - 用户可能手动
rm -rf vendor/xxx/package,绕过 Composer 流程,此时钩子毫无意义 - CI/CD 环境中 vendor 常被缓存复用,目录变更和依赖状态严重脱节
- PHP 脚本无法跨平台监听文件删除(Windows 下 inotify 不可用,fswatch 又非标配)
推荐的最小可行方案
与其强求“卸载后立刻执行”,不如接受 Composer 的声明式本质:把清理看作“状态同步”的一部分,而非“事件响应”。
一个轻量、可维护的做法是:
- 写一个独立命令
php artisan package:cleanup(或纯 PHP 脚本),负责扫描vendor/和composer.json差异并清理 - 把它注册为
pre-update-cmd和post-autoload-dump两个钩子,覆盖 install/update/uninstall 全流程 - 关键参数差异:不要在脚本里硬编码包名,改用
get_declared_classes()或scandir(vendor)动态发现,避免漏掉别名或分支包 - 性能影响极小:一次
scandir+ 两次json_decode,耗时通常低于 20ms
复杂点在于:你得自己定义“某个包卸载后该清理什么”,Composer 不知道你的 config 文件、缓存路径或数据库迁移状态。这部分逻辑没法自动化,必须手写,也最容易被忽略。











