Update to Drupal 8.1.0. For more information, see https://www.drupal.org/drupal-8.1.0-release-notes

This commit is contained in:
Pantheon Automation 2016-04-20 09:56:34 -07:00 committed by Greg Anderson
parent b11a755ba8
commit c0a0d5a94c
6920 changed files with 64395 additions and 57312 deletions

View file

@ -28,10 +28,11 @@ class ProcessFailedException extends RuntimeException
throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
}
$error = sprintf('The command "%s" failed.'."\nExit Code: %s(%s)",
$error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
$process->getCommandLine(),
$process->getExitCode(),
$process->getExitCodeText()
$process->getExitCodeText(),
$process->getWorkingDirectory()
);
if (!$process->isOutputDisabled()) {

View file

@ -1,4 +1,4 @@
Copyright (c) 2004-2015 Fabien Potencier
Copyright (c) 2004-2016 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -35,14 +35,17 @@ class PhpExecutableFinder
*/
public function find($includeArgs = true)
{
$args = $this->findArguments();
$args = $includeArgs && $args ? ' '.implode(' ', $args) : '';
// HHVM support
if (defined('HHVM_VERSION')) {
return (getenv('PHP_BINARY') ?: PHP_BINARY).($includeArgs ? ' '.implode(' ', $this->findArguments()) : '');
return (getenv('PHP_BINARY') ?: PHP_BINARY).$args;
}
// PHP_BINARY return the current sapi executable
if (defined('PHP_BINARY') && PHP_BINARY && in_array(PHP_SAPI, array('cli', 'cli-server')) && is_file(PHP_BINARY)) {
return PHP_BINARY;
if (defined('PHP_BINARY') && PHP_BINARY && in_array(PHP_SAPI, array('cli', 'cli-server', 'phpdbg')) && is_file(PHP_BINARY)) {
return PHP_BINARY.$args;
}
if ($php = getenv('PHP_PATH')) {
@ -76,9 +79,10 @@ class PhpExecutableFinder
{
$arguments = array();
// HHVM support
if (defined('HHVM_VERSION')) {
$arguments[] = '--php';
} elseif ('phpdbg' === PHP_SAPI) {
$arguments[] = '-qrr';
}
return $arguments;

View file

@ -39,6 +39,19 @@ class PhpProcess extends Process
if (false === $php = $executableFinder->find()) {
$php = null;
}
if ('phpdbg' === PHP_SAPI) {
$file = tempnam(sys_get_temp_dir(), 'dbg');
file_put_contents($file, $script);
register_shutdown_function('unlink', $file);
$php .= ' '.ProcessUtils::escapeArgument($file);
$script = null;
}
if ('\\' !== DIRECTORY_SEPARATOR && null !== $php) {
// exec is mandatory to deal with sending a signal to the process
// see https://github.com/symfony/symfony/issues/5030 about prepending
// command with exec
$php = 'exec '.$php;
}
parent::__construct($php, $cwd, $env, $script, $timeout, $options);
}

View file

@ -29,6 +29,17 @@ abstract class AbstractPipes implements PipesInterface
/** @var bool */
private $blocked = true;
public function __construct($input)
{
if (is_resource($input)) {
$this->input = $input;
} elseif (is_string($input)) {
$this->inputBuffer = $input;
} else {
$this->inputBuffer = (string) $input;
}
}
/**
* {@inheritdoc}
*/
@ -71,4 +82,64 @@ abstract class AbstractPipes implements PipesInterface
$this->blocked = false;
}
/**
* Writes input to stdin.
*/
protected function write()
{
if (!isset($this->pipes[0])) {
return;
}
$e = array();
$r = null !== $this->input ? array($this->input) : $e;
$w = array($this->pipes[0]);
// let's have a look if something changed in streams
if (false === $n = @stream_select($r, $w, $e, 0, 0)) {
return;
}
foreach ($w as $stdin) {
if (isset($this->inputBuffer[0])) {
$written = fwrite($stdin, $this->inputBuffer);
$this->inputBuffer = substr($this->inputBuffer, $written);
if (isset($this->inputBuffer[0])) {
return array($this->pipes[0]);
}
}
foreach ($r as $input) {
for (;;) {
$data = fread($input, self::CHUNK_SIZE);
if (!isset($data[0])) {
break;
}
$written = fwrite($stdin, $data);
$data = substr($data, $written);
if (isset($data[0])) {
$this->inputBuffer = $data;
return array($this->pipes[0]);
}
}
if (!isset($data[0]) && feof($input)) {
// no more data to read on input resource
// use an empty buffer in the next reads
$this->input = null;
}
}
}
// no input to read on resource, buffer is empty
if (null === $this->input && !isset($this->inputBuffer[0])) {
fclose($this->pipes[0]);
unset($this->pipes[0]);
}
if (!$w) {
return array($this->pipes[0]);
}
}
}

View file

@ -35,11 +35,7 @@ class UnixPipes extends AbstractPipes
$this->ptyMode = (bool) $ptyMode;
$this->disableOutput = (bool) $disableOutput;
if (is_resource($input)) {
$this->input = $input;
} else {
$this->inputBuffer = (string) $input;
}
parent::__construct($input);
}
public function __destruct()
@ -98,36 +94,15 @@ class UnixPipes extends AbstractPipes
*/
public function readAndWrite($blocking, $close = false)
{
// only stdin is left open, job has been done !
// we can now close it
if (1 === count($this->pipes) && array(0) === array_keys($this->pipes)) {
fclose($this->pipes[0]);
unset($this->pipes[0]);
}
if (empty($this->pipes)) {
return array();
}
$this->unblock();
$w = $this->write();
$read = array();
if (null !== $this->input) {
// if input is a resource, let's add it to stream_select argument to
// fill a buffer
$r = array_merge($this->pipes, array('input' => $this->input));
} else {
$r = $this->pipes;
}
// discard read on stdin
$read = $e = array();
$r = $this->pipes;
unset($r[0]);
$w = isset($this->pipes[0]) ? array($this->pipes[0]) : null;
$e = null;
// let's have a look if something changed in streams
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
if (($r || $w) && false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
// if a system call has been interrupted, forget about it, let's try again
// otherwise, an error occurred, let's reset pipes
if (!$this->hasSystemCallBeenInterrupted()) {
@ -137,55 +112,24 @@ class UnixPipes extends AbstractPipes
return $read;
}
// nothing has changed
if (0 === $n) {
return $read;
}
foreach ($r as $pipe) {
// prior PHP 5.4 the array passed to stream_select is modified and
// lose key association, we have to find back the key
$type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input';
$data = '';
while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) {
$data .= $dataread;
$read[$type = array_search($pipe, $this->pipes, true)] = '';
do {
$data = fread($pipe, self::CHUNK_SIZE);
$read[$type] .= $data;
} while (isset($data[0]));
if (!isset($read[$type][0])) {
unset($read[$type]);
}
if ('' !== $data) {
if ($type === 'input') {
$this->inputBuffer .= $data;
} else {
$read[$type] = $data;
}
if ($close && feof($pipe)) {
fclose($pipe);
unset($this->pipes[$type]);
}
if (false === $data || (true === $close && feof($pipe) && '' === $data)) {
if ($type === 'input') {
// no more data to read on input resource
// use an empty buffer in the next reads
$this->input = null;
} else {
fclose($this->pipes[$type]);
unset($this->pipes[$type]);
}
}
}
if (null !== $w && 0 < count($w)) {
while (strlen($this->inputBuffer)) {
$written = fwrite($w[0], $this->inputBuffer, 2 << 18); // write 512k
if ($written > 0) {
$this->inputBuffer = (string) substr($this->inputBuffer, $written);
} else {
break;
}
}
}
// no input to read on resource, buffer is empty and stdin still open
if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) {
fclose($this->pipes[0]);
unset($this->pipes[0]);
}
return $read;

View file

@ -52,17 +52,13 @@ class WindowsPipes extends AbstractPipes
Process::STDERR => tempnam(sys_get_temp_dir(), 'err_sf_proc'),
);
foreach ($this->files as $offset => $file) {
if (false === $file || false === $this->fileHandles[$offset] = fopen($file, 'rb')) {
if (false === $file || false === $this->fileHandles[$offset] = @fopen($file, 'rb')) {
throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable');
}
}
}
if (is_resource($input)) {
$this->input = $input;
} else {
$this->inputBuffer = $input;
}
parent::__construct($input);
}
public function __destruct()
@ -109,28 +105,26 @@ class WindowsPipes extends AbstractPipes
*/
public function readAndWrite($blocking, $close = false)
{
$this->write($blocking, $close);
$this->unblock();
$w = $this->write();
$read = $r = $e = array();
$read = array();
$fh = $this->fileHandles;
foreach ($fh as $type => $fileHandle) {
if (0 !== fseek($fileHandle, $this->readBytes[$type])) {
continue;
if ($blocking) {
if ($w) {
@stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);
} elseif ($this->fileHandles) {
usleep(Process::TIMEOUT_PRECISION * 1E6);
}
$data = '';
$dataread = null;
while (!feof($fileHandle)) {
if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) {
$data .= $dataread;
}
}
if (0 < $length = strlen($data)) {
$this->readBytes[$type] += $length;
}
foreach ($this->fileHandles as $type => $fileHandle) {
$data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]);
if (isset($data[0])) {
$this->readBytes[$type] += strlen($data);
$read[$type] = $data;
}
if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) {
fclose($this->fileHandles[$type]);
if ($close) {
fclose($fileHandle);
unset($this->fileHandles[$type]);
}
}
@ -143,7 +137,7 @@ class WindowsPipes extends AbstractPipes
*/
public function areOpen()
{
return (bool) $this->pipes && (bool) $this->fileHandles;
return $this->pipes && $this->fileHandles;
}
/**
@ -183,71 +177,4 @@ class WindowsPipes extends AbstractPipes
}
$this->files = array();
}
/**
* Writes input to stdin.
*
* @param bool $blocking
* @param bool $close
*/
private function write($blocking, $close)
{
if (empty($this->pipes)) {
return;
}
$this->unblock();
$r = null !== $this->input ? array('input' => $this->input) : null;
$w = isset($this->pipes[0]) ? array($this->pipes[0]) : null;
$e = null;
// let's have a look if something changed in streams
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
// if a system call has been interrupted, forget about it, let's try again
// otherwise, an error occurred, let's reset pipes
if (!$this->hasSystemCallBeenInterrupted()) {
$this->pipes = array();
}
return;
}
// nothing has changed
if (0 === $n) {
return;
}
if (null !== $w && 0 < count($r)) {
$data = '';
while ($dataread = fread($r['input'], self::CHUNK_SIZE)) {
$data .= $dataread;
}
$this->inputBuffer .= $data;
if (false === $data || (true === $close && feof($r['input']) && '' === $data)) {
// no more data to read on input resource
// use an empty buffer in the next reads
$this->input = null;
}
}
if (null !== $w && 0 < count($w)) {
while (strlen($this->inputBuffer)) {
$written = fwrite($w[0], $this->inputBuffer, 2 << 18);
if ($written > 0) {
$this->inputBuffer = (string) substr($this->inputBuffer, $written);
} else {
break;
}
}
}
// no input to read on resource, buffer is empty and stdin still open
if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) {
fclose($this->pipes[0]);
unset($this->pipes[0]);
}
}
}

