* This file is part of the Symfony package.
* (c) Fabien Potencier <fabien@symfony.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\InvalidArgumentException;
use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Pipes\PipesInterface;
use Symfony\Component\Process\Pipes\UnixPipes;
use Symfony\Component\Process\Pipes\WindowsPipes;
* Process is a thin wrapper around proc_* functions to easily
* start independent PHP processes.
* @author Fabien Potencier <fabien@symfony.com>
* @author Romain Neutron <imprec@gmail.com>
class Process implements \IteratorAggregate
const STATUS_READY = 'ready';
const STATUS_STARTED = 'started';
const STATUS_TERMINATED = 'terminated';
// Timeout Precision in seconds.
const TIMEOUT_PRECISION = 0.2;
const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory
const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating
const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating
private $hasCallback = false;
private $options = ['suppress_errors' => true];
private $fallbackStatus = [];
private $processInformation;
private $outputDisabled = false;
private $enhanceWindowsCompatibility = true;
private $enhanceSigchildCompatibility;
private $status = self::STATUS_READY;
private $incrementalOutputOffset = 0;
private $incrementalErrorOutputOffset = 0;
private $inheritEnv = false;
private $useFileHandles = false;
/** @var PipesInterface */
private static $sigchild;
* Exit codes translation table.
* User-defined errors must use exit codes in the 64-113 range.
public static $exitCodes = [
2 => 'Misuse of shell builtins',
126 => 'Invoked command cannot execute',
127 => 'Command not found',
128 => 'Invalid exit argument',
131 => 'Quit and dump core',
132 => 'Illegal instruction',
133 => 'Trace/breakpoint trap',
134 => 'Process aborted',
135 => 'Bus error: "access to undefined portion of memory object"',
136 => 'Floating point exception: "erroneous arithmetic operation"',
137 => 'Kill (terminate immediately)',
139 => 'Segmentation violation',
141 => 'Write to pipe with no one reading',
142 => 'Signal raised by alarm',
143 => 'Termination (request to terminate)',
145 => 'Child process terminated, stopped (or continued*)',
146 => 'Continue if stopped',
147 => 'Stop executing temporarily',
148 => 'Terminal stop signal',
149 => 'Background process attempting to read from tty ("in")',
150 => 'Background process attempting to write to tty ("out")',
151 => 'Urgent data available on socket',
152 => 'CPU time limit exceeded',
153 => 'File size limit exceeded',
154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
155 => 'Profiling timer expired',
* @param string|array $commandline The command line to run
* @param string|null $cwd The working directory or null to use the working dir of the current PHP process
* @param array|null $env The environment variables or null to use the same environment as the current PHP process
* @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
* @param int|float|null $timeout The timeout in seconds or null to disable
* @param array $options An array of options for proc_open
* @throws RuntimeException When proc_open is not installed
public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = null)
if (!\function_exists('proc_open')) {
throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
$this->commandline = $commandline;
// on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
// on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
// @see : https://bugs.php.net/51800
// @see : https://bugs.php.net/50524
if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) {
$this->setTimeout($timeout);
$this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR;
$this->enhanceSigchildCompatibility = '\\' !== \DIRECTORY_SEPARATOR && $this->isSigchildEnabled();
@trigger_error(sprintf('The $options parameter of the %s constructor is deprecated since Symfony 3.3 and will be removed in 4.0.', __CLASS__), \E_USER_DEPRECATED);
$this->options = array_replace($this->options, $options);
public function __destruct()
public function __clone()
$this->resetProcessData();
* The callback receives the type of output (out or err) and
* some bytes from the output in real-time. It allows to have feedback
* from the independent process during execution.
* The STDOUT and STDERR are also available after the process is finished
* via the getOutput() and getErrorOutput() methods.
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
* @return int The exit status code
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process stopped after receiving signal
* @throws LogicException In case a callback is provided and output has been disabled
* @final since version 3.3
public function run($callback = null/*, array $env = []*/)
$env = 1 < \func_num_args() ? func_get_arg(1) : null;
$this->start($callback, $env);
* This is identical to run() except that an exception is thrown if the process
* exits with a non-zero exit code.
* @throws RuntimeException if PHP was compiled with --enable-sigchild and the enhanced sigchild compatibility mode is not enabled
* @throws ProcessFailedException if the process didn't terminate successfully
* @final since version 3.3
public function mustRun(callable $callback = null/*, array $env = []*/)
if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
$env = 1 < \func_num_args() ? func_get_arg(1) : null;
if (0 !== $this->run($callback, $env)) {
throw new ProcessFailedException($this);
* Starts the process and returns after writing the input to STDIN.
* This method blocks until all STDIN data is sent to the process then it
* returns while the process runs in the background.
* The termination of the process can be awaited with wait().
* 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.
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
* @throws LogicException In case a callback is provided and output has been disabled
public function start(callable $callback = null/*, array $env = [*/)
if ($this->isRunning()) {
throw new RuntimeException('Process is already running.');
if (2 <= \func_num_args()) {
if (__CLASS__ !== static::class) {
$r = new \ReflectionMethod($this, __FUNCTION__);
if (__CLASS__ !== $r->getDeclaringClass()->getName() && (2 > $r->getNumberOfParameters() || 'env' !== $r->getParameters()[1]->name)) {
@trigger_error(sprintf('The %s::start() method expects a second "$env" argument since Symfony 3.3. It will be made mandatory in 4.0.', static::class), \E_USER_DEPRECATED);
$this->resetProcessData();
$this->starttime = $this->lastOutputTime = microtime(true);
$this->callback = $this->buildCallback($callback);
$this->hasCallback = null !== $callback;
$descriptors = $this->getDescriptors();
$inheritEnv = $this->inheritEnv;
if (\is_array($commandline = $this->commandline)) {
$commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline));
if ('\\' !== \DIRECTORY_SEPARATOR) {
// exec is mandatory to deal with sending a signal to the process
$commandline = 'exec '.$commandline;
if (null !== $env && $inheritEnv) {
$env += $this->getDefaultEnv();
} elseif (null !== $env) {
@trigger_error('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.', \E_USER_DEPRECATED);
$env = $this->getDefaultEnv();
if ('\\' === \DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) {
$this->options['bypass_shell'] = true;
$commandline = $this->prepareWindowsCommandLine($commandline, $env);
} 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] = ['pipe', 'w'];
// See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
$commandline = '{ ('.$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');
if (\defined('HHVM_VERSION')) {
foreach ($env as $k => $v) {
if (!is_dir($this->cwd)) {
@trigger_error('The provided cwd does not exist. Command is currently ran against getcwd(). This behavior is deprecated since Symfony 3.4 and will be removed in 4.0.', \E_USER_DEPRECATED);
$this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);
if (!\is_resource($this->process)) {
throw new RuntimeException('Unable to launch a new process.');
$this->status = self::STATUS_STARTED;
if (isset($descriptors[3])) {
$this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
$this->updateStatus(false);
* Be warned that the process is cloned before being started.
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
* @final since version 3.3
public function restart(callable $callback = null/*, array $env = []*/)
if ($this->isRunning()) {
throw new RuntimeException('Process is already running.');
$env = 1 < \func_num_args() ? func_get_arg(1) : null;
$process->start($callback, $env);
* Waits for the process to terminate.
* 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.
* @param callable|null $callback A valid PHP callback
* @return int The exitcode of the process
* @throws RuntimeException When process timed out
* @throws RuntimeException When process stopped after receiving signal
* @throws LogicException When process is not yet started
public function wait(callable $callback = null)
$this->requireProcessIsStarted(__FUNCTION__);
$this->updateStatus(false);
if (null !== $callback) {
if (!$this->processPipes->haveReadSupport()) {
throw new \LogicException('Pass the callback to the Process::start method or enableOutput to use a callback with Process::wait.');
$this->callback = $this->buildCallback($callback);
$running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
$this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
while ($this->isRunning()) {
if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
* Returns the Pid (process identifier), if applicable.
* @return int|null The process id if running, null otherwise
return $this->isRunning() ? $this->processInformation['pid'] : null;
* Sends a POSIX signal to the process.
* @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
* @throws LogicException In case the process is not running
* @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)
$this->doSignal($signal, true);
* Disables fetching output and error output from the underlying process.
* @throws RuntimeException In case the process is already running
* @throws LogicException if an idle timeout is set
public function disableOutput()
if ($this->isRunning()) {
throw new RuntimeException('Disabling output while the process is running is not possible.');
if (null !== $this->idleTimeout) {
throw new LogicException('Output can not be disabled while an idle timeout is set.');
$this->outputDisabled = true;
* Enables fetching output and error output from the underlying process.
* @throws RuntimeException In case the process is already running
public function enableOutput()
if ($this->isRunning()) {
throw new RuntimeException('Enabling output while the process is running is not possible.');
$this->outputDisabled = false;