class Process

Process is a thin wrapper around proc_* functions to ease start independent PHP processes.

@author Fabien Potencier <fabien@symfony.com>

@api

Hierarchy

  • class \Symfony\Component\Process\Process

Expanded class hierarchy of Process

5 files declare their use of Process
AbstractProcessTest.php in drupal/core/vendor/symfony/process/Symfony/Component/Process/Tests/AbstractProcessTest.php
FilterException.php in drupal/core/vendor/kriswallsmith/assetic/src/Assetic/Exception/FilterException.php
ProcessFailedException.php in drupal/core/vendor/symfony/process/Symfony/Component/Process/Exception/ProcessFailedException.php
ProcessInSigchildEnvironment.php in drupal/core/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessInSigchildEnvironment.php
SimpleProcessTest.php in drupal/core/vendor/symfony/process/Symfony/Component/Process/Tests/SimpleProcessTest.php

File

drupal/core/vendor/symfony/process/Symfony/Component/Process/Process.php, line 26

Namespace

Symfony\Component\Process
View source
class Process {
  const ERR = 'err';
  const OUT = 'out';
  const STATUS_READY = 'ready';
  const STATUS_STARTED = 'started';
  const STATUS_TERMINATED = 'terminated';
  const STDIN = 0;
  const STDOUT = 1;
  const STDERR = 2;

  // Timeout Precision in seconds.
  const TIMEOUT_PRECISION = 0.2;
  private $commandline;
  private $cwd;
  private $env;
  private $stdin;
  private $starttime;
  private $timeout;
  private $options;
  private $exitcode;
  private $fallbackExitcode;
  private $processInformation;
  private $stdout;
  private $stderr;
  private $enhanceWindowsCompatibility;
  private $enhanceSigchildCompatibility;
  private $pipes;
  private $process;
  private $status = self::STATUS_READY;
  private $incrementalOutputOffset;
  private $incrementalErrorOutputOffset;
  private $tty;
  private $fileHandles;
  private $readBytes;
  private static $sigchild;

