直接安装 symfony/process 即可,它是独立组件,支持 php 7.2+ 和全平台;用 run() 执行短命令,start() 需配 wait() 处理长任务;建议同时设置 settimeout() 和 setidletimeout();需结合 getexitcode() 与 geterroroutput() 判断真实执行结果。

直接装 symfony/process 就够了,不需要框架或 Bundle
它是一个完全独立的组件,不依赖 Symfony 全栈框架,纯 Composer 包,装完就能在任何 PHP 项目里用(PHP 7.2+,且 proc_open 启用)。
- 执行
composer require symfony/process即可,无其他前置条件 - 不要尝试
composer require symfony/framework-bundle或symfony/console来“配套安装”——多余,还可能引入冲突 - 如果你在旧项目中看到
symfony/console已存在,那只是巧合,symfony/process并不读取它的配置或服务 - Windows、Linux、macOS 全支持,底层自动选
UnixPipes或WindowsPipes,不用手动适配
run() 和 start() 到底该选哪个
这是最常被混淆的操作:前者同步阻塞,后者异步启动。选错会导致进程卡死、超时误判,甚至内存泄漏。
-
run():适合短命令(如ls、php -v、git status),会等命令彻底退出才返回 -
start():必须配合wait()或isRunning()使用,适合长任务(如ffmpeg转码、composer install)、需要实时捕获输出、或要并发跑多个进程 - 错误示范:
$process->start(); echo "done";—— 这里“done”立刻打印,但进程还在后台跑,没人等它结束 - 正确做法:用
wait()带回调实时处理流:$process->wait(function ($type, $buffer) { echo $type === Process::ERR ? "ERR: $buffer" : "OUT: $buffer"; });
超时不是只设 setTimeout() 就万事大吉
只设总超时(setTimeout(30))对“卡住但没退出”的进程无效,比如命令挂起、等待用户输入、或 stdout 停滞——这时得靠空闲超时(idle timeout)。
工作流任务拆解与模型编排技能。使用场景:(1) 收到复杂工作任务需要拆解为可执行步骤,(2) 需要为不同步骤选择最合适的模型,(3) 需要跟踪工作流进度和模型使用情况,(4) 长时间任务卡住需要问题诊断和解决方案。
-
setTimeout(30):从run()开始计时,30 秒后强制 kill -
setIdleTimeout(10):只要连续 10 秒没往 stdout/stderr 写任何内容,就判定为“卡死”,立即终止 - 两者建议同时设:
$process->setTimeout(60)->setIdleTimeout(15),覆盖“慢但稳”和“假死”两类问题 - 注意:
setIdleTimeout()在 Windows 下部分版本支持不稳定,生产环境务必实测
别忽略 getExitCode() 和 getErrorOutput() 的组合判断
仅靠 isSuccessful() 容易漏掉“命令执行了但语义失败”的情况,比如 curl 返回 404、mysql 认证失败、或脚本 exit(1) 但没打错字到 stderr。
-
isSuccessful()只检查退出码是否为 0,不关心输出内容 - 真实场景中,应结合:
if ($process->getExitCode() !== 0 || !empty($process->getErrorOutput())) - 尤其注意:某些命令(如
rsync)成功时也会往 stderr 写日志,不能单凭getErrorOutput()非空就判失败 - 调试技巧:本地开发时加一句
echo "Exit code: {$process->getExitCode()}\nError: {$process->getErrorOutput()}";,比看异常更直接
真正麻烦的从来不是“怎么启动一个进程”,而是“怎么确认它真的按预期结束了,没卡、没假死、没静默失败”。超时策略、退出码与输出的交叉验证、以及 Windows 下 idle timeout 的兼容性,这三点最容易在线上突然出问题。










