post-install-cmd仅在composer.lock不存在或过期(依赖变更未同步)时触发,反复运行composer install且lock完整匹配则不执行;需严格拼写、避免--no-scripts,并可改用post-autoload-dump确保每次触发。

Composer 安装包时不会自动执行任何脚本,除非你明确配置了 post-install-cmd 或 post-update-cmd 事件,并且触发条件满足。
为什么 post-install-cmd 有时不执行
它只在 composer.lock 不存在、或已存在但过期(即依赖声明变更未同步到 lock)时才触发。如果你反复运行 composer install 而 lock 文件完整且匹配 composer.json,该钩子根本不会跑。
- 常见错误现象:
composer install后脚本静默跳过,但手动composer run-script post-install-cmd却能成功——大概率是 lock 文件“太新”了 - CI/CD 环境中常加
--no-scripts参数,这会直接禁用所有脚本,包括post-*钩子 - 脚本路径基于项目根目录解析,但若用 symlink 或 Docker volume 挂载,
getcwd()可能不是你预期的路径,导致./bin/init.php找不到 - 拼写必须严格为
post-install-cmd(全小写、短横线),写成PostInstall、post_install_cmd、post-install都无效
怎么让脚本“每次 install 都执行”
靠单一 post-install-cmd 无法保证每次运行,得组合策略:
工作流任务拆解与模型编排技能。使用场景:(1) 收到复杂工作任务需要拆解为可执行步骤,(2) 需要为不同步骤选择最合适的模型,(3) 需要跟踪工作流进度和模型使用情况,(4) 长时间任务卡住需要问题诊断和解决方案。
- 同时注册
post-install-cmd和post-update-cmd,覆盖两种主流安装场景 - 更稳定的选择是改用
post-autoload-dump:只要 autoloader 重建(install、update、甚至dump-autoload)就触发,时机稍晚但更可靠 - 如果脚本只是初始化配置或生成文件,也可在 PHP 入口处加懒加载逻辑(如检查
config.php是否存在,不存在则调用vendor/your-package/bin/init.php),避开 Composer 生命周期限制
脚本值写法与执行安全边界
scripts 是声明式钩子注册机制,不是任意代码执行器。它的值只能是三类之一:
- 字符串命令:
"php vendor/acme/tool/bin/setup.php"—— 注意引号必须存在,否则 JSON 解析失败 - 命令数组:
["npm ci", "php artisan migrate --force"]—— 按顺序执行,任一失败则中断后续 - 类方法调用:
["Acme\Installer", "run"]—— 要求该类已通过autoload加载,且方法为public static - 所有脚本都在项目根目录下执行,但不自动
chdir()到那里;如果脚本里用了相对路径,务必先chdir(__DIR__)或用getcwd()显式判断
第三方包如何“自带”安装后逻辑
包作者不能强制项目执行自己的初始化脚本。Composer 默认禁止自动运行第三方任意代码,这是安全底线。
- 包可提供标准入口(如
src/Installer.php中的静态方法),并在文档里明确要求用户在项目composer.json中手动注册:"post-install-cmd": ["Vendor\Package\Installer::onInstall"] - 更进一步的做法是发布一个 Composer 插件(
composer-plugin类型包),通过PluginInterface在安装阶段介入,但这需要用户显式 require 插件并启用,且插件本身也要经过审核 - 别指望
extra字段能绕过限制——它只是元数据容器,不触发执行
真正容易被忽略的点在于:脚本是否执行,不取决于你写了什么,而取决于谁在什么条件下调用了哪个 Composer 命令。锁文件状态、CI 参数、autoload 加载顺序、路径解析上下文——这些细节比语法本身更容易导致静默失败。