View file

@ -54,7 +54,7 @@ class Process
private $idleTimeout;
private $options;
private $exitcode;
private $fallbackExitcode;
private $fallbackStatus = array();
private $processInformation;
private $outputDisabled = false;
private $stdout;
@ -169,8 +169,7 @@ class Process
public function __destruct()
{
// stop() will check if we have a process running.
$this->stop();
$this->stop(0);
}
public function __clone()
@ -219,7 +218,7 @@ class Process
*/
public function mustRun($callback = null)
{
if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) {
if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
}
@ -241,9 +240,6 @@ class Process
* The callback receives the type of output (out or err) and some bytes from
* the output in real-time while writing the standard input to the process.
* It allows to have feedback from the independent process during execution.
* If there is no callback passed, the wait() method can be called
* with true as a second parameter then the callback will get all data occurred
* in (and since) the start call.
*
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
@ -278,6 +274,17 @@ class Process
if (!isset($this->options['bypass_shell'])) {
$this->options['bypass_shell'] = true;
}
} elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
$descriptors[3] = array('pipe', 'w');
// See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
$commandline = '{ ('.$this->commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
$commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
// Workaround for the bug, when PTS functionality is enabled.
// @see : https://bugs.php.net/69442
$ptsWorkaround = fopen(__FILE__, 'r');
}
$this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options);
@ -287,6 +294,10 @@ class Process
}
$this->status = self::STATUS_STARTED;
if (isset($descriptors[3])) {
$this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
}
if ($this->tty) {
return;
}
@ -349,8 +360,7 @@ class Process
do {
$this->checkTimeout();
$running = '\\' === DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
$close = '\\' !== DIRECTORY_SEPARATOR || !$running;
$this->readPipes(true, $close);
$this->readPipes($running, '\\' !== DIRECTORY_SEPARATOR || !$running);
} while ($running);
while ($this->isRunning()) {
@ -368,17 +378,9 @@ class Process
* Returns the Pid (process identifier), if applicable.
*
* @return int|null The process id if running, null otherwise
*
* @throws RuntimeException In case --enable-sigchild is activated
*/
public function getPid()
{
if ($this->isSigchildEnabled()) {
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.');
}
$this->updateStatus(false);
return $this->isRunning() ? $this->processInformation['pid'] : null;
}
@ -390,7 +392,7 @@ class Process
* @return Process
*
* @throws LogicException In case the process is not running
* @throws RuntimeException In case --enable-sigchild is activated
* @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
* @throws RuntimeException In case of failure
*/
public function signal($signal)
@ -460,15 +462,13 @@ class Process
*/
public function getOutput()
{
if ($this->outputDisabled) {
throw new LogicException('Output has been disabled.');
$this->readPipesForOutput(__FUNCTION__);
if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
return '';
}
$this->requireProcessIsStarted(__FUNCTION__);
$this->readPipes(false, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true);
return $this->stdout;
return $ret;
}
/**
@ -484,18 +484,15 @@ class Process
*/
public function getIncrementalOutput()
{
$this->requireProcessIsStarted(__FUNCTION__);
$this->readPipesForOutput(__FUNCTION__);
$data = $this->getOutput();
$latest = substr($data, $this->incrementalOutputOffset);
$latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
$this->incrementalOutputOffset = ftell($this->stdout);
if (false === $latest) {
return '';
}
$this->incrementalOutputOffset = strlen($data);
return $latest;
}
@ -506,7 +503,8 @@ class Process
*/
public function clearOutput()
{
$this->stdout = '';
ftruncate($this->stdout, 0);
fseek($this->stdout, 0);
$this->incrementalOutputOffset = 0;
return $this;
@ -522,15 +520,13 @@ class Process
*/
public function getErrorOutput()
{
if ($this->outputDisabled) {
throw new LogicException('Output has been disabled.');
$this->readPipesForOutput(__FUNCTION__);
if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
return '';
}
$this->requireProcessIsStarted(__FUNCTION__);
$this->readPipes(false, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true);
return $this->stderr;
return $ret;
}
/**
@ -547,18 +543,15 @@ class Process
*/
public function getIncrementalErrorOutput()
{
$this->requireProcessIsStarted(__FUNCTION__);
$this->readPipesForOutput(__FUNCTION__);
$data = $this->getErrorOutput();
$latest = substr($data, $this->incrementalErrorOutputOffset);
$latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
$this->incrementalErrorOutputOffset = ftell($this->stderr);
if (false === $latest) {
return '';
}
$this->incrementalErrorOutputOffset = strlen($data);
return $latest;
}
@ -569,7 +562,8 @@ class Process
*/
public function clearErrorOutput()
{
$this->stderr = '';
ftruncate($this->stderr, 0);
fseek($this->stderr, 0);
$this->incrementalErrorOutputOffset = 0;
return $this;
@ -584,7 +578,7 @@ class Process
*/
public function getExitCode()
{
if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) {
if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
}
@ -601,8 +595,6 @@ class Process
*
* @return null|string A string representation for the exit status code, null if the Process is not terminated.
*
* @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
*
* @see http://tldp.org/LDP/abs/html/exitcodes.html
* @see http://en.wikipedia.org/wiki/Unix_signal
*/
@ -639,12 +631,10 @@ class Process
{
$this->requireProcessIsTerminated(__FUNCTION__);
if ($this->isSigchildEnabled()) {
if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
}
$this->updateStatus(false);
return $this->processInformation['signaled'];
}
@ -662,12 +652,10 @@ class Process
{
$this->requireProcessIsTerminated(__FUNCTION__);
if ($this->isSigchildEnabled()) {
if ($this->isSigchildEnabled() && (!$this->enhanceSigchildCompatibility || -1 === $this->processInformation['termsig'])) {
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
}
$this->updateStatus(false);
return $this->processInformation['termsig'];
}
@ -684,8 +672,6 @@ class Process
{
$this->requireProcessIsTerminated(__FUNCTION__);
$this->updateStatus(false);
return $this->processInformation['stopped'];
}
@ -702,8 +688,6 @@ class Process
{
$this->requireProcessIsTerminated(__FUNCTION__);
$this->updateStatus(false);
return $this->processInformation['stopsig'];
}
@ -763,41 +747,33 @@ class Process
* Stops the process.
*
* @param int|float $timeout The timeout in seconds
* @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL
* @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
*
* @return int The exit-code of the process
*
* @throws RuntimeException if the process got signaled
*/
public function stop($timeout = 10, $signal = null)
{
$timeoutMicro = microtime(true) + $timeout;
if ($this->isRunning()) {
if ('\\' === DIRECTORY_SEPARATOR && !$this->isSigchildEnabled()) {
exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode);
if ($exitCode > 0) {
throw new RuntimeException('Unable to kill the process');
}
}
// given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here
$this->doSignal(15, false);
do {
usleep(1000);
} while ($this->isRunning() && microtime(true) < $timeoutMicro);
if ($this->isRunning() && !$this->isSigchildEnabled()) {
if (null !== $signal || defined('SIGKILL')) {
// avoid exception here :
// process is supposed to be running, but it might have stop
// just after this line.
// in any case, let's silently discard the error, we can not do anything
$this->doSignal($signal ?: SIGKILL, false);
}
if ($this->isRunning()) {
// Avoid exception here: process is supposed to be running, but it might have stopped just
// after this line. In any case, let's silently discard the error, we cannot do anything.
$this->doSignal($signal ?: 9, false);
}
}
$this->updateStatus(false);
if ($this->processInformation['running']) {
if ($this->isRunning()) {
if (isset($this->fallbackStatus['pid'])) {
unset($this->fallbackStatus['pid']);
return $this->stop(0, $signal);
}
$this->close();
}
@ -807,23 +783,33 @@ class Process
/**
* Adds a line to the STDOUT stream.
*
* @internal
*
* @param string $line The line to append
*/
public function addOutput($line)
{
$this->lastOutputTime = microtime(true);
$this->stdout .= $line;
fseek($this->stdout, 0, SEEK_END);
fwrite($this->stdout, $line);
fseek($this->stdout, $this->incrementalOutputOffset);
}
/**
* Adds a line to the STDERR stream.
*
* @internal
*
* @param string $line The line to append
*/
public function addErrorOutput($line)
{
$this->lastOutputTime = microtime(true);
$this->stderr .= $line;
fseek($this->stderr, 0, SEEK_END);
fwrite($this->stderr, $line);
fseek($this->stderr, $this->incrementalErrorOutputOffset);
}
/**
@ -1228,14 +1214,7 @@ class Process
return $result = false;
}
$proc = @proc_open('echo 1', array(array('pty'), array('pty'), array('pty')), $pipes);
if (is_resource($proc)) {
proc_close($proc);
return $result = true;
}
return $result = false;
return $result = (bool) @proc_open('echo 1', array(array('pty'), array('pty'), array('pty')), $pipes);
}
/**
@ -1250,16 +1229,8 @@ class Process
} else {
$this->processPipes = UnixPipes::create($this, $this->input);
}
$descriptors = $this->processPipes->getDescriptors($this->outputDisabled);
if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
$descriptors = array_merge($descriptors, array(array('pipe', 'w')));
$this->commandline = '('.$this->commandline.') 3>/dev/null; code=$?; echo $code >&3; exit $code';
}
return $descriptors;
return $this->processPipes->getDescriptors();
}
/**
@ -1303,11 +1274,15 @@ class Process
}
$this->processInformation = proc_get_status($this->process);
$this->captureExitCode();
$running = $this->processInformation['running'];
$this->readPipes($blocking, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true);
$this->readPipes($running && $blocking, '\\' !== DIRECTORY_SEPARATOR || !$running);
if (!$this->processInformation['running']) {
if ($this->fallbackStatus && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
$this->processInformation = $this->fallbackStatus + $this->processInformation;
}
if (!$running) {
$this->close();
}
}
@ -1323,7 +1298,7 @@ class Process
return self::$sigchild;
}
if (!function_exists('phpinfo')) {
if (!function_exists('phpinfo') || defined('HHVM_VERSION')) {
return self::$sigchild = false;
}
@ -1333,6 +1308,24 @@ class Process
return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
}
/**
* Reads pipes for the freshest output.
*
* @param $caller The name of the method that needs fresh outputs
*
* @throws LogicException in case output has been disabled or process is not started
*/
private function readPipesForOutput($caller)
{
if ($this->outputDisabled) {
throw new LogicException('Output has been disabled.');
}
$this->requireProcessIsStarted($caller);
$this->updateStatus(false);
}
/**
* Validates and returns the filtered timeout.
*
@ -1367,24 +1360,14 @@ class Process
$callback = $this->callback;
foreach ($result as $type => $data) {
if (3 == $type) {
$this->fallbackExitcode = (int) $data;
} else {
if (3 !== $type) {
$callback($type === self::STDOUT ? self::OUT : self::ERR, $data);
} elseif (!isset($this->fallbackStatus['signaled'])) {
$this->fallbackStatus['exitcode'] = (int) $data;
}
}
}
/**
* Captures the exitcode if mentioned in the process information.
*/
private function captureExitCode()
{
if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) {
$this->exitcode = $this->processInformation['exitcode'];
}
}
/**
* Closes process resource, closes file handles, sets the exitcode.
*
@ -1394,21 +1377,26 @@ class Process
{
$this->processPipes->close();
if (is_resource($this->process)) {
$exitcode = proc_close($this->process);
} else {
$exitcode = -1;
proc_close($this->process);
}
$this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1);
$this->exitcode = $this->processInformation['exitcode'];
$this->status = self::STATUS_TERMINATED;
if (-1 === $this->exitcode && null !== $this->fallbackExitcode) {
$this->exitcode = $this->fallbackExitcode;
} elseif (-1 === $this->exitcode && $this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
// if process has been signaled, no exitcode but a valid termsig, apply Unix convention
$this->exitcode = 128 + $this->processInformation['termsig'];
if (-1 === $this->exitcode) {
if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
// if process has been signaled, no exitcode but a valid termsig, apply Unix convention
$this->exitcode = 128 + $this->processInformation['termsig'];
} elseif ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
$this->processInformation['signaled'] = true;
$this->processInformation['termsig'] = -1;
}
}
// Free memory from self-reference callback created by buildCallback
// Doing so in other contexts like __destruct or by garbage collector is ineffective
// Now pipes are closed, so the callback is no longer necessary
$this->callback = null;
return $this->exitcode;
}
@ -1420,10 +1408,10 @@ class Process
$this->starttime = null;
$this->callback = null;
$this->exitcode = null;
$this->fallbackExitcode = null;
$this->fallbackStatus = array();
$this->processInformation = null;
$this->stdout = null;
$this->stderr = null;
$this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+');
$this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+');
$this->process = null;
$this->latestSignal = null;
$this->status = self::STATUS_READY;
@ -1440,12 +1428,12 @@ class Process
* @return bool True if the signal was sent successfully, false otherwise
*
* @throws LogicException In case the process is not running
* @throws RuntimeException In case --enable-sigchild is activated
* @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
* @throws RuntimeException In case of failure
*/
private function doSignal($signal, $throwException)
{
if (!$this->isRunning()) {
if (null === $pid = $this->getPid()) {
if ($throwException) {
throw new LogicException('Can not send signal on a non running process.');
}
@ -1453,23 +1441,36 @@ class Process
return false;
}
if ($this->isSigchildEnabled()) {
if ($throwException) {
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
}
if ('\\' === DIRECTORY_SEPARATOR) {
exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
if ($exitCode && $this->isRunning()) {
if ($throwException) {
throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
}
return false;
return false;
}
} else {
if (!$this->enhanceSigchildCompatibility || !$this->isSigchildEnabled()) {
$ok = @proc_terminate($this->process, $signal);
} elseif (function_exists('posix_kill')) {
$ok = @posix_kill($pid, $signal);
} elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), array(2 => array('pipe', 'w')), $pipes)) {
$ok = false === fgets($pipes[2]);
}
if (!$ok) {
if ($throwException) {
throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal));
}
return false;
}
}
if (true !== @proc_terminate($this->process, $signal)) {
if ($throwException) {
throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal));
}
return false;
}
$this->latestSignal = $signal;
$this->latestSignal = (int) $signal;
$this->fallbackStatus['signaled'] = true;
$this->fallbackStatus['exitcode'] = -1;
$this->fallbackStatus['termsig'] = $this->latestSignal;
return true;
}