  /**
   * Exit codes translation table.
   *
   * User-defined errors must use exit codes in the 64-113 range.
   *
   * @var array
   */
  public static $exitCodes = array(
    0 => 'OK',
    1 => 'General error',
    2 => 'Misuse of shell builtins',
    126 => 'Invoked command cannot execute',
    127 => 'Command not found',
    128 => 'Invalid exit argument',
    // signals
    129 => 'Hangup',
    130 => 'Interrupt',
    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)',
    138 => 'User-defined 1',
    139 => 'Segmentation violation',
    140 => 'User-defined 2',
    141 => 'Write to pipe with no one reading',
    142 => 'Signal raised by alarm',
    143 => 'Termination (request to terminate)',
    // 144 - not defined
    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',
    // 156 - not defined
    157 => 'Pollable event',
    // 158 - not defined
    159 => 'Bad syscall',
  );

  /**
   * Constructor.
   *
   * @param string  $commandline The command line to run
   * @param string  $cwd         The working directory
   * @param array   $env         The environment variables or null to inherit
   * @param string  $stdin       The STDIN content
   * @param integer $timeout     The timeout in seconds
   * @param array   $options     An array of options for proc_open
   *
   * @throws RuntimeException When proc_open is not installed
   *
   * @api
   */
  public function __construct($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array()) {
    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;
    $this->cwd = $cwd;

    // 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/bug.php?id=51800
    // @see : https://bugs.php.net/bug.php?id=50524
    if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || defined('PHP_WINDOWS_VERSION_BUILD'))) {
      $this->cwd = getcwd();
    }
    if (null !== $env) {
      $this->env = array();
      foreach ($env as $key => $value) {
        $this->env[(string) $key] = (string) $value;
      }
    }
    else {
      $this->env = null;
    }
    $this->stdin = $stdin;
    $this
      ->setTimeout($timeout);
    $this->enhanceWindowsCompatibility = true;
    $this->enhanceSigchildCompatibility = !defined('PHP_WINDOWS_VERSION_BUILD') && $this
      ->isSigchildEnabled();
    $this->options = array_replace(array(
      'suppress_errors' => true,
      'binary_pipes' => true,
    ), $options);
  }
  public function __destruct() {

    // stop() will check if we have a process running.
    $this
      ->stop();
  }
  public function __clone() {
    $this->exitcode = null;
    $this->fallbackExitcode = null;
    $this->processInformation = null;
    $this->stdout = null;
    $this->stderr = null;
    $this->pipes = null;
    $this->process = null;
    $this->status = self::STATUS_READY;
    $this->fileHandles = null;
    $this->readBytes = null;
  }

  /**
   * Runs the process.
   *
   * 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 callback|null $callback A PHP callback to run whenever there is some
   *                                output available on STDOUT or STDERR
   *
   * @return integer The exit status code
   *
   * @throws RuntimeException When process can't be launch or is stopped
   *
   * @api
   */
  public function run($callback = null) {
    $this
      ->start($callback);
    return $this
      ->wait($callback);
  }

  /**
   * Starts the process and returns after sending the 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.
   * 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 callback|null $callback A PHP callback to run whenever there is some
   *                                output available on STDOUT or STDERR
   *
   * @throws RuntimeException When process can't be launch or is stopped
   * @throws RuntimeException When process is already running
   */
  public function start($callback = null) {
    if ($this
      ->isRunning()) {
      throw new RuntimeException('Process is already running');
    }
    $this->starttime = microtime(true);
    $this->stdout = '';
    $this->stderr = '';
    $this->incrementalOutputOffset = 0;
    $this->incrementalErrorOutputOffset = 0;
    $callback = $this
      ->buildCallback($callback);
    $descriptors = $this
      ->getDescriptors();
    $commandline = $this->commandline;
    if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->enhanceWindowsCompatibility) {
      $commandline = 'cmd /V:ON /E:ON /C "' . $commandline . '"';
      if (!isset($this->options['bypass_shell'])) {
        $this->options['bypass_shell'] = true;
      }
    }
    $this->process = proc_open($commandline, $descriptors, $this->pipes, $this->cwd, $this->env, $this->options);
    if (!is_resource($this->process)) {
      throw new RuntimeException('Unable to launch a new process.');
    }
    $this->status = self::STATUS_STARTED;
    foreach ($this->pipes as $pipe) {
      stream_set_blocking($pipe, false);
    }
    if ($this->tty) {
      $this->status = self::STATUS_TERMINATED;
      return;
    }
    if (null === $this->stdin) {
      fclose($this->pipes[0]);
      unset($this->pipes[0]);
      return;
    }
    $writePipes = array(
      $this->pipes[0],
    );
    unset($this->pipes[0]);
    $stdinLen = strlen($this->stdin);
    $stdinOffset = 0;
    while ($writePipes) {
      if (defined('PHP_WINDOWS_VERSION_BUILD')) {
        $this
          ->processFileHandles($callback);
      }
      $r = $this->pipes;
      $w = $writePipes;
      $e = null;
      $n = @stream_select($r, $w, $e, 0, ceil(static::TIMEOUT_PRECISION * 1000000.0));
      if (false === $n) {
        break;
      }
      if ($n === 0) {
        proc_terminate($this->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, $this->pipes);
        $data = fread($pipe, 8192);
        if (strlen($data) > 0) {
          call_user_func($callback, $type == 1 ? self::OUT : self::ERR, $data);
        }
        if (false === $data || feof($pipe)) {
          fclose($pipe);
          unset($this->pipes[$type]);
        }
      }
      $this
        ->checkTimeout();
    }
    $this
      ->updateStatus();
  }

  /**
   * Restarts the process.
   *
   * Be warned that the process is cloned before being started.
   *
   * @param callable $callback A PHP callback to run whenever there is some
   *                           output available on STDOUT or STDERR
   *
   * @return Process The new process
   *
   * @throws \RuntimeException When process can't be launch or is stopped
   * @throws \RuntimeException When process is already running
   *
   * @see start()
   */
  public function restart($callback = null) {
    if ($this
      ->isRunning()) {
      throw new RuntimeException('Process is already running');
    }
    $process = clone $this;
    $process
      ->start($callback);
    return $process;
  }

  /**
   * 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 callback|null $callback A valid PHP callback
   *
   * @return integer The exitcode of the process
   *
   * @throws \RuntimeException When process timed out
   * @throws \RuntimeException When process stopped after receiving signal
   */
  public function wait($callback = null) {
    $this
      ->updateStatus();
    $callback = $this
      ->buildCallback($callback);
    while ($this->pipes || defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles) {
      if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles) {
        $this
          ->processFileHandles($callback, !$this->pipes);
      }
      $this
        ->checkTimeout();
      if ($this->pipes) {
        $r = $this->pipes;
        $w = null;
        $e = null;

        // let's have a look if something changed in streams
        if (false === ($n = @stream_select($r, $w, $e, 0, ceil(static::TIMEOUT_PRECISION * 1000000.0)))) {
          $lastError = error_get_last();

          // stream_select returns false when the `select` system call is interrupted by an incoming signal
          if (isset($lastError['message']) && false === stripos($lastError['message'], 'interrupted system call')) {
            $this->pipes = array();
          }
          continue;
        }

        // nothing has changed
        if (0 === $n) {
          continue;
        }
        foreach ($r as $pipe) {
          $type = array_search($pipe, $this->pipes);
          $data = fread($pipe, 8192);
          if (strlen($data) > 0) {

            // last exit code is output and caught to work around --enable-sigchild
            if (3 == $type) {
              $this->fallbackExitcode = (int) $data;
            }
            else {
              call_user_func($callback, $type == 1 ? self::OUT : self::ERR, $data);
            }
          }
          if (false === $data || feof($pipe)) {
            fclose($pipe);
            unset($this->pipes[$type]);
          }
        }
      }
    }
    $this
      ->updateStatus();
    if ($this->processInformation['signaled']) {
      if ($this
        ->isSigchildEnabled()) {
        throw new RuntimeException('The process has been signaled.');
      }
      throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
    }
    $time = 0;
    while ($this
      ->isRunning() && $time < 1000000) {
      $time += 1000;
      usleep(1000);
    }
    $exitcode = proc_close($this->process);
    if ($this->processInformation['signaled']) {
      if ($this
        ->isSigchildEnabled()) {
        throw new RuntimeException('The process has been signaled.');
      }
      throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
    }
    $this->exitcode = $this->processInformation['running'] ? $exitcode : $this->processInformation['exitcode'];
    if (-1 == $this->exitcode && null !== $this->fallbackExitcode) {
      $this->exitcode = $this->fallbackExitcode;
    }
    return $this->exitcode;
  }

  /**
   * Returns the Pid (process identifier), if applicable.
   *
   * @return integer|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();
    return $this
      ->isRunning() ? $this->processInformation['pid'] : null;
  }

  /**
   * Sends a posix signal to the process.
   *
   * @param  integer $signal A valid posix signal (see http://www.php.net/manual/en/pcntl.constants.php)
   * @return Process
   *
   * @throws LogicException   In case the process is not running
   * @throws RuntimeException In case --enable-sigchild is activated
   * @throws RuntimeException In case of failure
   */
  public function signal($signal) {
    if (!$this
      ->isRunning()) {
      throw new LogicException('Can not send signal on a non running process.');
    }
    if ($this
      ->isSigchildEnabled()) {
      throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
    }
    if (true !== @proc_terminate($this->process, $signal)) {
      throw new RuntimeException(sprintf('Error while sending signal `%d`.', $signal));
    }
    return $this;
  }

  /**
   * Returns the current output of the process (STDOUT).
   *
   * @return string The process output
   *
   * @api
   */
  public function getOutput() {
    $this
      ->updateOutput();
    return $this->stdout;
  }

  /**
   * Returns the output incrementally.
   *
   * In comparison with the getOutput method which always return the whole
   * output, this one returns the new output since the last call.
   *
   * @return string The process output since the last call
   */
  public function getIncrementalOutput() {
    $data = $this
      ->getOutput();
    $latest = substr($data, $this->incrementalOutputOffset);
    $this->incrementalOutputOffset = strlen($data);
    return $latest;
  }

  /**
   * Returns the current error output of the process (STDERR).
   *
   * @return string The process error output
   *
   * @api
   */
  public function getErrorOutput() {
    $this
      ->updateErrorOutput();
    return $this->stderr;
  }

  /**
   * Returns the errorOutput incrementally.
   *
   * In comparison with the getErrorOutput method which always return the
   * whole error output, this one returns the new error output since the last
   * call.
   *
   * @return string The process error output since the last call
   */
  public function getIncrementalErrorOutput() {
    $data = $this
      ->getErrorOutput();
    $latest = substr($data, $this->incrementalErrorOutputOffset);
    $this->incrementalErrorOutputOffset = strlen($data);
    return $latest;
  }

  /**
   * Returns the exit code returned by the process.
   *
   * @return integer The exit status code
   *
   * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
   *
   * @api
   */
  public function getExitCode() {
    if ($this
      ->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) {
      throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method');
    }
    $this
      ->updateStatus();
    return $this->exitcode;
  }

  /**
   * Returns a string representation for the exit code returned by the process.
   *
   * This method relies on the Unix exit code status standardization
   * and might not be relevant for other operating systems.
   *
   * @return string A string representation for the exit status code
   *
   * @see http://tldp.org/LDP/abs/html/exitcodes.html
   * @see http://en.wikipedia.org/wiki/Unix_signal
   */
  public function getExitCodeText() {
    $exitcode = $this
      ->getExitCode();
    return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
  }

  /**
   * Checks if the process ended successfully.
   *
   * @return Boolean true if the process ended successfully, false otherwise
   *
   * @api
   */
  public function isSuccessful() {
    return 0 == $this
      ->getExitCode();
  }

  /**
   * Returns true if the child process has been terminated by an uncaught signal.
   *
   * It always returns false on Windows.
   *
   * @return Boolean
   *
   * @throws RuntimeException In case --enable-sigchild is activated
   *
   * @api
   */
  public function hasBeenSignaled() {
    if ($this
      ->isSigchildEnabled()) {
      throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
    }
    $this
      ->updateStatus();
    return $this->processInformation['signaled'];
  }

  /**
   * Returns the number of the signal that caused the child process to terminate its execution.
   *
   * It is only meaningful if hasBeenSignaled() returns true.
   *
   * @return integer
   *
   * @throws RuntimeException In case --enable-sigchild is activated
   *
   * @api
   */
  public function getTermSignal() {
    if ($this
      ->isSigchildEnabled()) {
      throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
    }
    $this
      ->updateStatus();
    return $this->processInformation['termsig'];
  }

  /**
   * Returns true if the child process has been stopped by a signal.
   *
   * It always returns false on Windows.
   *
   * @return Boolean
   *
   * @api
   */
  public function hasBeenStopped() {
    $this
      ->updateStatus();
    return $this->processInformation['stopped'];
  }

  /**
   * Returns the number of the signal that caused the child process to stop its execution.
   *
   * It is only meaningful if hasBeenStopped() returns true.
   *
   * @return integer
   *
   * @api
   */
  public function getStopSignal() {
    $this
      ->updateStatus();
    return $this->processInformation['stopsig'];
  }

  /**
   * Checks if the process is currently running.
   *
   * @return Boolean true if the process is currently running, false otherwise
   */
  public function isRunning() {
    if (self::STATUS_STARTED !== $this->status) {
      return false;
    }
    $this
      ->updateStatus();
    return $this->processInformation['running'];
  }

  /**
   * Checks if the process has been started with no regard to the current state.
   *
   * @return Boolean true if status is ready, false otherwise
   */
  public function isStarted() {
    return $this->status != self::STATUS_READY;
  }

  /**
   * Checks if the process is terminated.
   *
   * @return Boolean true if process is terminated, false otherwise
   */
  public function isTerminated() {
    $this
      ->updateStatus();
    return $this->status == self::STATUS_TERMINATED;
  }

  /**
   * Gets the process status.
   *
   * The status is one of: ready, started, terminated.
   *
   * @return string The current process status
   */
  public function getStatus() {
    $this
      ->updateStatus();
    return $this->status;
  }

  /**
   * Stops the process.
   *
   * @param integer|float $timeout The timeout in seconds
   * @param integer       $signal  A posix signal to send in case the process has not stop at timeout, default is SIGKILL
   *
   * @return integer The exit-code of the process
   *
   * @throws RuntimeException if the process got signaled
   */
  public function stop($timeout = 10, $signal = null) {
    $timeoutMicro = (int) $timeout * 1000000.0;
    if ($this
      ->isRunning()) {
      proc_terminate($this->process);
      $time = 0;
      while (1 == $this
        ->isRunning() && $time < $timeoutMicro) {
        $time += 1000;
        usleep(1000);
      }
      if ($this
        ->isRunning() && !$this
        ->isSigchildEnabled()) {
        if (null !== $signal || defined('SIGKILL')) {
          $this
            ->signal($signal ?: SIGKILL);
        }
      }
      foreach ($this->pipes as $pipe) {
        fclose($pipe);
      }
      $this->pipes = array();
      $exitcode = proc_close($this->process);
      $this->exitcode = -1 === $this->processInformation['exitcode'] ? $exitcode : $this->processInformation['exitcode'];
      if (defined('PHP_WINDOWS_VERSION_BUILD')) {
        foreach ($this->fileHandles as $fileHandle) {
          fclose($fileHandle);
        }
        $this->fileHandles = array();
      }
    }
    $this->status = self::STATUS_TERMINATED;
    return $this->exitcode;
  }

  /**
   * Adds a line to the STDOUT stream.
   *
   * @param string $line The line to append
   */
  public function addOutput($line) {
    $this->stdout .= $line;
  }

  /**
   * Adds a line to the STDERR stream.
   *
   * @param string $line The line to append
   */
  public function addErrorOutput($line) {
    $this->stderr .= $line;
  }

  /**
   * Gets the command line to be executed.
   *
   * @return string The command to execute
   */
  public function getCommandLine() {
    return $this->commandline;
  }

  /**
   * Sets the command line to be executed.
   *
   * @param string $commandline The command to execute
   *
   * @return self The current Process instance
   */
  public function setCommandLine($commandline) {
    $this->commandline = $commandline;
    return $this;
  }

  /**
   * Gets the process timeout.
   *
   * @return integer|null The timeout in seconds or null if it's disabled
   */
  public function getTimeout() {
    return $this->timeout;
  }

  /**
   * Sets the process timeout.
   *
   * To disable the timeout, set this value to null.
   *
   * @param float|null $timeout The timeout in seconds
   *
   * @return self The current Process instance
   *
   * @throws InvalidArgumentException if the timeout is negative
   */
  public function setTimeout($timeout) {
    if (null === $timeout) {
      $this->timeout = null;
      return $this;
    }
    $timeout = (double) $timeout;
    if ($timeout < 0) {
      throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
    }
    $this->timeout = $timeout;
    return $this;
  }

  /**
   * Enables or disables the TTY mode.
   *
   * @param boolean $tty True to enabled and false to disable
   *
   * @return self The current Process instance
   */
  public function setTty($tty) {
    $this->tty = (bool) $tty;
    return $this;
  }

  /**
   * Checks if  the TTY mode is enabled.
   *
   * @return Boolean true if the TTY mode is enabled, false otherwise
   */
  public function isTty() {
    return $this->tty;
  }

  /**
   * Gets the working directory.
   *
   * @return string The current working directory
   */
  public function getWorkingDirectory() {

    // This is for BC only
    if (null === $this->cwd) {

      // getcwd() will return false if any one of the parent directories does not have
      // the readable or search mode set, even if the current directory does
      return getcwd() ?: null;
    }
    return $this->cwd;
  }

  /**
   * Sets the current working directory.
   *
   * @param string $cwd The new working directory
   *
   * @return self The current Process instance
   */
  public function setWorkingDirectory($cwd) {
    $this->cwd = $cwd;
    return $this;
  }

  /**
   * Gets the environment variables.
   *
   * @return array The current environment variables
   */
  public function getEnv() {
    return $this->env;
  }

  /**
   * Sets the environment variables.
   *
   * @param array $env The new environment variables
   *
   * @return self The current Process instance
   */
  public function setEnv(array $env) {
    $this->env = $env;
    return $this;
  }

  /**
   * Gets the contents of STDIN.
   *
   * @return string The current contents
   */
  public function getStdin() {
    return $this->stdin;
  }

  /**
   * Sets the contents of STDIN.
   *
   * @param string $stdin The new contents
   *
   * @return self The current Process instance
   */
  public function setStdin($stdin) {
    $this->stdin = $stdin;
    return $this;
  }

  /**
   * Gets the options for proc_open.
   *
   * @return array The current options
   */
  public function getOptions() {
    return $this->options;
  }

  /**
   * Sets the options for proc_open.
   *
   * @param array $options The new options
   *
   * @return self The current Process instance
   */
  public function setOptions(array $options) {
    $this->options = $options;
    return $this;
  }

  /**
   * Gets whether or not Windows compatibility is enabled.
   *
   * This is true by default.
   *
   * @return Boolean
   */
  public function getEnhanceWindowsCompatibility() {
    return $this->enhanceWindowsCompatibility;
  }

  /**
   * Sets whether or not Windows compatibility is enabled.
   *
   * @param Boolean $enhance
   *
   * @return self The current Process instance
   */
  public function setEnhanceWindowsCompatibility($enhance) {
    $this->enhanceWindowsCompatibility = (bool) $enhance;
    return $this;
  }

  /**
   * Returns whether sigchild compatibility mode is activated or not.
   *
   * @return Boolean
   */
  public function getEnhanceSigchildCompatibility() {
    return $this->enhanceSigchildCompatibility;
  }

  /**
   * Activates sigchild compatibility mode.
   *
   * Sigchild compatibility mode is required to get the exit code and
   * determine the success of a process when PHP has been compiled with
   * the --enable-sigchild option
   *
   * @param Boolean $enhance
   *
   * @return self The current Process instance
   */
  public function setEnhanceSigchildCompatibility($enhance) {
    $this->enhanceSigchildCompatibility = (bool) $enhance;
    return $this;
  }

  /**
   * Performs a check between the timeout definition and the time the process started.
   *
   * In case you run a background process (with the start method), you should
   * trigger this method regularly to ensure the process timeout
   *
   * @throws RuntimeException In case the timeout was reached
   */
  public function checkTimeout() {
    if (0 < $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
      $this
        ->stop(0);
      throw new RuntimeException('The process timed-out.');
    }
  }

  /**
   * Creates the descriptors needed by the proc_open.
   *
   * @return array
   */
  private function getDescriptors() {

    //Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.

    //Workaround for this problem is to use temporary files instead of pipes on Windows platform.

    //@see https://bugs.php.net/bug.php?id=51800
    if (defined('PHP_WINDOWS_VERSION_BUILD')) {
      $this->fileHandles = array(
        self::STDOUT => tmpfile(),
      );
      if (false === $this->fileHandles[self::STDOUT]) {
        throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable');
      }
      $this->readBytes = array(
        self::STDOUT => 0,
      );
      return array(
        array(
          'pipe',
          'r',
        ),
        $this->fileHandles[self::STDOUT],
        array(
          'pipe',
          'w',
        ),
      );
    }
    if ($this->tty) {
      $descriptors = array(
        array(
          'file',
          '/dev/tty',
          'r',
        ),
        array(
          'file',
          '/dev/tty',
          'w',
        ),
        array(
          'file',
          '/dev/tty',
          'w',
        ),
      );
    }
    else {
      $descriptors = array(
        array(
          'pipe',
          'r',
        ),
        // stdin
        array(
          'pipe',
          'w',
        ),
        // stdout
        array(
          'pipe',
          'w',
        ),
      );
    }
    if ($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;
  }

  /**
   * Builds up the callback used by wait().
   *
   * The callbacks adds all occurred output to the specific buffer and calls
   * the user callback (if present) with the received output.
   *
   * @param callback|null $callback The user defined PHP callback
   *
   * @return callback A PHP callable
   */
  protected function buildCallback($callback) {
    $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);
      }
    };
    return $callback;
  }

  /**
   * Updates the status of the process.
   */
  protected function updateStatus() {
    if (self::STATUS_STARTED !== $this->status) {
      return;
    }
    $this->processInformation = proc_get_status($this->process);
    if (!$this->processInformation['running']) {
      $this->status = self::STATUS_TERMINATED;
      if (-1 !== $this->processInformation['exitcode']) {
        $this->exitcode = $this->processInformation['exitcode'];
      }
    }
  }

  /**
   * Updates the current error output of the process (STDERR).
   */
  protected function updateErrorOutput() {
    if (isset($this->pipes[self::STDERR]) && is_resource($this->pipes[self::STDERR])) {
      $this
        ->addErrorOutput(stream_get_contents($this->pipes[self::STDERR]));
    }
  }

  /**
   * Updates the current output of the process (STDOUT).
   */
  protected function updateOutput() {
    if (defined('PHP_WINDOWS_VERSION_BUILD') && isset($this->fileHandles[self::STDOUT]) && is_resource($this->fileHandles[self::STDOUT])) {
      fseek($this->fileHandles[self::STDOUT], $this->readBytes[self::STDOUT]);
      $this
        ->addOutput(stream_get_contents($this->fileHandles[self::STDOUT]));
    }
    elseif (isset($this->pipes[self::STDOUT]) && is_resource($this->pipes[self::STDOUT])) {
      $this
        ->addOutput(stream_get_contents($this->pipes[self::STDOUT]));
    }
  }

  /**
   * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
   *
   * @return Boolean
   */
  protected function isSigchildEnabled() {
    if (null !== self::$sigchild) {
      return self::$sigchild;
    }
    ob_start();
    phpinfo(INFO_GENERAL);
    return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
  }

  /**
   * Handles the windows file handles fallbacks.
   *
   * @param callable $callback A valid PHP callback
   * @param Boolean $closeEmptyHandles if true, handles that are empty will be assumed closed
   */
  private function processFileHandles($callback, $closeEmptyHandles = false) {
    $fh = $this->fileHandles;
    foreach ($fh as $type => $fileHandle) {
      fseek($fileHandle, $this->readBytes[$type]);
      $data = fread($fileHandle, 8192);
      if (strlen($data) > 0) {
        $this->readBytes[$type] += strlen($data);
        call_user_func($callback, $type == 1 ? self::OUT : self::ERR, $data);
      }
      if (false === $data || $closeEmptyHandles && '' === $data && feof($fileHandle)) {
        fclose($fileHandle);
        unset($this->fileHandles[$type]);
      }
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
Process::$commandline private property
Process::$cwd private property
Process::$enhanceSigchildCompatibility private property
Process::$enhanceWindowsCompatibility private property
Process::$env private property
Process::$exitcode private property
Process::$exitCodes public static property Exit codes translation table.
Process::$fallbackExitcode private property
Process::$fileHandles private property
Process::$incrementalErrorOutputOffset private property
Process::$incrementalOutputOffset private property
Process::$options private property
Process::$pipes private property
Process::$process private property
Process::$processInformation private property
Process::$readBytes private property
Process::$sigchild private static property
Process::$starttime private property
Process::$status private property
Process::$stderr private property
Process::$stdin private property
Process::$stdout private property
Process::$timeout private property
Process::$tty private property
Process::addErrorOutput public function Adds a line to the STDERR stream.
Process::addOutput public function Adds a line to the STDOUT stream.
Process::buildCallback protected function Builds up the callback used by wait().
Process::checkTimeout public function Performs a check between the timeout definition and the time the process started.
Process::ERR constant
Process::getCommandLine public function Gets the command line to be executed.
Process::getDescriptors private function Creates the descriptors needed by the proc_open.
Process::getEnhanceSigchildCompatibility public function Returns whether sigchild compatibility mode is activated or not.
Process::getEnhanceWindowsCompatibility public function Gets whether or not Windows compatibility is enabled.
Process::getEnv public function Gets the environment variables.
Process::getErrorOutput public function Returns the current error output of the process (STDERR).
Process::getExitCode public function Returns the exit code returned by the process.
Process::getExitCodeText public function Returns a string representation for the exit code returned by the process.
Process::getIncrementalErrorOutput public function Returns the errorOutput incrementally.
Process::getIncrementalOutput public function Returns the output incrementally.
Process::getOptions public function Gets the options for proc_open.
Process::getOutput public function Returns the current output of the process (STDOUT).
Process::getPid public function Returns the Pid (process identifier), if applicable.
Process::getStatus public function Gets the process status.
Process::getStdin public function Gets the contents of STDIN.
Process::getStopSignal public function Returns the number of the signal that caused the child process to stop its execution.
Process::getTermSignal public function Returns the number of the signal that caused the child process to terminate its execution.
Process::getTimeout public function Gets the process timeout.
Process::getWorkingDirectory public function Gets the working directory.
Process::hasBeenSignaled public function Returns true if the child process has been terminated by an uncaught signal.
Process::hasBeenStopped public function Returns true if the child process has been stopped by a signal.
Process::isRunning public function Checks if the process is currently running.
Process::isSigchildEnabled protected function Returns whether PHP has been compiled with the '--enable-sigchild' option or not. 1
Process::isStarted public function Checks if the process has been started with no regard to the current state.
Process::isSuccessful public function Checks if the process ended successfully.
Process::isTerminated public function Checks if the process is terminated.
Process::isTty public function Checks if the TTY mode is enabled.
Process::OUT constant
Process::processFileHandles private function Handles the windows file handles fallbacks.
Process::restart public function Restarts the process.
Process::run public function Runs the process.
Process::setCommandLine public function Sets the command line to be executed.
Process::setEnhanceSigchildCompatibility public function Activates sigchild compatibility mode.
Process::setEnhanceWindowsCompatibility public function Sets whether or not Windows compatibility is enabled.
Process::setEnv public function Sets the environment variables.
Process::setOptions public function Sets the options for proc_open.
Process::setStdin public function Sets the contents of STDIN.
Process::setTimeout public function Sets the process timeout.
Process::setTty public function Enables or disables the TTY mode.
Process::setWorkingDirectory public function Sets the current working directory.
Process::signal public function Sends a posix signal to the process.
Process::start public function Starts the process and returns after sending the STDIN. 1
Process::STATUS_READY constant
Process::STATUS_STARTED constant
Process::STATUS_TERMINATED constant
Process::STDERR constant
Process::STDIN constant
Process::STDOUT constant
Process::stop public function Stops the process.
Process::TIMEOUT_PRECISION constant
Process::updateErrorOutput protected function Updates the current error output of the process (STDERR).
Process::updateOutput protected function Updates the current output of the process (STDOUT).
Process::updateStatus protected function Updates the status of the process.
Process::wait public function Waits for the process to terminate.
Process::__clone public function
Process::__construct public function Constructor. 1
Process::__destruct public function