Composer脚本中需用system()(传引用$exitCode)或proc_open()(调proc_close()获取)才能捕获命令退出码;exec/shell_exec仅返回stdout,无法获取退出码。

Composer脚本里执行命令后怎么拿到退出码
直接用 exec() 或 shell_exec() 拿不到退出码——它们只返回 stdout。必须用 proc_open() 或 system() + 引用参数才能捕获。
system() 是最简方案,但要注意输出干扰
它会把命令的 stdout 直接输出到页面/终端,不适合静默执行。但好处是退出码能直接拿到:
system('php artisan migrate --pretend', $exitCode);
if ($exitCode !== 0) {
throw new RuntimeException("Migration dry-run failed with code {$exitCode}");
}
- 第二个参数
$exitCode是引用传参,必须是变量,不能是表达式 - 如果命令本身有输出(比如
echo、var_dump),会混在当前脚本输出里,CI 环境可能污染日志 - 不支持超时控制或 stdin 输入,纯同步阻塞
要用 proc_open() 才能完全掌控(推荐)
这是 Composer 脚本中真正可控的方式,尤其适合需要捕获 stdout/stderr + 退出码 + 超时的场景:
$process = proc_open(
'php artisan config:clear',
[
1 => ['pipe', 'w'], // stdout
2 => ['pipe', 'w'], // stderr
],
$pipes
);
if (is_resource($process)) {
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$exitCode = proc_close($process); // 这才是真实退出码
if ($exitCode !== 0) {
echo "Command failed ({$exitCode}): {$stderr}";
}
}
-
proc_close()返回的就是子进程退出码,不是proc_open()的返回值 - 别漏关
$pipes,否则脚本可能卡住(尤其 Windows 下) - Composer 脚本默认运行在
proc_open可用环境中,但某些精简 Docker 镜像(如alpine-php-cli)可能禁用该函数,需检查disable_functions
在 composer.json 的 scripts 里怎么写才可靠
不要在 "scripts" 里直接写带逻辑的 shell 命令。退出码传播不可靠(比如 && 链式执行中前一个失败,后续根本不会跑)。统一走 PHP 回调:
"scripts": {
"post-install-cmd": [
"MyScript::checkConfig"
]
}
对应类方法里用上面的 proc_open() 方式执行,并显式 exit($exitCode) 或抛异常——Composer 会把未捕获异常或非零 exit 视为脚本失败。
最容易被忽略的是:Composer 默认把脚本退出码 1 当作“失败”并中断流程,但某些命令(比如 phpstan 或 psalm)用非 1 的码表示不同错误等级,这时候必须手动判断 $exitCode > 0 而不是只看是否等于 1。


















