Tags: , ,

stdin, stdout, stderr with proc_open in PHP

Want to use PHP as part of your toolchain? It’s a bit painful. If you want to pipe in some data from memory, and read it back into memory (why not! using tmp files is for wimps).

here’s the solution, thanks to richard at 2006 dot atterer dot net

// $command is the command to run, $stdin is your input.  You get back $stdout and $stderr and $returnValue
// you'll probably want to wrap this in a function ;)

$descriptorSpec = array(0 => array("pipe", "r"),
                           1 => array('pipe', 'w'),
                           2 => array('pipe', 'w'));
  $process = proc_open($command, $descriptorSpec, $pipes);
  $txOff = 0; $txLen = strlen($stdin);
  $stdout = ''; $stdoutDone = FALSE;
  $stderr = ''; $stderrDone = FALSE;
  stream_set_blocking($pipes[0], 0); // Make stdin/stdout/stderr non-blocking
  stream_set_blocking($pipes[1], 0);
  stream_set_blocking($pipes[2], 0);
  if ($txLen == 0) fclose($pipes[0]);
  while (TRUE) {
    $rx = array(); // The program's stdout/stderr
    if (!$stdoutDone) $rx[] = $pipes[1];
    if (!$stderrDone) $rx[] = $pipes[2];
    $tx = array(); // The program's stdin
    if ($txOff < $txLen) $tx[] = $pipes[0];
    $ex = NULL;
    stream_select($rx, $tx, $ex, NULL, NULL); // Block til r/w possible
    if (!empty($tx)) {
      $txRet = fwrite($pipes[0], substr($stdin, $txOff, 8192));
      if ($txRet !== FALSE) $txOff += $txRet;
      if ($txOff >= $txLen) fclose($pipes[0]);
    }
    foreach ($rx as $r) {
      if ($r == $pipes[1]) {
        $stdout .= fread($pipes[1], 8192);
        if (feof($pipes[1])) { fclose($pipes[1]); $stdoutDone = TRUE; }
      } else if ($r == $pipes[2]) {
        $stderr .= fread($pipes[2], 8192);
        if (feof($pipes[2])) { fclose($pipes[2]); $stderrDone = TRUE; }
      }
    }
    if (!is_resource($process)) break;
    if ($txOff >= $txLen && $stdoutDone && $stderrDone) break;
  }
  $returnValue = proc_close($process);

I previously had an implementation using `fwrite` and `stream_get_contents` as some other people, and the docs suggested, which works fine for small inputs, but once you get something bigger than ~200k input, it can completely deadlock.

This implementation is better as it reads and writes the data when needed, keeping everything flowing and alive.


2 comments on “stdin, stdout, stderr with proc_open in PHP