View file

@ -1,65 +1,13 @@
Process Component
=================
Process executes commands in sub-processes.
In this example, we run a simple directory listing and get the result back:
```php
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
$process = new Process('ls -lsa');
$process->setTimeout(3600);
$process->run();
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
print $process->getOutput();
```
You can think that this is easy to achieve with plain PHP but it's not especially
if you want to take care of the subtle differences between the different platforms.
You can simplify the code by using `mustRun()` instead of `run()`, which will
throw a `ProcessFailedException` automatically in case of a problem:
```php
use Symfony\Component\Process\Process;
$process = new Process('ls -lsa');
$process->setTimeout(3600);
$process->mustRun();
print $process->getOutput();
```
And if you want to be able to get some feedback in real-time, just pass an
anonymous function to the ``run()`` method and you will get the output buffer
as it becomes available:
```php
use Symfony\Component\Process\Process;
$process = new Process('ls -lsa');
$process->run(function ($type, $buffer) {
if (Process::ERR === $type) {
echo 'ERR > '.$buffer;
} else {
echo 'OUT > '.$buffer;
}
});
```
That's great if you want to execute a long running command (like rsync-ing files to a
remote server) and give feedback to the user in real-time.
The Process component executes commands in sub-processes.
Resources
---------
You can run the unit tests with the following command:
$ cd path/to/Symfony/Component/Process/
$ composer install
$ phpunit
* [Documentation](https://symfony.com/doc/current/components/process.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

View file

@ -19,12 +19,15 @@
"php": ">=5.3.9"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Process\\": "" }
"psr-4": { "Symfony\\Component\\Process\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
"dev-master": "2.8-dev"
}
}
}

View file

@ -21,6 +21,7 @@
<directory>./</directory>
<exclude>
<directory>./Tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>