public function run($callback = null)
{
$this->stdout = '';
$this->stderr = '';
$that = $this;
$out = self::OUT;
$err = self::ERR;
$callback = function ($type, $data) use($that, $callback, $out, $err) {
if ($out == $type) {
$that->addOutput($data);
} else {
$that->addErrorOutput($data);
}
if (null !== $callback) {
call_user_func($callback, $type, $data);
}
};
$descriptors = array(array('pipe', 'r'), array('pipe', 'w'), array('pipe', 'w'));
$process = proc_open($this->commandline, $descriptors, $pipes, $this->cwd, $this->env, $this->options);
if (!is_resource($process)) {
throw new \RuntimeException('Unable to launch a new process.');
}
foreach ($pipes as $pipe) {
stream_set_blocking($pipe, false);
}
$stdinLen = 0;
$stdinOffset = 0;
if (null === $this->stdin) {
fclose($pipes[0]);
$writePipes = null;
} else {
$writePipes = array($pipes[0]);
$stdinLen = strlen($this->stdin);
}
unset($pipes[0]);
while ($pipes || $writePipes) {
$r = $pipes;
$w = $writePipes;
$e = null;
$n = @stream_select($r, $w, $e, $this->timeout);
if (false === $n) {
break;
} elseif ($n === 0) {
proc_terminate($process);
throw new \RuntimeException('The process timed out.');
}
if ($w) {
$written = fwrite($writePipes[0], (string) substr($this->stdin, $stdinOffset), 8192);
if (false !== $written) {
$stdinOffset += $written;
}
if ($stdinOffset >= $stdinLen) {
fclose($writePipes[0]);
$writePipes = null;
}
}
foreach ($r as $pipe) {
$type = array_search($pipe, $pipes);
$data = fread($pipe, 8192);
if (strlen($data) > 0) {
call_user_func($callback, $type == 1 ? $out : $err, $data);
}
if (false === $data || feof($pipe)) {
fclose($pipe);
unset($pipes[$type]);
}
}
}
$this->status = proc_get_status($process);
$time = 0;
while (1 == $this->status['running'] && $time < 1000000) {
$time += 1000;
usleep(1000);
$this->status = proc_get_status($process);
}
$exitCode = proc_close($process);
if ($this->status['signaled']) {
throw new \RuntimeException(sprintf('The process stopped because of a "%s" signal.', $this->status['stopsig']));
}
//The exit code returned by the process (which is only meaningful if
//running is FALSE). Only first call of this function return real
//value, next calls return -1.
return $this->exitcode = $this->status['running'] ? $exitCode : $this->status['exitcode'];
}