脚本中应避免裸用exit(),而须用return或exit(整数)明确返回状态码:0表示成功并继续后续钩子,1–125为用户错误并中断当前脚本链,128+N表示被信号终止;未捕获异常会转为255码,难以追溯。

脚本里直接 exit() 会破坏 Composer 的状态管理
Composer 在执行 scripts 时会捕获子进程的退出码,并据此判断是否继续后续操作(比如是否运行 post-install-cmd)。用裸 exit(1) 或未处理的异常退出,会导致 Composer 认为整个流程失败,可能跳过你依赖的后续钩子,甚至让 CI 报错但没给出真正原因。
正确做法是:让脚本返回明确的退出码,且不提前终止 Composer 主进程的控制流。Composer 本身不提供「优雅退出」API,但你可以:
- 用
return代替exit()(在 script 函数体内) - 在 CLI 脚本中,用
die($code)或exit($code),但只在逻辑终点使用,且 $code 必须是整数(0 表示成功,非 0 表示失败) - 避免在匿名函数或回调中 throw 未被捕获的 Exception —— Composer 默认不处理它,会转成 255 退出码,难以追溯
PHP 进程如何响应 SIGINT / SIGTERM?
Composer 脚本本质是 PHP CLI 进程,可被系统信号中断。但默认 PHP 不监听信号,需手动注册。常见场景:用户按 Ctrl+C 中断长时间任务(如数据库迁移、文件扫描),你希望清理临时文件、关闭连接再退出。
关键点:
-
pcntl_signal()必须配合pcntl_signal_dispatch()或pcntl_signal(SIG_DFL, ...)使用,否则信号可能被忽略 - CLI 模式下才支持信号处理;Web SAPI(如 Apache)不适用
- 不要在信号处理器里做耗时操作(如写日志文件、发 HTTP 请求),应设标志位,主循环检查后退出
示例片段:
// 在脚本开头注册
$shouldExit = false;
pcntl_signal(SIGINT, function () use (&$shouldExit) {
$shouldExit = true;
});
pcntl_signal(SIGTERM, function () use (&$shouldExit) {
$shouldExit = true;
});
// 主逻辑中定期检查
while ($workRemaining && !$shouldExit) {
doSomeWork();
pcntl_signal_dispatch(); // 必须调用,否则信号不触发
}
Composer scripts 中的错误码含义和兼容性
Composer 依据脚本进程的退出码决定后续行为,不同退出码影响明显:
-
exit(0):一切正常,继续执行后续 script(如post-update-cmd) -
exit(1)到exit(125):视为用户定义错误,Composer 停止当前 script 链,但不会中断整个命令(如composer install仍完成) -
exit(126)–exit(127):权限/命令未找到,Composer 可能报更底层错误 -
exit(128 + N)(如130= 128+2):表示被信号终止(SIGINT 是 2,SIGTERM 是 15),Composer 会记录但通常不特殊处理
注意:Windows 不支持 POSIX 信号,pcntl_* 函数不可用,相关逻辑需用 function_exists('pcntl_signal') 包裹,否则 fatal error。
调试脚本退出行为的实用技巧
实际开发中,最难的是「为什么这个脚本没运行后续钩子?」—— 往往是退出码或异常没被正确捕获。
- 在
composer.json的 script 中加echo "before"; php your-script.php; echo "after",看after是否输出,快速定位是否提前退出 - 用
strace -e trace=exit_group,kill php your-script.php 2>&1 | grep exit(Linux)观察真实退出码 - 在 CI 环境中,把脚本包装成
bash -x your-script.sh,开启 bash 调试模式,看清每一步执行路径 - Composer 本身不打印子脚本的 stderr,记得在 PHP 脚本里用
error_log()写到php://stderr,否则日志消失
信号监听不是必须的,但一旦用了,就必须处理 dispatch 和跨平台兼容;退出码看似简单,却是 Composer 流程控制的唯一契约。别信「应该没问题」,每个 exit 都得有明确意图和对应测试。


















