ExecLib.php 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. <?php
  2. namespace Libs;
  3. use React\ChildProcess\Process;
  4. use React\Promise\Deferred;
  5. use React\EventLoop\Loop;
  6. final class ExecLib
  7. {
  8. public static function exec(string $cmd, ?float $timeoutSecs = null)
  9. {
  10. $process = new Process($cmd);
  11. $deferred = new Deferred();
  12. $bufferOut = '';
  13. $bufferErr = '';
  14. $timers = [];
  15. $process->start(Loop::get());
  16. if ($process->stdin !== null) {
  17. $process->stdin->end();
  18. }
  19. if ($process->stdout) {
  20. $process->stdout->on('data', function ($chunk) use (&$bufferOut) {
  21. $bufferOut .= (string)$chunk;
  22. });
  23. }
  24. if ($process->stderr) {
  25. $process->stderr->on('data', function ($chunk) use (&$bufferErr) {
  26. $bufferErr .= (string)$chunk;
  27. });
  28. }
  29. if ($timeoutSecs !== null && $timeoutSecs > 0) {
  30. $timers['timeout'] = Loop::addTimer($timeoutSecs, function () use ($process, $deferred) {
  31. if ($process->isRunning()) {
  32. $process->terminate(); // SIGTERM
  33. Loop::addTimer(1.0, function () use ($process) {
  34. if ($process->isRunning()) {
  35. $process->terminate(\defined('SIGKILL') ? \SIGKILL : null);
  36. }
  37. });
  38. }
  39. $deferred->reject(new \RuntimeException('CLI timeout'));
  40. });
  41. }
  42. $process->on('exit', function ($exitCode) use (&$bufferOut, &$bufferErr, $deferred, &$timers) {
  43. foreach ($timers as $t) {
  44. if ($t) {
  45. Loop::cancelTimer($t);
  46. }
  47. }
  48. $output = trim($bufferOut !== '' ? $bufferOut : $bufferErr);
  49. $deferred->resolve([(int)$exitCode, $output]);
  50. });
  51. $process->on('error', function (\Throwable $e) use ($deferred, &$timers) {
  52. foreach ($timers as $t) {
  53. if ($t) {
  54. Loop::cancelTimer($t);
  55. }
  56. }
  57. $deferred->reject($e);
  58. });
  59. return $deferred->promise();
  60. }
  61. }