2010-04-17 13:49:58 +01:00
< ? php
/*
2010-04-24 00:22:16 +01:00
* This file is part of the Symfony package .
2010-04-17 13:49:58 +01:00
*
2011-03-06 11:40:06 +00:00
* ( c ) Fabien Potencier < fabien @ symfony . com >
2010-04-17 13:49:58 +01:00
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
2011-01-15 13:29:43 +00:00
namespace Symfony\Component\Process ;
2012-08-30 20:25:16 +01:00
use Symfony\Component\Process\Exception\InvalidArgumentException ;
2012-09-09 15:56:43 +01:00
use Symfony\Component\Process\Exception\LogicException ;
2013-08-03 08:24:32 +01:00
use Symfony\Component\Process\Exception\ProcessFailedException ;
2013-08-02 20:20:26 +01:00
use Symfony\Component\Process\Exception\ProcessTimedOutException ;
2012-08-30 20:25:16 +01:00
use Symfony\Component\Process\Exception\RuntimeException ;
2014-04-22 22:51:46 +01:00
use Symfony\Component\Process\Pipes\PipesInterface ;
use Symfony\Component\Process\Pipes\UnixPipes ;
use Symfony\Component\Process\Pipes\WindowsPipes ;
2012-08-30 20:25:16 +01:00
2010-04-17 13:49:58 +01:00
/**
2014-02-02 13:42:18 +00:00
* Process is a thin wrapper around proc_ * functions to easily
2010-09-19 23:40:30 +01:00
* start independent PHP processes .
2010-04-17 13:49:58 +01:00
*
2011-03-06 11:40:06 +00:00
* @ author Fabien Potencier < fabien @ symfony . com >
2014-04-22 22:51:46 +01:00
* @ author Romain Neutron < imprec @ gmail . com >
2010-04-17 13:49:58 +01:00
*/
2016-04-02 12:30:41 +01:00
class Process implements \IteratorAggregate
2010-04-17 13:49:58 +01:00
{
2012-02-05 11:11:37 +00:00
const ERR = 'err' ;
const OUT = 'out' ;
2012-03-21 16:58:02 +00:00
const STATUS_READY = 'ready' ;
const STATUS_STARTED = 'started' ;
const STATUS_TERMINATED = 'terminated' ;
const STDIN = 0 ;
const STDOUT = 1 ;
const STDERR = 2 ;
2013-04-06 19:51:55 +01:00
// Timeout Precision in seconds.
2013-04-07 15:10:23 +01:00
const TIMEOUT_PRECISION = 0.2 ;
2013-04-06 19:51:55 +01:00
2016-04-12 09:05:51 +01:00
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
2013-08-13 17:00:31 +01:00
private $callback ;
2016-04-03 16:15:29 +01:00
private $hasCallback = false ;
2011-03-24 08:10:42 +00:00
private $commandline ;
private $cwd ;
private $env ;
2014-05-17 19:36:38 +01:00
private $input ;
2013-04-06 19:51:55 +01:00
private $starttime ;
2013-08-02 20:20:26 +01:00
private $lastOutputTime ;
2011-03-24 08:10:42 +00:00
private $timeout ;
2013-08-02 20:20:26 +01:00
private $idleTimeout ;
2017-01-31 09:03:29 +00:00
private $options = array ( 'suppress_errors' => true );
2011-03-24 08:10:42 +00:00
private $exitcode ;
2015-12-08 16:23:26 +00:00
private $fallbackStatus = array ();
2012-03-21 16:58:02 +00:00
private $processInformation ;
2013-12-11 18:23:45 +00:00
private $outputDisabled = false ;
2011-03-24 08:10:42 +00:00
private $stdout ;
private $stderr ;
2014-04-21 18:54:45 +01:00
private $enhanceWindowsCompatibility = true ;
2012-09-09 15:38:21 +01:00
private $enhanceSigchildCompatibility ;
2012-03-21 16:58:02 +00:00
private $process ;
private $status = self :: STATUS_READY ;
2014-03-18 20:00:58 +00:00
private $incrementalOutputOffset = 0 ;
private $incrementalErrorOutputOffset = 0 ;
2013-04-05 11:35:05 +01:00
private $tty ;
2013-08-03 08:10:03 +01:00
private $pty ;
2016-06-14 14:16:11 +01:00
private $inheritEnv = false ;
2010-05-06 12:25:53 +01:00
2013-09-02 09:23:35 +01:00
private $useFileHandles = false ;
2014-04-22 22:51:46 +01:00
/** @var PipesInterface */
2013-09-02 09:23:35 +01:00
private $processPipes ;
2012-04-22 20:52:08 +01:00
2014-07-22 17:04:22 +01:00
private $latestSignal ;
2012-09-09 15:38:21 +01:00
private static $sigchild ;
2012-02-13 06:31:37 +00:00
/**
* Exit codes translation table .
*
2012-04-07 19:15:29 +01:00
* User - defined errors must use exit codes in the 64 - 113 range .
*
2012-02-13 06:31:37 +00:00
* @ var array
*/
2012-07-09 13:50:58 +01:00
public static $exitCodes = array (
2012-02-13 06:31:37 +00:00
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' ,
);
2010-05-06 12:25:53 +01:00
/**
* Constructor .
*
2017-01-12 17:50:31 +00:00
* @ param string | array $commandline The command line to run
2014-11-30 13:33:44 +00:00
* @ param string | null $cwd The working directory or null to use the working dir of the current PHP process
2015-10-19 15:17:09 +01:00
* @ param array | null $env The environment variables or null to use the same environment as the current PHP process
2016-03-31 16:15:42 +01:00
* @ param mixed | null $input The input as stream resource , scalar or \Traversable , or null for no input
2014-11-30 13:33:44 +00:00
* @ param int | float | null $timeout The timeout in seconds or null to disable
* @ param array $options An array of options for proc_open
2010-05-06 12:25:53 +01:00
*
2012-08-30 20:25:16 +01:00
* @ throws RuntimeException When proc_open is not installed
2010-05-06 12:25:53 +01:00
*/
2017-01-31 09:03:29 +00:00
public function __construct ( $commandline , $cwd = null , array $env = null , $input = null , $timeout = 60 , array $options = null )
2010-04-17 13:49:58 +01:00
{
2010-05-07 15:09:11 +01:00
if ( ! function_exists ( 'proc_open' )) {
2012-08-30 20:25:16 +01:00
throw new RuntimeException ( 'The Process class relies on proc_open, which is not available on your PHP installation.' );
2010-05-06 12:25:53 +01:00
}
2010-04-17 13:49:58 +01:00
2017-04-20 15:08:24 +01:00
$this -> commandline = $commandline ;
2013-01-08 12:27:17 +00:00
$this -> cwd = $cwd ;
2013-03-02 17:41:21 +00:00
2013-12-27 15:08:19 +00:00
// 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
2013-03-02 17:41:21 +00:00
// @see : https://bugs.php.net/bug.php?id=51800
// @see : https://bugs.php.net/bug.php?id=50524
2014-12-29 12:05:07 +00:00
if ( null === $this -> cwd && ( defined ( 'ZEND_THREAD_SAFE' ) || '\\' === DIRECTORY_SEPARATOR )) {
2013-02-17 19:36:23 +00:00
$this -> cwd = getcwd ();
}
2011-05-19 15:52:45 +01:00
if ( null !== $env ) {
2013-06-13 07:50:29 +01:00
$this -> setEnv ( $env );
2010-05-06 12:25:53 +01:00
}
2014-04-21 18:54:45 +01:00
2016-03-29 17:06:47 +01:00
$this -> setInput ( $input );
2012-07-10 14:21:23 +01:00
$this -> setTimeout ( $timeout );
2014-12-29 12:05:07 +00:00
$this -> useFileHandles = '\\' === DIRECTORY_SEPARATOR ;
2013-08-03 08:10:03 +01:00
$this -> pty = false ;
2014-12-29 12:05:07 +00:00
$this -> enhanceSigchildCompatibility = '\\' !== DIRECTORY_SEPARATOR && $this -> isSigchildEnabled ();
2017-01-31 09:03:29 +00:00
if ( null !== $options ) {
@ trigger_error ( sprintf ( 'The $options parameter of the %s constructor is deprecated since version 3.3 and will be removed in 4.0.' , __CLASS__ ), E_USER_DEPRECATED );
$this -> options = array_replace ( $this -> options , $options );
}
2010-04-17 13:49:58 +01:00
}
2010-05-06 12:25:53 +01:00
2012-04-11 22:08:57 +01:00
public function __destruct ()
{
2015-12-07 09:45:14 +00:00
$this -> stop ( 0 );
2012-04-11 22:08:57 +01:00
}
2012-09-07 08:03:48 +01:00
public function __clone ()
{
2013-08-20 19:22:08 +01:00
$this -> resetProcessData ();
2012-09-07 08:03:48 +01:00
}
2010-05-06 12:25:53 +01:00
/**
2010-12-20 09:30:59 +00:00
* Runs the process .
2010-05-06 12:25:53 +01:00
*
* The callback receives the type of output ( out or err ) and
* some bytes from the output in real - time . It allows to have feedback
2010-09-19 23:40:30 +01:00
* from the independent process during execution .
2010-05-06 12:25:53 +01:00
*
2011-04-01 17:07:53 +01:00
* The STDOUT and STDERR are also available after the process is finished
* via the getOutput () and getErrorOutput () methods .
2010-05-06 12:25:53 +01:00
*
2013-11-20 02:35:05 +00:00
* @ param callable | null $callback A PHP callback to run whenever there is some
2013-01-08 19:11:30 +00:00
* output available on STDOUT or STDERR
2017-01-12 17:50:31 +00:00
* @ param array $env An array of additional env vars to set when running the process
2010-05-06 12:25:53 +01:00
*
2014-11-30 13:33:44 +00:00
* @ return int The exit status code
2010-05-06 12:25:53 +01:00
*
2014-03-18 20:00:58 +00:00
* @ throws RuntimeException When process can ' t be launched
* @ throws RuntimeException When process stopped after receiving signal
2013-12-11 18:23:45 +00:00
* @ throws LogicException In case a callback is provided and output has been disabled
2017-01-12 17:50:31 +00:00
*
* @ final since version 3.3
2010-05-06 12:25:53 +01:00
*/
2017-01-12 17:50:31 +00:00
public function run ( $callback = null /*, array $env = array()*/ )
2010-04-18 08:27:43 +01:00
{
2017-01-12 17:50:31 +00:00
$env = 1 < func_num_args () ? func_get_arg ( 1 ) : null ;
$this -> start ( $callback , $env );
2011-04-01 17:07:53 +01:00
2013-08-13 17:00:31 +01:00
return $this -> wait ();
2012-03-21 16:58:02 +00:00
}
2010-05-06 12:25:53 +01:00
2013-08-03 08:24:32 +01:00
/**
* Runs the process .
*
* This is identical to run () except that an exception is thrown if the process
* exits with a non - zero exit code .
*
* @ param callable | null $callback
2017-01-12 17:50:31 +00:00
* @ param array $env An array of additional env vars to set when running the process
2013-08-03 08:24:32 +01:00
*
* @ return self
2014-08-28 17:24:17 +01:00
*
* @ 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
2017-01-12 17:50:31 +00:00
*
* @ final since version 3.3
2013-08-03 08:24:32 +01:00
*/
2017-01-12 17:50:31 +00:00
public function mustRun ( callable $callback = null /*, array $env = array()*/ )
2013-08-03 08:24:32 +01:00
{
2015-12-10 16:58:22 +00:00
if ( ! $this -> enhanceSigchildCompatibility && $this -> isSigchildEnabled ()) {
2014-08-28 17:24:17 +01:00
throw new RuntimeException ( 'This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.' );
}
2017-01-12 17:50:31 +00:00
$env = 1 < func_num_args () ? func_get_arg ( 1 ) : null ;
2014-08-28 17:24:17 +01:00
2017-01-12 17:50:31 +00:00
if ( 0 !== $this -> run ( $callback , $env )) {
2013-08-03 08:24:32 +01:00
throw new ProcessFailedException ( $this );
}
return $this ;
}
2012-03-21 16:58:02 +00:00
/**
2014-05-17 19:36:38 +01:00
* Starts the process and returns after writing the input to STDIN .
2012-03-21 16:58:02 +00:00
*
2012-03-23 11:59:10 +00:00
* This method blocks until all STDIN data is sent to the process then it
* returns while the process runs in the background .
*
2012-03-23 12:02:23 +00:00
* The termination of the process can be awaited with wait () .
2012-03-23 11:59:10 +00:00
*
* 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 .
2012-03-21 16:58:02 +00:00
*
2013-11-20 02:35:05 +00:00
* @ param callable | null $callback A PHP callback to run whenever there is some
2013-01-08 19:11:30 +00:00
* output available on STDOUT or STDERR
2017-01-12 17:50:31 +00:00
* @ param array $env An array of additional env vars to set when running the process
2012-03-21 16:58:02 +00:00
*
2014-03-18 20:00:58 +00:00
* @ throws RuntimeException When process can ' t be launched
2012-08-30 20:25:16 +01:00
* @ throws RuntimeException When process is already running
2013-12-11 18:23:45 +00:00
* @ throws LogicException In case a callback is provided and output has been disabled
2012-03-21 16:58:02 +00:00
*/
2017-01-12 17:50:31 +00:00
public function start ( callable $callback = null /*, array $env = array()*/ )
2012-03-21 16:58:02 +00:00
{
2012-03-23 11:06:08 +00:00
if ( $this -> isRunning ()) {
2012-08-30 20:25:16 +01:00
throw new RuntimeException ( 'Process is already running' );
2012-03-21 16:58:02 +00:00
}
2017-01-12 17:50:31 +00:00
if ( 2 <= func_num_args ()) {
$env = func_get_arg ( 1 );
} else {
if ( __CLASS__ !== static :: class ) {
$r = new \ReflectionMethod ( $this , __FUNCTION__ );
if ( __CLASS__ !== $r -> getDeclaringClass () -> getName () && ( 2 > $r -> getNumberOfParameters () || 'env' !== $r -> getParameters ()[ 0 ] -> name )) {
@ trigger_error ( sprintf ( 'The %s::start() method expects a second "$env" argument since version 3.3. It will be made mandatory in 4.0.' , static :: class ), E_USER_DEPRECATED );
}
}
$env = null ;
}
2012-03-23 11:59:10 +00:00
2013-08-20 19:22:08 +01:00
$this -> resetProcessData ();
2013-08-02 20:20:26 +01:00
$this -> starttime = $this -> lastOutputTime = microtime ( true );
2013-08-13 17:00:31 +01:00
$this -> callback = $this -> buildCallback ( $callback );
2016-04-03 16:15:29 +01:00
$this -> hasCallback = null !== $callback ;
2013-04-05 11:35:05 +01:00
$descriptors = $this -> getDescriptors ();
2017-01-12 17:50:31 +00:00
$inheritEnv = $this -> inheritEnv ;
2017-04-20 15:08:24 +01:00
if ( is_array ( $commandline = $this -> commandline )) {
$commandline = implode ( ' ' , array_map ( array ( $this , 'escapeArgument' ), $commandline ));
if ( '\\' !== DIRECTORY_SEPARATOR ) {
// exec is mandatory to deal with sending a signal to the process
$commandline = 'exec ' . $commandline ;
}
}
2012-02-16 18:16:07 +00:00
2017-01-12 17:50:31 +00:00
if ( null === $env ) {
$env = $this -> env ;
} else {
if ( $this -> env ) {
$env += $this -> env ;
}
$inheritEnv = true ;
}
2017-01-31 09:03:29 +00:00
$envBackup = array ();
2017-01-12 17:50:31 +00:00
if ( null !== $env && $inheritEnv ) {
2017-01-31 09:03:29 +00:00
foreach ( $env as $k => $v ) {
2017-03-27 17:26:57 +01:00
$envBackup [ $k ] = getenv ( $k );
2017-01-31 09:03:29 +00:00
putenv ( false === $v || null === $v ? $k : " $k = $v " );
2016-06-14 14:16:11 +01:00
}
$env = null ;
2017-01-31 09:03:29 +00:00
} elseif ( null !== $env ) {
2017-02-28 14:54:44 +00:00
@ 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 );
2016-06-14 14:16:11 +01:00
}
2014-12-29 12:05:07 +00:00
if ( '\\' === DIRECTORY_SEPARATOR && $this -> enhanceWindowsCompatibility ) {
2017-01-12 17:50:31 +00:00
$this -> options [ 'bypass_shell' ] = true ;
$commandline = $this -> prepareWindowsCommandLine ( $commandline , $envBackup );
2015-12-18 20:16:23 +00:00
} 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
2017-04-20 15:08:24 +01:00
$commandline = '{ (' . $commandline . ') <&3 3<&- 3>/dev/null & } 3<&0;' ;
2015-12-23 09:10:11 +00:00
$commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code' ;
2015-11-10 00:31:08 +00:00
// Workaround for the bug, when PTS functionality is enabled.
// @see : https://bugs.php.net/69442
2015-11-17 16:45:51 +00:00
$ptsWorkaround = fopen ( __FILE__ , 'r' );
2015-11-10 00:31:08 +00:00
}
2016-06-14 14:16:11 +01:00
$this -> process = proc_open ( $commandline , $descriptors , $this -> processPipes -> pipes , $this -> cwd , $env , $this -> options );
2010-05-06 12:25:53 +01:00
2017-01-31 09:03:29 +00:00
foreach ( $envBackup as $k => $v ) {
putenv ( false === $v ? $k : " $k = $v " );
}
2012-03-21 16:58:02 +00:00
if ( ! is_resource ( $this -> process )) {
2012-08-30 20:25:16 +01:00
throw new RuntimeException ( 'Unable to launch a new process.' );
2010-05-06 12:25:53 +01:00
}
2012-03-21 16:58:02 +00:00
$this -> status = self :: STATUS_STARTED ;
2010-05-06 12:25:53 +01:00
2015-12-18 20:16:23 +00:00
if ( isset ( $descriptors [ 3 ])) {
$this -> fallbackStatus [ 'pid' ] = ( int ) fgets ( $this -> processPipes -> pipes [ 3 ]);
}
2014-01-07 09:02:59 +00:00
if ( $this -> tty ) {
return ;
}
2013-08-13 17:00:31 +01:00
$this -> updateStatus ( false );
$this -> checkTimeout ();
2012-03-21 16:58:02 +00:00
}
2012-09-07 08:03:48 +01:00
/**
2012-10-04 15:23:11 +01:00
* Restarts the process .
*
* Be warned that the process is cloned before being started .
2012-09-07 08:03:48 +01:00
*
2013-11-20 02:35:05 +00:00
* @ param callable | null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
2017-01-12 17:50:31 +00:00
* @ param array $env An array of additional env vars to set when running the process
2012-09-07 08:03:48 +01:00
*
2016-12-26 07:50:27 +00:00
* @ return $this
2012-09-07 08:03:48 +01:00
*
2014-03-18 20:00:58 +00:00
* @ throws RuntimeException When process can ' t be launched
2013-08-29 23:32:35 +01:00
* @ throws RuntimeException When process is already running
2012-10-04 15:23:11 +01:00
*
2012-09-07 08:03:48 +01:00
* @ see start ()
2017-01-12 17:50:31 +00:00
*
* @ final since version 3.3
2012-09-07 08:03:48 +01:00
*/
2017-01-12 17:50:31 +00:00
public function restart ( callable $callback = null /*, array $env = array()*/ )
2012-09-07 08:03:48 +01:00
{
if ( $this -> isRunning ()) {
2013-04-07 21:19:55 +01:00
throw new RuntimeException ( 'Process is already running' );
2012-09-07 08:03:48 +01:00
}
2017-01-12 17:50:31 +00:00
$env = 1 < func_num_args () ? func_get_arg ( 1 ) : null ;
2012-09-07 08:03:48 +01:00
$process = clone $this ;
2017-01-12 17:50:31 +00:00
$process -> start ( $callback , $env );
2012-09-07 08:03:48 +01:00
return $process ;
}
2012-03-21 16:58:02 +00:00
/**
2012-03-23 11:59:10 +00:00
* Waits for the process to terminate .
2012-03-21 16:58:02 +00:00
*
2012-03-23 11:59:10 +00:00
* 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 .
*
2013-11-20 02:35:05 +00:00
* @ param callable | null $callback A valid PHP callback
2012-03-23 11:59:10 +00:00
*
2014-11-30 13:33:44 +00:00
* @ return int The exitcode of the process
2012-03-21 16:58:02 +00:00
*
2013-08-29 23:32:35 +01:00
* @ throws RuntimeException When process timed out
* @ throws RuntimeException When process stopped after receiving signal
2014-03-18 15:17:31 +00:00
* @ throws LogicException When process is not yet started
2012-03-21 16:58:02 +00:00
*/
2015-10-05 17:52:37 +01:00
public function wait ( callable $callback = null )
2012-03-21 16:58:02 +00:00
{
2014-03-18 15:17:31 +00:00
$this -> requireProcessIsStarted ( __FUNCTION__ );
2013-11-05 22:13:55 +00:00
2013-08-13 17:00:31 +01:00
$this -> updateStatus ( false );
2016-01-18 17:27:18 +00:00
2013-08-13 17:00:31 +01:00
if ( null !== $callback ) {
2016-01-18 17:27:18 +00:00
if ( ! $this -> processPipes -> haveReadSupport ()) {
$this -> stop ( 0 );
2017-02-18 12:12:14 +00:00
throw new \LogicException ( 'Pass the callback to the Process::start method or enableOutput to use a callback with Process::wait' );
2016-01-18 17:27:18 +00:00
}
2013-08-13 17:00:31 +01:00
$this -> callback = $this -> buildCallback ( $callback );
}
2013-10-09 22:17:57 +01:00
2013-10-09 14:14:24 +01:00
do {
2013-04-06 19:51:55 +01:00
$this -> checkTimeout ();
2014-12-30 13:34:16 +00:00
$running = '\\' === DIRECTORY_SEPARATOR ? $this -> isRunning () : $this -> processPipes -> areOpen ();
2016-03-13 10:06:10 +00:00
$this -> readPipes ( $running , '\\' !== DIRECTORY_SEPARATOR || ! $running );
2013-10-09 14:14:24 +01:00
} while ( $running );
2013-10-09 22:17:57 +01:00
2013-10-09 14:14:24 +01:00
while ( $this -> isRunning ()) {
usleep ( 1000 );
2012-03-21 16:58:02 +00:00
}
2010-04-17 13:49:58 +01:00
2014-07-22 17:04:22 +01:00
if ( $this -> processInformation [ 'signaled' ] && $this -> processInformation [ 'termsig' ] !== $this -> latestSignal ) {
2013-04-27 19:16:01 +01:00
throw new RuntimeException ( sprintf ( 'The process has been signaled with signal "%s".' , $this -> processInformation [ 'termsig' ]));
2010-05-06 12:25:53 +01:00
}
2011-07-12 07:16:22 +01:00
2012-08-26 14:13:51 +01:00
return $this -> exitcode ;
2010-05-06 12:25:53 +01:00
}
2012-09-09 15:56:43 +01:00
/**
* Returns the Pid ( process identifier ), if applicable .
*
2014-11-30 13:33:44 +00:00
* @ return int | null The process id if running , null otherwise
2012-09-09 15:56:43 +01:00
*/
public function getPid ()
{
return $this -> isRunning () ? $this -> processInformation [ 'pid' ] : null ;
}
/**
2013-12-27 15:08:19 +00:00
* Sends a POSIX signal to the process .
2012-09-09 15:56:43 +01:00
*
2014-11-30 13:33:44 +00:00
* @ param int $signal A valid POSIX signal ( see http :// www . php . net / manual / en / pcntl . constants . php )
2014-03-18 20:00:58 +00:00
*
2016-12-26 07:50:27 +00:00
* @ return $this
2012-09-09 15:56:43 +01:00
*
* @ throws LogicException In case the process is not running
2015-12-08 16:23:26 +00:00
* @ throws RuntimeException In case -- enable - sigchild is activated and the process can ' t be killed
2012-09-09 15:56:43 +01:00
* @ throws RuntimeException In case of failure
*/
public function signal ( $signal )
{
2014-03-14 15:52:09 +00:00
$this -> doSignal ( $signal , true );
2012-09-09 15:56:43 +01:00
return $this ;
}
2013-12-11 18:23:45 +00:00
/**
* Disables fetching output and error output from the underlying process .
*
2016-12-26 07:50:27 +00:00
* @ return $this
2013-12-11 18:23:45 +00:00
*
* @ throws RuntimeException In case the process is already running
2014-09-03 18:33:24 +01:00
* @ throws LogicException if an idle timeout is set
2013-12-11 18:23:45 +00:00
*/
public function disableOutput ()
{
if ( $this -> isRunning ()) {
throw new RuntimeException ( 'Disabling output while the process is running is not possible.' );
}
2014-03-14 18:08:20 +00:00
if ( null !== $this -> idleTimeout ) {
throw new LogicException ( 'Output can not be disabled while an idle timeout is set.' );
}
2013-12-11 18:23:45 +00:00
$this -> outputDisabled = true ;
return $this ;
}
/**
* Enables fetching output and error output from the underlying process .
*
2016-12-26 07:50:27 +00:00
* @ return $this
2013-12-11 18:23:45 +00:00
*
* @ 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 ;
return $this ;
}
/**
* Returns true in case the output is disabled , false otherwise .
*
2014-04-16 11:36:34 +01:00
* @ return bool
2013-12-11 18:23:45 +00:00
*/
public function isOutputDisabled ()
{
return $this -> outputDisabled ;
}
2010-05-06 12:25:53 +01:00
/**
2012-03-21 16:58:02 +00:00
* Returns the current output of the process ( STDOUT ) .
2012-03-23 11:59:10 +00:00
*
2010-05-06 12:25:53 +01:00
* @ return string The process output
2011-03-24 08:13:58 +00:00
*
2014-03-26 11:51:10 +00:00
* @ throws LogicException in case the output has been disabled
2013-11-05 22:13:55 +00:00
* @ throws LogicException In case the process is not started
2010-05-06 12:25:53 +01:00
*/
public function getOutput ()
2010-04-17 13:49:58 +01:00
{
2016-03-05 15:49:11 +00:00
$this -> readPipesForOutput ( __FUNCTION__ );
2012-03-21 16:58:02 +00:00
2016-01-18 16:23:05 +00:00
if ( false === $ret = stream_get_contents ( $this -> stdout , - 1 , 0 )) {
return '' ;
}
return $ret ;
2010-04-17 13:49:58 +01:00
}
2012-09-19 01:35:24 +01:00
/**
* 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 .
*
2016-06-06 10:38:47 +01:00
* @ return string The process output since the last call
*
2014-03-26 11:51:10 +00:00
* @ throws LogicException in case the output has been disabled
2014-03-18 15:17:31 +00:00
* @ throws LogicException In case the process is not started
2012-09-19 01:35:24 +01:00
*/
public function getIncrementalOutput ()
{
2016-03-05 15:49:11 +00:00
$this -> readPipesForOutput ( __FUNCTION__ );
2012-09-19 01:35:24 +01:00
2016-01-18 16:23:05 +00:00
$latest = stream_get_contents ( $this -> stdout , - 1 , $this -> incrementalOutputOffset );
$this -> incrementalOutputOffset = ftell ( $this -> stdout );
2014-12-31 00:27:17 +00:00
if ( false === $latest ) {
return '' ;
}
2012-09-19 01:35:24 +01:00
return $latest ;
}
2016-04-02 12:30:41 +01:00
/**
* Returns an iterator to the output of the process , with the output type as keys ( Process :: OUT / ERR ) .
*
2016-06-29 06:42:25 +01:00
* @ param int $flags A bit field of Process :: ITER_ * flags
2016-04-02 12:30:41 +01:00
*
* @ throws LogicException in case the output has been disabled
* @ throws LogicException In case the process is not started
*
* @ return \Generator
*/
2016-04-12 09:05:51 +01:00
public function getIterator ( $flags = 0 )
2016-04-02 12:30:41 +01:00
{
$this -> readPipesForOutput ( __FUNCTION__ , false );
2016-04-12 09:05:51 +01:00
$clearOutput = ! ( self :: ITER_KEEP_OUTPUT & $flags );
$blocking = ! ( self :: ITER_NON_BLOCKING & $flags );
$yieldOut = ! ( self :: ITER_SKIP_OUT & $flags );
$yieldErr = ! ( self :: ITER_SKIP_ERR & $flags );
2016-04-02 12:30:41 +01:00
2016-04-12 09:05:51 +01:00
while ( null !== $this -> callback || ( $yieldOut && ! feof ( $this -> stdout )) || ( $yieldErr && ! feof ( $this -> stderr ))) {
if ( $yieldOut ) {
$out = stream_get_contents ( $this -> stdout , - 1 , $this -> incrementalOutputOffset );
if ( isset ( $out [ 0 ])) {
if ( $clearOutput ) {
$this -> clearOutput ();
} else {
$this -> incrementalOutputOffset = ftell ( $this -> stdout );
}
2016-04-02 12:30:41 +01:00
2016-04-12 09:05:51 +01:00
yield self :: OUT => $out ;
}
2016-04-02 12:30:41 +01:00
}
2016-04-12 09:05:51 +01:00
if ( $yieldErr ) {
$err = stream_get_contents ( $this -> stderr , - 1 , $this -> incrementalErrorOutputOffset );
2016-04-02 12:30:41 +01:00
2016-04-12 09:05:51 +01:00
if ( isset ( $err [ 0 ])) {
if ( $clearOutput ) {
$this -> clearErrorOutput ();
} else {
$this -> incrementalErrorOutputOffset = ftell ( $this -> stderr );
}
2016-04-02 12:30:41 +01:00
2016-04-12 09:05:51 +01:00
yield self :: ERR => $err ;
}
2016-04-02 12:30:41 +01:00
}
if ( ! $blocking && ! isset ( $out [ 0 ]) && ! isset ( $err [ 0 ])) {
yield self :: OUT => '' ;
}
2016-11-22 20:44:12 +00:00
$this -> checkTimeout ();
2016-04-02 12:30:41 +01:00
$this -> readPipesForOutput ( __FUNCTION__ , $blocking );
}
}
2013-06-16 10:12:20 +01:00
/**
* Clears the process output .
*
2016-12-26 07:50:27 +00:00
* @ return $this
2013-06-16 10:12:20 +01:00
*/
2013-10-30 12:27:00 +00:00
public function clearOutput ()
2013-06-16 10:12:20 +01:00
{
2016-01-18 16:23:05 +00:00
ftruncate ( $this -> stdout , 0 );
fseek ( $this -> stdout , 0 );
2013-06-16 10:12:20 +01:00
$this -> incrementalOutputOffset = 0 ;
return $this ;
}
2010-05-06 12:25:53 +01:00
/**
2012-03-21 16:58:02 +00:00
* Returns the current error output of the process ( STDERR ) .
2010-05-06 12:25:53 +01:00
*
* @ return string The process error output
2011-03-24 08:13:58 +00:00
*
2014-03-26 11:51:10 +00:00
* @ throws LogicException in case the output has been disabled
2013-11-05 22:13:55 +00:00
* @ throws LogicException In case the process is not started
2010-05-06 12:25:53 +01:00
*/
public function getErrorOutput ()
2010-04-17 13:49:58 +01:00
{
2016-03-05 15:49:11 +00:00
$this -> readPipesForOutput ( __FUNCTION__ );
2012-03-21 16:58:02 +00:00
2016-01-18 16:23:05 +00:00
if ( false === $ret = stream_get_contents ( $this -> stderr , - 1 , 0 )) {
return '' ;
}
return $ret ;
2010-04-17 13:49:58 +01:00
}
2012-09-19 01:35:24 +01:00
/**
* 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 .
*
2016-06-06 10:38:47 +01:00
* @ return string The process error output since the last call
*
2014-03-26 11:51:10 +00:00
* @ throws LogicException in case the output has been disabled
2014-03-18 15:17:31 +00:00
* @ throws LogicException In case the process is not started
2012-09-19 01:35:24 +01:00
*/
public function getIncrementalErrorOutput ()
{
2016-03-05 15:49:11 +00:00
$this -> readPipesForOutput ( __FUNCTION__ );
2012-09-19 01:35:24 +01:00
2016-01-18 16:23:05 +00:00
$latest = stream_get_contents ( $this -> stderr , - 1 , $this -> incrementalErrorOutputOffset );
$this -> incrementalErrorOutputOffset = ftell ( $this -> stderr );
2014-12-31 00:27:17 +00:00
if ( false === $latest ) {
return '' ;
}
2012-09-19 01:35:24 +01:00
return $latest ;
}
2013-06-16 10:12:20 +01:00
/**
* Clears the process output .
*
2016-12-26 07:50:27 +00:00
* @ return $this
2013-06-16 10:12:20 +01:00
*/
2013-10-30 12:27:00 +00:00
public function clearErrorOutput ()
2013-06-16 10:12:20 +01:00
{
2016-01-18 16:23:05 +00:00
ftruncate ( $this -> stderr , 0 );
fseek ( $this -> stderr , 0 );
2013-06-16 10:12:20 +01:00
$this -> incrementalErrorOutputOffset = 0 ;
return $this ;
}
2010-05-06 12:25:53 +01:00
/**
* Returns the exit code returned by the process .
*
2014-11-30 13:33:44 +00:00
* @ return null | int The exit status code , null if the Process is not terminated
2011-03-24 08:13:58 +00:00
*
2012-09-09 15:38:21 +01:00
* @ throws RuntimeException In case -- enable - sigchild is activated and the sigchild compatibility mode is disabled
2010-05-06 12:25:53 +01:00
*/
public function getExitCode ()
2010-04-17 13:49:58 +01:00
{
2015-12-08 16:23:26 +00:00
if ( ! $this -> enhanceSigchildCompatibility && $this -> isSigchildEnabled ()) {
2014-03-11 16:00:25 +00:00
throw new RuntimeException ( 'This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.' );
2012-09-09 15:38:21 +01:00
}
2013-08-13 17:00:31 +01:00
$this -> updateStatus ( false );
2012-03-21 16:58:02 +00:00
2010-05-06 12:25:53 +01:00
return $this -> exitcode ;
}
2010-04-17 13:49:58 +01:00
2012-02-13 06:31:37 +00:00
/**
* 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 .
*
2016-06-28 06:50:50 +01:00
* @ return null | string A string representation for the exit status code , null if the Process is not terminated
2014-03-18 15:17:31 +00:00
*
2012-02-13 06:31:37 +00:00
* @ see http :// tldp . org / LDP / abs / html / exitcodes . html
* @ see http :// en . wikipedia . org / wiki / Unix_signal
*/
public function getExitCodeText ()
{
2014-03-18 15:17:31 +00:00
if ( null === $exitcode = $this -> getExitCode ()) {
return ;
}
2012-03-21 16:58:02 +00:00
2012-09-09 15:38:21 +01:00
return isset ( self :: $exitCodes [ $exitcode ]) ? self :: $exitCodes [ $exitcode ] : 'Unknown error' ;
2012-02-13 06:31:37 +00:00
}
2010-09-16 07:55:44 +01:00
/**
* Checks if the process ended successfully .
*
2014-11-30 13:33:44 +00:00
* @ return bool true if the process ended successfully , false otherwise
2010-09-16 07:55:44 +01:00
*/
public function isSuccessful ()
{
2013-08-21 15:50:47 +01:00
return 0 === $this -> getExitCode ();
2010-09-16 07:55:44 +01:00
}
2010-05-06 12:25:53 +01:00
/**
* Returns true if the child process has been terminated by an uncaught signal .
*
* It always returns false on Windows .
*
2014-04-16 11:30:19 +01:00
* @ return bool
2011-03-24 08:13:58 +00:00
*
2012-09-09 15:38:21 +01:00
* @ throws RuntimeException In case -- enable - sigchild is activated
2014-03-18 20:00:58 +00:00
* @ throws LogicException In case the process is not terminated
2010-05-06 12:25:53 +01:00
*/
public function hasBeenSignaled ()
{
2014-03-18 15:17:31 +00:00
$this -> requireProcessIsTerminated ( __FUNCTION__ );
2015-12-08 16:23:26 +00:00
if ( ! $this -> enhanceSigchildCompatibility && $this -> isSigchildEnabled ()) {
2014-03-11 16:00:25 +00:00
throw new RuntimeException ( 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.' );
2012-09-09 15:38:21 +01:00
}
2012-03-21 16:58:02 +00:00
return $this -> processInformation [ 'signaled' ];
2010-05-06 12:25:53 +01:00
}
2010-04-17 13:49:58 +01:00
2010-05-06 12:25:53 +01:00
/**
* Returns the number of the signal that caused the child process to terminate its execution .
*
* It is only meaningful if hasBeenSignaled () returns true .
*
2014-04-16 11:30:19 +01:00
* @ return int
2011-03-24 08:13:58 +00:00
*
2012-09-09 15:38:21 +01:00
* @ throws RuntimeException In case -- enable - sigchild is activated
2014-03-18 20:00:58 +00:00
* @ throws LogicException In case the process is not terminated
2010-05-06 12:25:53 +01:00
*/
public function getTermSignal ()
{
2014-03-18 15:17:31 +00:00
$this -> requireProcessIsTerminated ( __FUNCTION__ );
2015-12-08 16:23:26 +00:00
if ( $this -> isSigchildEnabled () && ( ! $this -> enhanceSigchildCompatibility || - 1 === $this -> processInformation [ 'termsig' ])) {
2014-03-11 16:00:25 +00:00
throw new RuntimeException ( 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.' );
2012-09-09 15:38:21 +01:00
}
2012-03-21 16:58:02 +00:00
return $this -> processInformation [ 'termsig' ];
2010-04-17 13:49:58 +01:00
}
2010-05-06 12:25:53 +01:00
/**
* Returns true if the child process has been stopped by a signal .
*
* It always returns false on Windows .
*
2014-04-16 11:30:19 +01:00
* @ return bool
2011-03-24 08:13:58 +00:00
*
2014-03-18 20:00:58 +00:00
* @ throws LogicException In case the process is not terminated
2010-05-06 12:25:53 +01:00
*/
public function hasBeenStopped ()
{
2014-03-18 15:17:31 +00:00
$this -> requireProcessIsTerminated ( __FUNCTION__ );
2012-03-21 16:58:02 +00:00
return $this -> processInformation [ 'stopped' ];
2010-05-06 12:25:53 +01:00
}
2010-04-17 13:49:58 +01:00
2010-05-06 12:25:53 +01:00
/**
2012-10-06 13:46:45 +01:00
* Returns the number of the signal that caused the child process to stop its execution .
2010-05-06 12:25:53 +01:00
*
* It is only meaningful if hasBeenStopped () returns true .
*
2014-04-16 11:30:19 +01:00
* @ return int
2011-03-24 08:13:58 +00:00
*
2014-03-18 20:00:58 +00:00
* @ throws LogicException In case the process is not terminated
2010-05-06 12:25:53 +01:00
*/
public function getStopSignal ()
{
2014-03-18 15:17:31 +00:00
$this -> requireProcessIsTerminated ( __FUNCTION__ );
2012-03-21 16:58:02 +00:00
return $this -> processInformation [ 'stopsig' ];
}
/**
2012-03-23 11:59:10 +00:00
* Checks if the process is currently running .
*
2014-11-30 13:33:44 +00:00
* @ return bool true if the process is currently running , false otherwise
2012-03-21 16:58:02 +00:00
*/
2012-03-23 11:59:10 +00:00
public function isRunning ()
2012-03-23 11:06:08 +00:00
{
2012-03-23 11:59:10 +00:00
if ( self :: STATUS_STARTED !== $this -> status ) {
return false ;
}
2013-08-13 17:00:31 +01:00
$this -> updateStatus ( false );
2012-03-21 16:58:02 +00:00
2012-03-23 11:59:10 +00:00
return $this -> processInformation [ 'running' ];
2012-03-21 16:58:02 +00:00
}
2012-10-27 17:54:34 +01:00
/**
* Checks if the process has been started with no regard to the current state .
*
2014-11-30 13:33:44 +00:00
* @ return bool true if status is ready , false otherwise
2012-10-27 17:54:34 +01:00
*/
public function isStarted ()
{
return $this -> status != self :: STATUS_READY ;
}
/**
* Checks if the process is terminated .
*
2014-11-30 13:33:44 +00:00
* @ return bool true if process is terminated , false otherwise
2012-10-27 17:54:34 +01:00
*/
public function isTerminated ()
{
2013-08-13 17:00:31 +01:00
$this -> updateStatus ( false );
2012-10-27 17:54:34 +01:00
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 ()
{
2013-08-13 17:00:31 +01:00
$this -> updateStatus ( false );
2012-10-27 17:54:34 +01:00
return $this -> status ;
}
2012-03-21 16:58:02 +00:00
/**
2012-03-23 11:59:10 +00:00
* Stops the process .
*
2014-11-30 13:33:44 +00:00
* @ param int | float $timeout The timeout in seconds
2015-11-30 08:52:07 +00:00
* @ param int $signal A POSIX signal to send in case the process has not stop at timeout , default is SIGKILL ( 9 )
2012-03-23 11:59:10 +00:00
*
2014-11-30 13:33:44 +00:00
* @ return int The exit - code of the process
2012-03-21 16:58:02 +00:00
*/
2012-09-09 15:56:43 +01:00
public function stop ( $timeout = 10 , $signal = null )
2012-03-23 11:06:08 +00:00
{
2013-08-13 18:23:51 +01:00
$timeoutMicro = microtime ( true ) + $timeout ;
2012-03-23 11:06:08 +00:00
if ( $this -> isRunning ()) {
2014-07-22 17:04:22 +01:00
// 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 );
2013-08-14 16:41:18 +01:00
do {
2012-03-21 16:58:02 +00:00
usleep ( 1000 );
2013-08-14 16:41:18 +01:00
} while ( $this -> isRunning () && microtime ( true ) < $timeoutMicro );
2012-04-08 19:27:37 +01:00
2015-12-08 16:23:26 +00:00
if ( $this -> isRunning ()) {
2015-11-30 08:52:07 +00:00
// 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 );
2013-04-06 18:59:02 +01:00
}
2013-09-16 12:32:46 +01:00
}
2013-04-06 18:59:02 +01:00
2016-01-06 08:44:17 +00:00
if ( $this -> isRunning ()) {
if ( isset ( $this -> fallbackStatus [ 'pid' ])) {
unset ( $this -> fallbackStatus [ 'pid' ]);
return $this -> stop ( 0 , $signal );
}
2013-09-16 12:32:46 +01:00
$this -> close ();
2012-03-21 16:58:02 +00:00
}
2013-09-16 12:32:46 +01:00
2012-03-21 16:58:02 +00:00
return $this -> exitcode ;
2010-05-06 12:25:53 +01:00
}
2010-04-17 13:49:58 +01:00
2012-10-06 05:39:50 +01:00
/**
2012-10-06 13:46:45 +01:00
* Adds a line to the STDOUT stream .
2012-10-06 05:39:50 +01:00
*
2016-01-18 16:23:05 +00:00
* @ internal
*
2012-10-06 05:39:50 +01:00
* @ param string $line The line to append
*/
2010-05-06 12:25:53 +01:00
public function addOutput ( $line )
2010-04-17 13:49:58 +01:00
{
2013-08-02 20:20:26 +01:00
$this -> lastOutputTime = microtime ( true );
2016-01-20 06:45:12 +00:00
2016-01-18 16:23:05 +00:00
fseek ( $this -> stdout , 0 , SEEK_END );
fwrite ( $this -> stdout , $line );
fseek ( $this -> stdout , $this -> incrementalOutputOffset );
2010-04-17 13:49:58 +01:00
}
2012-10-06 05:39:50 +01:00
/**
2012-10-06 13:46:45 +01:00
* Adds a line to the STDERR stream .
2012-10-06 05:39:50 +01:00
*
2016-01-18 16:23:05 +00:00
* @ internal
*
2012-10-06 05:39:50 +01:00
* @ param string $line The line to append
*/
2010-05-06 12:25:53 +01:00
public function addErrorOutput ( $line )
{
2013-08-02 20:20:26 +01:00
$this -> lastOutputTime = microtime ( true );
2016-01-20 06:45:12 +00:00
2016-01-18 16:23:05 +00:00
fseek ( $this -> stderr , 0 , SEEK_END );
fwrite ( $this -> stderr , $line );
fseek ( $this -> stderr , $this -> incrementalErrorOutputOffset );
2010-05-06 12:25:53 +01:00
}
2011-03-24 08:10:42 +00:00
2012-10-06 05:39:50 +01:00
/**
2012-10-06 13:46:45 +01:00
* Gets the command line to be executed .
2012-10-06 05:39:50 +01:00
*
* @ return string The command to execute
*/
2011-03-24 08:15:33 +00:00
public function getCommandLine ()
{
2017-04-20 15:08:24 +01:00
return is_array ( $this -> commandline ) ? implode ( ' ' , array_map ( array ( $this , 'escapeArgument' ), $this -> commandline )) : $this -> commandline ;
2011-03-24 08:15:33 +00:00
}
2012-10-03 15:02:00 +01:00
/**
2012-10-06 13:46:45 +01:00
* Sets the command line to be executed .
2012-10-06 05:39:50 +01:00
*
2017-01-12 17:50:31 +00:00
* @ param string | array $commandline The command to execute
2012-10-06 20:57:59 +01:00
*
2012-10-03 15:02:00 +01:00
* @ return self The current Process instance
*/
2011-03-24 08:10:42 +00:00
public function setCommandLine ( $commandline )
{
$this -> commandline = $commandline ;
2012-09-16 04:07:09 +01:00
return $this ;
2011-03-24 08:10:42 +00:00
}
2011-04-02 09:08:01 +01:00
2012-10-06 05:39:50 +01:00
/**
2014-01-21 09:48:43 +00:00
* Gets the process timeout ( max . runtime ) .
2012-10-06 05:39:50 +01:00
*
2013-08-02 20:20:26 +01:00
* @ return float | null The timeout in seconds or null if it ' s disabled
2012-10-06 05:39:50 +01:00
*/
2011-04-02 09:08:01 +01:00
public function getTimeout ()
{
return $this -> timeout ;
}
2013-08-02 20:20:26 +01:00
/**
2014-01-21 09:48:43 +00:00
* Gets the process idle timeout ( max . time since last output ) .
2013-08-02 20:20:26 +01:00
*
2014-01-21 09:48:43 +00:00
* @ return float | null The timeout in seconds or null if it ' s disabled
2013-08-02 20:20:26 +01:00
*/
public function getIdleTimeout ()
{
return $this -> idleTimeout ;
}
2012-07-11 08:39:13 +01:00
/**
2014-01-21 09:48:43 +00:00
* Sets the process timeout ( max . runtime ) .
2012-07-11 08:39:13 +01:00
*
* To disable the timeout , set this value to null .
*
2014-11-30 13:33:44 +00:00
* @ param int | float | null $timeout The timeout in seconds
2012-10-03 15:02:00 +01:00
*
* @ return self The current Process instance
2012-10-06 20:57:59 +01:00
*
2012-12-16 12:02:54 +00:00
* @ throws InvalidArgumentException if the timeout is negative
2012-07-11 08:39:13 +01:00
*/
2011-04-02 09:08:01 +01:00
public function setTimeout ( $timeout )
{
2013-08-02 20:20:26 +01:00
$this -> timeout = $this -> validateTimeout ( $timeout );
2012-07-10 14:21:23 +01:00
2013-08-02 20:20:26 +01:00
return $this ;
}
2012-07-10 14:21:23 +01:00
2013-08-02 20:20:26 +01:00
/**
2014-01-21 09:48:43 +00:00
* Sets the process idle timeout ( max . time since last output ) .
*
* To disable the timeout , set this value to null .
2013-08-02 20:20:26 +01:00
*
2014-12-04 20:26:11 +00:00
* @ param int | float | null $timeout The timeout in seconds
2013-08-02 20:20:26 +01:00
*
2016-06-28 06:50:50 +01:00
* @ return self The current Process instance
2013-08-02 20:20:26 +01:00
*
2014-09-03 18:33:24 +01:00
* @ throws LogicException if the output is disabled
2013-08-02 20:20:26 +01:00
* @ throws InvalidArgumentException if the timeout is negative
*/
public function setIdleTimeout ( $timeout )
{
2014-03-14 18:08:20 +00:00
if ( null !== $timeout && $this -> outputDisabled ) {
throw new LogicException ( 'Idle timeout can not be set while the output is disabled.' );
}
2013-08-02 20:20:26 +01:00
$this -> idleTimeout = $this -> validateTimeout ( $timeout );
2012-09-16 04:07:09 +01:00
return $this ;
2011-04-02 09:08:01 +01:00
}
2013-04-05 11:35:05 +01:00
/**
2013-04-20 13:33:48 +01:00
* Enables or disables the TTY mode .
2013-04-05 11:35:05 +01:00
*
2014-11-30 13:33:44 +00:00
* @ param bool $tty True to enabled and false to disable
2013-04-05 11:35:05 +01:00
*
* @ return self The current Process instance
2014-04-22 21:38:30 +01:00
*
* @ throws RuntimeException In case the TTY mode is not supported
2013-04-05 11:35:05 +01:00
*/
2013-04-20 13:33:48 +01:00
public function setTty ( $tty )
2013-04-05 11:35:05 +01:00
{
2014-12-29 12:05:07 +00:00
if ( '\\' === DIRECTORY_SEPARATOR && $tty ) {
2014-04-22 21:38:30 +01:00
throw new RuntimeException ( 'TTY mode is not supported on Windows platform.' );
}
2016-11-21 08:40:36 +00:00
if ( $tty ) {
static $isTtySupported ;
if ( null === $isTtySupported ) {
$isTtySupported = ( bool ) @ proc_open ( 'echo 1 >/dev/null' , array ( array ( 'file' , '/dev/tty' , 'r' ), array ( 'file' , '/dev/tty' , 'w' ), array ( 'file' , '/dev/tty' , 'w' )), $pipes );
}
if ( ! $isTtySupported ) {
throw new RuntimeException ( 'TTY mode requires /dev/tty to be read/writable.' );
}
2015-02-13 15:10:22 +00:00
}
2014-04-22 21:38:30 +01:00
2014-04-12 18:44:00 +01:00
$this -> tty = ( bool ) $tty ;
2013-04-05 11:35:05 +01:00
return $this ;
}
/**
2014-01-07 09:02:59 +00:00
* Checks if the TTY mode is enabled .
2013-04-05 11:35:05 +01:00
*
2014-11-30 13:33:44 +00:00
* @ return bool true if the TTY mode is enabled , false otherwise
2013-04-05 11:35:05 +01:00
*/
2013-04-20 13:33:48 +01:00
public function isTty ()
2013-04-05 11:35:05 +01:00
{
return $this -> tty ;
}
2013-08-03 08:10:03 +01:00
/**
* Sets PTY mode .
*
2014-12-04 20:26:11 +00:00
* @ param bool $bool
2013-08-03 08:10:03 +01:00
*
* @ return self
*/
public function setPty ( $bool )
{
2014-04-16 09:09:01 +01:00
$this -> pty = ( bool ) $bool ;
2013-08-03 08:10:03 +01:00
return $this ;
}
/**
* Returns PTY state .
*
2014-04-16 11:36:34 +01:00
* @ return bool
2013-08-03 08:10:03 +01:00
*/
public function isPty ()
{
return $this -> pty ;
}
2012-10-06 05:39:50 +01:00
/**
2012-10-06 13:46:45 +01:00
* Gets the working directory .
2012-10-06 05:39:50 +01:00
*
2013-11-08 00:33:52 +00:00
* @ return string | null The current working directory or null on failure
2012-10-06 05:39:50 +01:00
*/
2011-04-02 09:08:01 +01:00
public function getWorkingDirectory ()
{
2013-01-08 12:27:17 +00:00
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 ;
}
2011-04-02 09:08:01 +01:00
return $this -> cwd ;
}
2012-10-03 15:02:00 +01:00
/**
2012-10-06 13:46:45 +01:00
* Sets the current working directory .
2012-10-06 05:39:50 +01:00
*
* @ param string $cwd The new working directory
2012-10-06 20:57:59 +01:00
*
2012-10-03 15:02:00 +01:00
* @ return self The current Process instance
*/
2011-04-02 09:08:01 +01:00
public function setWorkingDirectory ( $cwd )
{
$this -> cwd = $cwd ;
2012-09-16 04:07:09 +01:00
return $this ;
2011-04-02 09:08:01 +01:00
}
2012-10-06 05:39:50 +01:00
/**
2012-10-06 13:46:45 +01:00
* Gets the environment variables .
2012-10-06 05:39:50 +01:00
*
* @ return array The current environment variables
*/
2011-04-02 09:08:01 +01:00
public function getEnv ()
{
return $this -> env ;
}
2012-10-03 15:02:00 +01:00
/**
2012-10-06 13:46:45 +01:00
* Sets the environment variables .
2012-10-06 05:39:50 +01:00
*
2013-06-13 07:50:29 +01:00
* An environment variable value should be a string .
* If it is an array , the variable is ignored .
2017-01-31 09:03:29 +00:00
* If it is false or null , it will be removed when
* env vars are otherwise inherited .
2013-06-13 07:50:29 +01:00
*
* That happens in PHP when 'argv' is registered into
* the $_ENV array for instance .
*
2012-10-06 05:39:50 +01:00
* @ param array $env The new environment variables
2012-10-06 20:57:59 +01:00
*
2012-10-03 15:02:00 +01:00
* @ return self The current Process instance
*/
2011-04-02 09:08:01 +01:00
public function setEnv ( array $env )
{
2013-06-13 07:50:29 +01:00
// Process can not handle env values that are arrays
2014-02-21 07:41:38 +00:00
$env = array_filter ( $env , function ( $value ) {
return ! is_array ( $value );
});
2013-06-13 07:50:29 +01:00
2017-01-31 09:03:29 +00:00
$this -> env = $env ;
2012-09-16 04:07:09 +01:00
return $this ;
2011-04-02 09:08:01 +01:00
}
2014-05-17 19:36:38 +01:00
/**
* Gets the Process input .
*
2016-03-31 16:15:42 +01:00
* @ return resource | string | \Iterator | null The Process input
2014-05-17 19:36:38 +01:00
*/
public function getInput ()
{
return $this -> input ;
2011-04-02 09:08:01 +01:00
}
2014-05-17 19:36:38 +01:00
/**
* Sets the input .
*
* This content will be passed to the underlying process standard input .
*
2016-03-31 16:15:42 +01:00
* @ param resource | scalar | \Traversable | null $input The content
2014-05-17 19:36:38 +01:00
*
* @ return self The current Process instance
*
* @ throws LogicException In case the process is running
*/
public function setInput ( $input )
2011-04-02 09:08:01 +01:00
{
2014-04-21 22:11:42 +01:00
if ( $this -> isRunning ()) {
2014-05-17 19:36:38 +01:00
throw new LogicException ( 'Input can not be set while the process is running.' );
2014-04-21 22:11:42 +01:00
}
2016-04-02 08:48:01 +01:00
$this -> input = ProcessUtils :: validateInput ( __METHOD__ , $input );
2012-09-16 04:07:09 +01:00
return $this ;
2011-04-02 09:08:01 +01:00
}
2012-10-06 05:39:50 +01:00
/**
2012-10-06 13:46:45 +01:00
* Gets the options for proc_open .
2012-10-06 05:39:50 +01:00
*
* @ return array The current options
2017-01-31 09:03:29 +00:00
*
* @ deprecated since version 3.3 , to be removed in 4.0 .
2012-10-06 05:39:50 +01:00
*/
2011-04-02 09:08:01 +01:00
public function getOptions ()
{
2017-01-31 09:03:29 +00:00
@ trigger_error ( sprintf ( 'The %s() method is deprecated since version 3.3 and will be removed in 4.0.' , __METHOD__ ), E_USER_DEPRECATED );
2011-04-02 09:08:01 +01:00
return $this -> options ;
}
2012-10-03 15:02:00 +01:00
/**
2012-10-06 13:46:45 +01:00
* Sets the options for proc_open .
2012-10-06 05:39:50 +01:00
*
* @ param array $options The new options
2012-10-06 20:57:59 +01:00
*
2012-10-03 15:02:00 +01:00
* @ return self The current Process instance
2017-01-31 09:03:29 +00:00
*
* @ deprecated since version 3.3 , to be removed in 4.0 .
2012-10-03 15:02:00 +01:00
*/
2011-04-02 09:08:01 +01:00
public function setOptions ( array $options )
{
2017-01-31 09:03:29 +00:00
@ trigger_error ( sprintf ( 'The %s() method is deprecated since version 3.3 and will be removed in 4.0.' , __METHOD__ ), E_USER_DEPRECATED );
2011-04-02 09:08:01 +01:00
$this -> options = $options ;
2012-09-16 04:07:09 +01:00
return $this ;
2011-04-02 09:08:01 +01:00
}
2012-02-16 18:16:07 +00:00
2012-10-06 05:44:21 +01:00
/**
2013-04-20 13:33:48 +01:00
* Gets whether or not Windows compatibility is enabled .
2012-10-06 05:44:21 +01:00
*
* This is true by default .
*
2014-04-16 11:30:19 +01:00
* @ return bool
2017-01-31 09:03:29 +00:00
*
* @ deprecated since version 3.3 , to be removed in 4.0 . Enhanced Windows compatibility will always be enabled .
2012-10-06 05:44:21 +01:00
*/
2012-02-16 18:16:07 +00:00
public function getEnhanceWindowsCompatibility ()
{
2017-01-31 09:03:29 +00:00
@ trigger_error ( sprintf ( 'The %s() method is deprecated since version 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.' , __METHOD__ ), E_USER_DEPRECATED );
2012-02-16 18:16:07 +00:00
return $this -> enhanceWindowsCompatibility ;
}
2012-10-03 15:02:00 +01:00
/**
2013-04-20 13:33:48 +01:00
* Sets whether or not Windows compatibility is enabled .
2012-10-06 05:44:21 +01:00
*
2014-11-30 13:33:44 +00:00
* @ param bool $enhance
2012-10-06 20:57:59 +01:00
*
2012-10-03 15:02:00 +01:00
* @ return self The current Process instance
2017-01-31 09:03:29 +00:00
*
* @ deprecated since version 3.3 , to be removed in 4.0 . Enhanced Windows compatibility will always be enabled .
2012-10-03 15:02:00 +01:00
*/
2012-02-16 18:16:07 +00:00
public function setEnhanceWindowsCompatibility ( $enhance )
{
2017-01-31 09:03:29 +00:00
@ trigger_error ( sprintf ( 'The %s() method is deprecated since version 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.' , __METHOD__ ), E_USER_DEPRECATED );
2014-04-12 18:44:00 +01:00
$this -> enhanceWindowsCompatibility = ( bool ) $enhance ;
2012-09-16 04:07:09 +01:00
2012-10-03 15:02:00 +01:00
return $this ;
2012-02-16 18:16:07 +00:00
}
2012-03-21 16:58:02 +00:00
2012-09-09 15:38:21 +01:00
/**
2013-04-20 13:33:48 +01:00
* Returns whether sigchild compatibility mode is activated or not .
2012-09-09 15:38:21 +01:00
*
2014-04-16 11:30:19 +01:00
* @ return bool
2017-01-31 09:03:29 +00:00
*
* @ deprecated since version 3.3 , to be removed in 4.0 . Sigchild compatibility will always be enabled .
2012-09-09 15:38:21 +01:00
*/
public function getEnhanceSigchildCompatibility ()
{
2017-01-31 09:03:29 +00:00
@ trigger_error ( sprintf ( 'The %s() method is deprecated since version 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.' , __METHOD__ ), E_USER_DEPRECATED );
2012-09-09 15:38:21 +01:00
return $this -> enhanceSigchildCompatibility ;
}
/**
2013-04-20 13:33:48 +01:00
* Activates sigchild compatibility mode .
2012-09-09 15:38:21 +01:00
*
* 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
*
2014-11-30 13:33:44 +00:00
* @ param bool $enhance
2012-10-03 15:02:00 +01:00
*
* @ return self The current Process instance
2017-01-31 09:03:29 +00:00
*
* @ deprecated since version 3.3 , to be removed in 4.0 .
2012-09-09 15:38:21 +01:00
*/
public function setEnhanceSigchildCompatibility ( $enhance )
{
2017-01-31 09:03:29 +00:00
@ trigger_error ( sprintf ( 'The %s() method is deprecated since version 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.' , __METHOD__ ), E_USER_DEPRECATED );
2014-04-12 18:44:00 +01:00
$this -> enhanceSigchildCompatibility = ( bool ) $enhance ;
2012-10-03 15:02:00 +01:00
return $this ;
2012-09-09 15:38:21 +01:00
}
2016-06-14 14:16:11 +01:00
/**
* Sets whether environment variables will be inherited or not .
*
* @ param bool $inheritEnv
*
* @ return self The current Process instance
*/
public function inheritEnvironmentVariables ( $inheritEnv = true )
{
2017-01-31 09:03:29 +00:00
if ( ! $inheritEnv ) {
2017-02-28 14:54:44 +00:00
@ 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 );
2017-01-31 09:03:29 +00:00
}
2016-06-14 14:16:11 +01:00
$this -> inheritEnv = ( bool ) $inheritEnv ;
return $this ;
}
/**
* Returns whether environment variables will be inherited or not .
*
* @ return bool
2017-01-31 09:03:29 +00:00
*
* @ deprecated since version 3.3 , to be removed in 4.0 . Environment variables will always be inherited .
2016-06-14 14:16:11 +01:00
*/
public function areEnvironmentVariablesInherited ()
{
2017-01-31 09:03:29 +00:00
@ trigger_error ( sprintf ( 'The %s() method is deprecated since version 3.3 and will be removed in 4.0. Environment variables will always be inherited.' , __METHOD__ ), E_USER_DEPRECATED );
2016-06-14 14:16:11 +01:00
return $this -> inheritEnv ;
}
2013-04-06 19:51:55 +01:00
/**
2013-04-20 13:33:48 +01:00
* Performs a check between the timeout definition and the time the process started .
2013-04-06 19:51:55 +01:00
*
* In case you run a background process ( with the start method ), you should
* trigger this method regularly to ensure the process timeout
*
2013-08-12 13:18:24 +01:00
* @ throws ProcessTimedOutException In case the timeout was reached
2013-04-06 19:51:55 +01:00
*/
public function checkTimeout ()
{
2014-03-18 15:17:31 +00:00
if ( $this -> status !== self :: STATUS_STARTED ) {
return ;
}
2013-11-08 00:33:52 +00:00
if ( null !== $this -> timeout && $this -> timeout < microtime ( true ) - $this -> starttime ) {
2013-04-06 19:51:55 +01:00
$this -> stop ( 0 );
2013-04-07 15:10:23 +01:00
2013-08-02 20:20:26 +01:00
throw new ProcessTimedOutException ( $this , ProcessTimedOutException :: TYPE_GENERAL );
}
2014-01-21 09:48:43 +00:00
if ( null !== $this -> idleTimeout && $this -> idleTimeout < microtime ( true ) - $this -> lastOutputTime ) {
2013-08-02 20:20:26 +01:00
$this -> stop ( 0 );
throw new ProcessTimedOutException ( $this , ProcessTimedOutException :: TYPE_IDLE );
2013-04-06 19:51:55 +01:00
}
}
2013-04-05 11:35:05 +01:00
/**
2014-02-03 08:11:05 +00:00
* Returns whether PTY is supported on the current operating system .
2013-04-05 11:35:05 +01:00
*
2014-04-16 11:36:34 +01:00
* @ return bool
2013-04-05 11:35:05 +01:00
*/
2014-02-03 08:11:05 +00:00
public static function isPtySupported ()
2013-04-05 11:35:05 +01:00
{
2014-02-03 08:11:05 +00:00
static $result ;
2013-07-01 13:24:43 +01:00
2014-02-03 08:11:05 +00:00
if ( null !== $result ) {
return $result ;
2013-07-01 13:24:43 +01:00
}
2013-04-05 11:35:05 +01:00
2014-12-29 12:11:25 +00:00
if ( '\\' === DIRECTORY_SEPARATOR ) {
2014-02-03 08:11:05 +00:00
return $result = false ;
2013-04-05 11:35:05 +01:00
}
2016-11-21 08:40:36 +00:00
return $result = ( bool ) @ proc_open ( 'echo 1 >/dev/null' , array ( array ( 'pty' ), array ( 'pty' ), array ( 'pty' )), $pipes );
2014-02-03 08:11:05 +00:00
}
2013-04-05 11:35:05 +01:00
/**
2013-04-20 13:33:48 +01:00
* Creates the descriptors needed by the proc_open .
2013-04-05 11:35:05 +01:00
*
* @ return array
*/
private function getDescriptors ()
{
2016-03-29 15:14:08 +01:00
if ( $this -> input instanceof \Iterator ) {
$this -> input -> rewind ();
}
2014-12-29 12:15:05 +00:00
if ( '\\' === DIRECTORY_SEPARATOR ) {
2016-04-03 16:15:29 +01:00
$this -> processPipes = new WindowsPipes ( $this -> input , ! $this -> outputDisabled || $this -> hasCallback );
2014-04-22 22:51:46 +01:00
} else {
2016-04-03 16:15:29 +01:00
$this -> processPipes = new UnixPipes ( $this -> isTty (), $this -> isPty (), $this -> input , ! $this -> outputDisabled || $this -> hasCallback );
2014-04-22 22:51:46 +01:00
}
2013-04-05 11:35:05 +01:00
2016-01-18 17:32:04 +00:00
return $this -> processPipes -> getDescriptors ();
2013-04-05 11:35:05 +01:00
}
2012-03-21 16:58:02 +00:00
/**
2012-03-23 12:02:23 +00:00
* Builds up the callback used by wait () .
2012-03-23 11:59:10 +00:00
*
2012-07-28 23:02:29 +01:00
* The callbacks adds all occurred output to the specific buffer and calls
* the user callback ( if present ) with the received output .
2012-03-23 11:59:10 +00:00
*
2013-11-20 02:35:05 +00:00
* @ param callable | null $callback The user defined PHP callback
2012-03-23 11:59:10 +00:00
*
2015-10-05 17:52:37 +01:00
* @ return \Closure A PHP closure
2012-03-21 16:58:02 +00:00
*/
2016-01-18 17:27:18 +00:00
protected function buildCallback ( callable $callback = null )
2012-03-23 11:59:10 +00:00
{
2016-01-18 17:27:18 +00:00
if ( $this -> outputDisabled ) {
return function ( $type , $data ) use ( $callback ) {
if ( null !== $callback ) {
call_user_func ( $callback , $type , $data );
}
};
}
2012-03-21 16:58:02 +00:00
$out = self :: OUT ;
2016-01-18 17:27:18 +00:00
return function ( $type , $data ) use ( $callback , $out ) {
2012-03-21 16:58:02 +00:00
if ( $out == $type ) {
2014-12-03 22:04:33 +00:00
$this -> addOutput ( $data );
2012-03-21 16:58:02 +00:00
} else {
2014-12-03 22:04:33 +00:00
$this -> addErrorOutput ( $data );
2012-03-21 16:58:02 +00:00
}
if ( null !== $callback ) {
call_user_func ( $callback , $type , $data );
}
};
}
/**
2013-08-13 17:00:31 +01:00
* Updates the status of the process , reads pipes .
*
2016-06-28 06:50:50 +01:00
* @ param bool $blocking Whether to use a blocking read call
2012-03-21 16:58:02 +00:00
*/
2013-08-13 17:00:31 +01:00
protected function updateStatus ( $blocking )
2012-03-21 16:58:02 +00:00
{
2012-03-23 11:59:10 +00:00
if ( self :: STATUS_STARTED !== $this -> status ) {
return ;
}
$this -> processInformation = proc_get_status ( $this -> process );
2016-03-13 10:06:10 +00:00
$running = $this -> processInformation [ 'running' ];
2013-10-09 21:51:36 +01:00
2016-03-13 10:06:10 +00:00
$this -> readPipes ( $running && $blocking , '\\' !== DIRECTORY_SEPARATOR || ! $running );
2013-10-09 14:14:24 +01:00
2015-12-08 16:23:26 +00:00
if ( $this -> fallbackStatus && $this -> enhanceSigchildCompatibility && $this -> isSigchildEnabled ()) {
$this -> processInformation = $this -> fallbackStatus + $this -> processInformation ;
}
2016-03-13 10:06:10 +00:00
if ( ! $running ) {
2013-08-13 19:21:22 +01:00
$this -> close ();
2012-03-21 16:58:02 +00:00
}
}
2012-05-24 18:53:48 +01:00
2012-09-09 15:38:21 +01:00
/**
2013-04-20 13:33:48 +01:00
* Returns whether PHP has been compiled with the '--enable-sigchild' option or not .
2012-09-09 15:38:21 +01:00
*
2014-04-16 11:30:19 +01:00
* @ return bool
2012-09-09 15:38:21 +01:00
*/
protected function isSigchildEnabled ()
{
if ( null !== self :: $sigchild ) {
return self :: $sigchild ;
}
2015-12-08 16:23:26 +00:00
if ( ! function_exists ( 'phpinfo' ) || defined ( 'HHVM_VERSION' )) {
2014-02-18 12:56:35 +00:00
return self :: $sigchild = false ;
}
2012-09-09 15:38:21 +01:00
ob_start ();
phpinfo ( INFO_GENERAL );
return self :: $sigchild = false !== strpos ( ob_get_clean (), '--enable-sigchild' );
}
2016-03-05 15:49:11 +00:00
/**
* Reads pipes for the freshest output .
*
2016-04-02 12:30:41 +01:00
* @ param string $caller The name of the method that needs fresh outputs
2016-06-29 06:42:25 +01:00
* @ param bool $blocking Whether to use blocking calls or not
2016-03-05 15:49:11 +00:00
*
2016-03-17 11:37:14 +00:00
* @ throws LogicException in case output has been disabled or process is not started
2016-03-05 15:49:11 +00:00
*/
2016-04-02 12:30:41 +01:00
private function readPipesForOutput ( $caller , $blocking = false )
2016-03-05 15:49:11 +00:00
{
if ( $this -> outputDisabled ) {
throw new LogicException ( 'Output has been disabled.' );
}
$this -> requireProcessIsStarted ( $caller );
2016-04-02 12:30:41 +01:00
$this -> updateStatus ( $blocking );
2016-03-05 15:49:11 +00:00
}
2013-08-02 20:20:26 +01:00
/**
* Validates and returns the filtered timeout .
*
2014-12-04 20:26:11 +00:00
* @ param int | float | null $timeout
2013-08-02 20:20:26 +01:00
*
* @ return float | null
2014-09-03 18:33:24 +01:00
*
* @ throws InvalidArgumentException if the given timeout is a negative number
2013-08-02 20:20:26 +01:00
*/
private function validateTimeout ( $timeout )
{
$timeout = ( float ) $timeout ;
2013-11-23 21:17:02 +00:00
if ( 0.0 === $timeout ) {
$timeout = null ;
} elseif ( $timeout < 0 ) {
2013-08-02 20:20:26 +01:00
throw new InvalidArgumentException ( 'The timeout value must be a valid positive integer or float number.' );
}
return $timeout ;
}
2013-08-14 14:08:25 +01:00
2013-08-13 17:00:31 +01:00
/**
* Reads pipes , executes callback .
*
2016-06-28 06:50:50 +01:00
* @ param bool $blocking Whether to use blocking calls or not
* @ param bool $close Whether to close file handles or not
2013-08-13 17:00:31 +01:00
*/
2013-10-09 14:14:24 +01:00
private function readPipes ( $blocking , $close )
2013-08-13 17:00:31 +01:00
{
2014-04-22 22:51:46 +01:00
$result = $this -> processPipes -> readAndWrite ( $blocking , $close );
2013-10-09 22:17:57 +01:00
2014-11-21 08:49:35 +00:00
$callback = $this -> callback ;
2013-10-09 14:14:24 +01:00
foreach ( $result as $type => $data ) {
2015-12-30 08:46:06 +00:00
if ( 3 !== $type ) {
2014-11-21 08:49:35 +00:00
$callback ( $type === self :: STDOUT ? self :: OUT : self :: ERR , $data );
2015-12-30 08:46:06 +00:00
} elseif ( ! isset ( $this -> fallbackStatus [ 'signaled' ])) {
$this -> fallbackStatus [ 'exitcode' ] = ( int ) $data ;
2013-08-14 11:09:40 +01:00
}
2013-08-13 17:00:31 +01:00
}
}
2013-08-13 19:21:22 +01:00
/**
* Closes process resource , closes file handles , sets the exitcode .
*
2014-11-30 13:33:44 +00:00
* @ return int The exitcode
2013-08-13 19:21:22 +01:00
*/
private function close ()
{
2013-10-09 22:17:57 +01:00
$this -> processPipes -> close ();
2013-08-13 19:21:22 +01:00
if ( is_resource ( $this -> process )) {
2015-12-08 16:23:26 +00:00
proc_close ( $this -> process );
2013-08-13 19:21:22 +01:00
}
2015-12-08 16:23:26 +00:00
$this -> exitcode = $this -> processInformation [ 'exitcode' ];
2014-03-18 20:00:58 +00:00
$this -> status = self :: STATUS_TERMINATED ;
2013-08-13 19:21:22 +01:00
2015-12-08 16:23:26 +00:00
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 ;
}
2013-08-13 19:21:22 +01:00
}
2015-12-02 09:09:02 +00:00
// 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 ;
2013-08-13 19:21:22 +01:00
return $this -> exitcode ;
}
2013-08-21 08:58:38 +01:00
2013-08-20 19:22:08 +01:00
/**
* Resets data related to the latest run of the process .
*/
private function resetProcessData ()
{
$this -> starttime = null ;
$this -> callback = null ;
$this -> exitcode = null ;
2015-12-08 16:23:26 +00:00
$this -> fallbackStatus = array ();
2013-08-20 19:22:08 +01:00
$this -> processInformation = null ;
2016-01-18 16:23:05 +00:00
$this -> stdout = fopen ( 'php://temp/maxmemory:' . ( 1024 * 1024 ), 'wb+' );
$this -> stderr = fopen ( 'php://temp/maxmemory:' . ( 1024 * 1024 ), 'wb+' );
2013-08-20 19:22:08 +01:00
$this -> process = null ;
2014-07-22 17:04:22 +01:00
$this -> latestSignal = null ;
2013-08-20 19:22:08 +01:00
$this -> status = self :: STATUS_READY ;
$this -> incrementalOutputOffset = 0 ;
$this -> incrementalErrorOutputOffset = 0 ;
}
2014-03-14 15:52:09 +00:00
/**
* Sends a POSIX signal to the process .
*
2014-11-30 13:33:44 +00:00
* @ param int $signal A valid POSIX signal ( see http :// www . php . net / manual / en / pcntl . constants . php )
* @ param bool $throwException Whether to throw exception in case signal failed
2014-03-18 20:00:58 +00:00
*
2014-11-30 13:33:44 +00:00
* @ return bool True if the signal was sent successfully , false otherwise
2014-03-14 15:52:09 +00:00
*
* @ throws LogicException In case the process is not running
2015-12-08 16:23:26 +00:00
* @ throws RuntimeException In case -- enable - sigchild is activated and the process can ' t be killed
2014-03-14 15:52:09 +00:00
* @ throws RuntimeException In case of failure
*/
private function doSignal ( $signal , $throwException )
{
2016-01-06 08:44:17 +00:00
if ( null === $pid = $this -> getPid ()) {
2014-03-14 15:52:09 +00:00
if ( $throwException ) {
throw new LogicException ( 'Can not send signal on a non running process.' );
}
return false ;
}
2015-11-30 08:52:07 +00:00
if ( '\\' === DIRECTORY_SEPARATOR ) {
2016-01-06 08:44:17 +00:00
exec ( sprintf ( 'taskkill /F /T /PID %d 2>&1' , $pid ), $output , $exitCode );
2015-12-02 16:43:18 +00:00
if ( $exitCode && $this -> isRunning ()) {
2015-11-30 08:52:07 +00:00
if ( $throwException ) {
throw new RuntimeException ( sprintf ( 'Unable to kill the process (%s).' , implode ( ' ' , $output )));
}
return false ;
}
2015-12-18 20:16:23 +00:00
} else {
if ( ! $this -> enhanceSigchildCompatibility || ! $this -> isSigchildEnabled ()) {
$ok = @ proc_terminate ( $this -> process , $signal );
} elseif ( function_exists ( 'posix_kill' )) {
2016-01-06 08:44:17 +00:00
$ok = @ posix_kill ( $pid , $signal );
} elseif ( $ok = proc_open ( sprintf ( 'kill -%d %d' , $signal , $pid ), array ( 2 => array ( 'pipe' , 'w' )), $pipes )) {
2015-12-18 20:16:23 +00:00
$ok = false === fgets ( $pipes [ 2 ]);
2014-03-14 15:52:09 +00:00
}
2015-12-18 20:16:23 +00:00
if ( ! $ok ) {
if ( $throwException ) {
throw new RuntimeException ( sprintf ( 'Error while sending signal `%s`.' , $signal ));
}
2014-03-14 15:52:09 +00:00
2015-12-18 20:16:23 +00:00
return false ;
}
2014-03-14 15:52:09 +00:00
}
2015-12-08 16:23:26 +00:00
$this -> latestSignal = ( int ) $signal ;
$this -> fallbackStatus [ 'signaled' ] = true ;
$this -> fallbackStatus [ 'exitcode' ] = - 1 ;
$this -> fallbackStatus [ 'termsig' ] = $this -> latestSignal ;
2014-07-22 17:04:22 +01:00
2014-03-14 15:52:09 +00:00
return true ;
}
2014-03-18 15:17:31 +00:00
2017-01-12 17:50:31 +00:00
private function prepareWindowsCommandLine ( $cmd , array & $envBackup )
{
$uid = uniqid ( '' , true );
$varCount = 0 ;
$varCache = array ();
$cmd = preg_replace_callback (
' / " (
[ ^ " %!^]*+
( ? :
( ? : ! LF ! | " (?: \ ^[%!^])?+ " )
[ ^ " %!^]*+
) ++
) " /x',
function ( $m ) use ( & $envBackup , & $varCache , & $varCount , $uid ) {
if ( isset ( $varCache [ $m [ 0 ]])) {
return $varCache [ $m [ 0 ]];
}
if ( false !== strpos ( $value = $m [ 1 ], " \0 " )) {
$value = str_replace ( " \0 " , '?' , $value );
}
if ( false === strpbrk ( $value , " \" %! \n " )) {
return '"' . $value . '"' ;
}
$value = str_replace ( array ( '!LF!' , '"^!"' , '"^%"' , '"^^"' , '""' ), array ( " \n " , '!' , '%' , '^' , '"' ), $value );
$value = preg_replace ( '/(\\\\*)"/' , '$1$1\\"' , $value );
$var = $uid .++ $varCount ;
putenv ( " $var = \" $value\ " " );
$envBackup [ $var ] = false ;
return $varCache [ $m [ 0 ]] = '!' . $var . '!' ;
},
$cmd
);
$cmd = 'cmd /V:ON /E:ON /D /C (' . str_replace ( " \n " , ' ' , $cmd ) . ')' ;
foreach ( $this -> processPipes -> getFiles () as $offset => $filename ) {
$cmd .= ' ' . $offset . '>"' . $filename . '"' ;
}
return $cmd ;
}
2014-03-18 15:17:31 +00:00
/**
* Ensures the process is running or terminated , throws a LogicException if the process has a not started .
*
2016-06-28 06:50:50 +01:00
* @ param string $functionName The function name that was called
2014-03-18 15:17:31 +00:00
*
* @ throws LogicException If the process has not run .
*/
private function requireProcessIsStarted ( $functionName )
{
if ( ! $this -> isStarted ()) {
throw new LogicException ( sprintf ( 'Process must be started before calling %s.' , $functionName ));
}
}
/**
* Ensures the process is terminated , throws a LogicException if the process has a status different than `terminated` .
*
2016-06-28 06:50:50 +01:00
* @ param string $functionName The function name that was called
2014-03-18 15:17:31 +00:00
*
* @ throws LogicException If the process is not yet terminated .
*/
private function requireProcessIsTerminated ( $functionName )
{
if ( ! $this -> isTerminated ()) {
throw new LogicException ( sprintf ( 'Process must be terminated before calling %s.' , $functionName ));
}
}
2017-01-12 17:50:31 +00:00
/**
* Escapes a string to be used as a shell argument .
*
* @ param string $argument The argument that will be escaped
*
* @ return string The escaped argument
*/
private function escapeArgument ( $argument )
{
if ( '\\' !== DIRECTORY_SEPARATOR ) {
return " ' " . str_replace ( " ' " , " ' \\ '' " , $argument ) . " ' " ;
}
if ( '' === $argument = ( string ) $argument ) {
return '""' ;
}
if ( false !== strpos ( $argument , " \0 " )) {
$argument = str_replace ( " \0 " , '?' , $argument );
}
if ( ! preg_match ( '/[()%!^"<>&|\s]/' , $argument )) {
return $argument ;
}
$argument = preg_replace ( '/(\\\\+)$/' , '$1$1' , $argument );
return '"' . str_replace ( array ( '"' , '^' , '%' , '!' , " \n " ), array ( '""' , '"^^"' , '"^%"' , '"^!"' , '!LF!' ), $argument ) . '"' ;
}
2010-04-17 13:49:58 +01:00
}