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-09-09 15:38:21 +01:00
use Symfony\Component\Process\Exception\RuntimeException ;
2010-04-17 13:49:58 +01:00
/**
* Process is a thin wrapper around proc_ * functions to ease
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 >
2011-03-24 08:13:58 +00:00
*
* @ api
2010-04-17 13:49:58 +01:00
*/
class Process
{
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 ;
2011-03-24 08:10:42 +00:00
private $commandline ;
private $cwd ;
private $env ;
private $stdin ;
private $timeout ;
private $options ;
private $exitcode ;
2012-08-26 14:13:51 +01:00
private $fallbackExitcode ;
2012-03-21 16:58:02 +00:00
private $processInformation ;
2011-03-24 08:10:42 +00:00
private $stdout ;
private $stderr ;
2012-03-05 14:19:26 +00:00
private $enhanceWindowsCompatibility ;
2012-09-09 15:38:21 +01:00
private $enhanceSigchildCompatibility ;
2012-03-21 16:58:02 +00:00
private $pipes ;
private $process ;
private $status = self :: STATUS_READY ;
2010-05-06 12:25:53 +01:00
2012-04-22 20:52:08 +01:00
private $fileHandles ;
2012-04-23 12:43:49 +01:00
private $readBytes ;
2012-04-22 20:52:08 +01:00
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 .
*
* @ param string $commandline The command line to run
* @ param string $cwd The working directory
2012-02-22 13:10:14 +00:00
* @ param array $env The environment variables or null to inherit
2010-05-06 12:25:53 +01:00
* @ 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
2011-03-24 08:13:58 +00:00
*
* @ api
2010-05-06 12:25:53 +01:00
*/
2011-05-19 15:52:45 +01:00
public function __construct ( $commandline , $cwd = null , array $env = null , $stdin = null , $timeout = 60 , array $options = array ())
2010-04-17 13:49:58 +01:00
{
2010-05-07 15:09:11 +01:00
if ( ! function_exists ( 'proc_open' )) {
2010-05-06 12:25:53 +01:00
throw new \RuntimeException ( 'The Process class relies on proc_open, which is not available on your PHP installation.' );
}
2010-04-17 13:49:58 +01:00
2010-05-06 12:25:53 +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-02-17 19:36:23 +00:00
// on windows, if the cwd changed via chdir(), proc_open defaults to the dir where php was started
2013-03-02 17:41:21 +00:00
// 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' ))) {
2013-02-17 19:36:23 +00:00
$this -> cwd = getcwd ();
}
2011-05-19 15:52:45 +01:00
if ( null !== $env ) {
$this -> env = array ();
foreach ( $env as $key => $value ) {
$this -> env [( binary ) $key ] = ( binary ) $value ;
}
} else {
$this -> env = null ;
2010-05-06 12:25:53 +01:00
}
$this -> stdin = $stdin ;
2012-07-10 14:21:23 +01:00
$this -> setTimeout ( $timeout );
2012-03-05 14:19:26 +00:00
$this -> enhanceWindowsCompatibility = true ;
2012-09-09 15:38:21 +01:00
$this -> enhanceSigchildCompatibility = ! defined ( 'PHP_WINDOWS_VERSION_BUILD' ) && $this -> isSigchildEnabled ();
2012-02-22 13:38:43 +00:00
$this -> options = array_replace ( array ( 'suppress_errors' => true , 'binary_pipes' => true ), $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 ()
{
// stop() will check if we have a process running.
$this -> stop ();
}
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-01-08 19:11:30 +00:00
* @ param callback | null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
2010-05-06 12:25:53 +01:00
*
* @ return integer The exit status code
*
* @ throws \RuntimeException When process can ' t be launch or is stopped
2011-03-24 08:13:58 +00:00
*
* @ api
2010-05-06 12:25:53 +01:00
*/
public function run ( $callback = null )
2010-04-18 08:27:43 +01:00
{
2012-03-21 16:58:02 +00:00
$this -> start ( $callback );
2011-04-01 17:07:53 +01:00
2012-03-23 12:02:23 +00:00
return $this -> wait ( $callback );
2012-03-21 16:58:02 +00:00
}
2010-05-06 12:25:53 +01:00
2012-03-21 16:58:02 +00:00
/**
2012-03-23 11:59:10 +00:00
* Starts the process and returns after sending the 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-23 12:02:23 +00:00
* If there is no callback passed , the wait () method can be called
2012-07-28 23:02:29 +01:00
* with true as a second parameter then the callback will get all data occurred
2012-03-23 11:59:10 +00:00
* in ( and since ) the start call .
2012-03-21 16:58:02 +00:00
*
2013-01-08 19:11:30 +00:00
* @ param callback | null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
2012-03-21 16:58:02 +00:00
*
* @ throws \RuntimeException When process can ' t be launch or is stopped
* @ throws \RuntimeException When process is already running
*/
public function start ( $callback = null )
{
2012-03-23 11:06:08 +00:00
if ( $this -> isRunning ()) {
2012-03-21 16:58:02 +00:00
throw new \RuntimeException ( 'Process is already running' );
}
2012-03-23 11:59:10 +00:00
2012-03-21 16:58:02 +00:00
$this -> stdout = '' ;
$this -> stderr = '' ;
$callback = $this -> buildCallback ( $callback );
2012-04-22 20:52:08 +01:00
//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 (),
);
2013-02-14 11:11:15 +00:00
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' );
}
2012-05-24 18:53:48 +01:00
$this -> readBytes = array (
self :: STDOUT => 0 ,
);
2012-04-22 20:52:08 +01:00
$descriptors = array ( array ( 'pipe' , 'r' ), $this -> fileHandles [ self :: STDOUT ], array ( 'pipe' , 'w' ));
} else {
2012-08-26 14:13:51 +01:00
$descriptors = array (
array ( 'pipe' , 'r' ), // stdin
array ( 'pipe' , 'w' ), // stdout
array ( 'pipe' , 'w' ), // stderr
);
2012-09-09 15:38:21 +01:00
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' ;
}
2012-04-22 20:52:08 +01:00
}
2010-05-06 12:25:53 +01:00
2012-02-16 18:16:07 +00:00
$commandline = $this -> commandline ;
if ( defined ( 'PHP_WINDOWS_VERSION_BUILD' ) && $this -> enhanceWindowsCompatibility ) {
$commandline = 'cmd /V:ON /E:ON /C "' . $commandline . '"' ;
2012-02-22 13:19:04 +00:00
if ( ! isset ( $this -> options [ 'bypass_shell' ])) {
$this -> options [ 'bypass_shell' ] = true ;
}
2012-02-16 18:16:07 +00:00
}
2012-03-21 16:58:02 +00:00
$this -> process = proc_open ( $commandline , $descriptors , $this -> pipes , $this -> cwd , $this -> env , $this -> options );
2010-05-06 12:25:53 +01:00
2012-03-21 16:58:02 +00:00
if ( ! is_resource ( $this -> process )) {
2010-05-06 12:25:53 +01:00
throw new \RuntimeException ( 'Unable to launch a new process.' );
}
2012-03-21 16:58:02 +00:00
$this -> status = self :: STATUS_STARTED ;
2010-05-06 12:25:53 +01:00
2012-03-21 16:58:02 +00:00
foreach ( $this -> pipes as $pipe ) {
2011-06-22 14:16:44 +01:00
stream_set_blocking ( $pipe , false );
}
2011-06-23 12:39:36 +01:00
2012-03-23 11:06:08 +00:00
if ( null === $this -> stdin ) {
2012-03-21 16:58:02 +00:00
fclose ( $this -> pipes [ 0 ]);
2012-04-08 19:26:04 +01:00
unset ( $this -> pipes [ 0 ]);
2012-03-21 16:58:02 +00:00
return ;
2010-05-06 12:25:53 +01:00
}
2012-05-24 18:53:48 +01:00
$writePipes = array ( $this -> pipes [ 0 ]);
unset ( $this -> pipes [ 0 ]);
$stdinLen = strlen ( $this -> stdin );
$stdinOffset = 0 ;
2012-03-21 16:58:02 +00:00
while ( $writePipes ) {
2012-05-24 18:53:48 +01:00
if ( defined ( 'PHP_WINDOWS_VERSION_BUILD' )) {
$this -> processFileHandles ( $callback );
}
2012-03-21 16:58:02 +00:00
$r = $this -> pipes ;
2011-06-22 14:16:44 +01:00
$w = $writePipes ;
2010-05-06 12:25:53 +01:00
$e = null ;
$n = @ stream_select ( $r , $w , $e , $this -> timeout );
2010-12-21 02:59:17 +00:00
if ( false === $n ) {
2010-05-06 12:25:53 +01:00
break ;
2012-05-24 18:53:48 +01:00
}
if ( $n === 0 ) {
2012-03-21 16:58:02 +00:00
proc_terminate ( $this -> process );
2010-05-06 12:25:53 +01:00
throw new \RuntimeException ( 'The process timed out.' );
2011-06-22 14:16:44 +01:00
}
if ( $w ) {
$written = fwrite ( $writePipes [ 0 ], ( binary ) substr ( $this -> stdin , $stdinOffset ), 8192 );
if ( false !== $written ) {
$stdinOffset += $written ;
}
if ( $stdinOffset >= $stdinLen ) {
fclose ( $writePipes [ 0 ]);
$writePipes = null ;
2010-05-06 12:25:53 +01:00
}
2011-06-22 14:16:44 +01:00
}
2010-05-06 12:25:53 +01:00
2011-06-22 14:16:44 +01:00
foreach ( $r as $pipe ) {
2012-03-21 16:58:02 +00:00
$type = array_search ( $pipe , $this -> pipes );
2011-06-22 14:16:44 +01:00
$data = fread ( $pipe , 8192 );
if ( strlen ( $data ) > 0 ) {
2012-03-21 16:58:02 +00:00
call_user_func ( $callback , $type == 1 ? self :: OUT : self :: ERR , $data );
2011-06-22 14:16:44 +01:00
}
if ( false === $data || feof ( $pipe )) {
fclose ( $pipe );
2012-03-21 16:58:02 +00:00
unset ( $this -> pipes [ $type ]);
2010-05-06 12:25:53 +01:00
}
}
2010-04-18 08:27:43 +01:00
}
2012-07-18 18:20:44 +01:00
$this -> updateStatus ();
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-01-08 19:11:30 +00:00
* @ param callback | null $callback A valid PHP callback
2012-03-23 11:59:10 +00:00
*
2013-01-08 19:11:30 +00:00
* @ return integer The exitcode of the process
2012-03-21 16:58:02 +00:00
*
2013-01-08 19:11:30 +00:00
* @ throws \RuntimeException When process timed out
* @ throws \RuntimeException When process stopped after receiving signal
2012-03-21 16:58:02 +00:00
*/
2012-03-23 12:02:23 +00:00
public function wait ( $callback = null )
2012-03-21 16:58:02 +00:00
{
2012-07-18 18:20:44 +01:00
$this -> updateStatus ();
2012-03-21 16:58:02 +00:00
$callback = $this -> buildCallback ( $callback );
2012-04-24 13:28:42 +01:00
while ( $this -> pipes || ( defined ( 'PHP_WINDOWS_VERSION_BUILD' ) && $this -> fileHandles )) {
2012-05-24 18:53:48 +01:00
if ( defined ( 'PHP_WINDOWS_VERSION_BUILD' ) && $this -> fileHandles ) {
$this -> processFileHandles ( $callback , ! $this -> pipes );
}
2012-03-21 16:58:02 +00:00
2012-05-24 18:53:48 +01:00
if ( $this -> pipes ) {
$r = $this -> pipes ;
$w = null ;
$e = null ;
2012-03-21 16:58:02 +00:00
2013-01-03 14:01:40 +00:00
if ( false === $n = @ stream_select ( $r , $w , $e , $this -> timeout )) {
$lastError = error_get_last ();
2012-03-21 16:58:02 +00:00
2013-01-03 14:01:40 +00:00
// 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 ();
}
2012-03-21 16:58:02 +00:00
2012-05-24 18:53:48 +01:00
continue ;
}
if ( 0 === $n ) {
proc_terminate ( $this -> process );
2012-04-22 23:05:10 +01:00
2012-05-24 18:53:48 +01:00
throw new \RuntimeException ( 'The process timed out.' );
}
foreach ( $r as $pipe ) {
$type = array_search ( $pipe , $this -> pipes );
$data = fread ( $pipe , 8192 );
2012-08-26 14:13:51 +01:00
2012-04-22 20:52:08 +01:00
if ( strlen ( $data ) > 0 ) {
2012-08-26 14:13:51 +01:00
// 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 );
}
2012-04-22 20:52:08 +01:00
}
2012-05-24 18:53:48 +01:00
if ( false === $data || feof ( $pipe )) {
fclose ( $pipe );
unset ( $this -> pipes [ $type ]);
2012-04-22 20:52:08 +01:00
}
}
}
2012-03-21 16:58:02 +00:00
}
$this -> updateStatus ();
if ( $this -> processInformation [ 'signaled' ]) {
2012-03-23 11:06:08 +00:00
throw new \RuntimeException ( sprintf ( 'The process stopped because of a "%s" signal.' , $this -> processInformation [ 'stopsig' ]));
2012-03-21 16:58:02 +00:00
}
2010-04-17 13:49:58 +01:00
2011-06-23 10:47:31 +01:00
$time = 0 ;
2012-03-21 16:58:02 +00:00
while ( $this -> isRunning () && $time < 1000000 ) {
2011-06-23 10:47:31 +01:00
$time += 1000 ;
usleep ( 1000 );
}
2012-03-21 16:58:02 +00:00
$exitcode = proc_close ( $this -> process );
2010-04-17 13:49:58 +01:00
2012-03-21 16:58:02 +00:00
if ( $this -> processInformation [ 'signaled' ]) {
throw new \RuntimeException ( sprintf ( 'The process stopped because of a "%s" signal.' , $this -> processInformation [ 'stopsig' ]));
2010-05-06 12:25:53 +01:00
}
2011-07-12 07:16:22 +01:00
2012-08-26 14:13:51 +01:00
$this -> exitcode = $this -> processInformation [ 'running' ] ? $exitcode : $this -> processInformation [ 'exitcode' ];
if ( - 1 == $this -> exitcode && null !== $this -> fallbackExitcode ) {
$this -> exitcode = $this -> fallbackExitcode ;
}
return $this -> exitcode ;
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
*
* @ api
2010-05-06 12:25:53 +01:00
*/
public function getOutput ()
2010-04-17 13:49:58 +01:00
{
2012-03-21 16:58:02 +00:00
$this -> updateOutput ();
2010-05-06 12:25:53 +01:00
return $this -> stdout ;
2010-04-17 13:49:58 +01:00
}
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
*
* @ api
2010-05-06 12:25:53 +01:00
*/
public function getErrorOutput ()
2010-04-17 13:49:58 +01:00
{
2012-03-21 16:58:02 +00:00
$this -> updateErrorOutput ();
2010-05-06 12:25:53 +01:00
return $this -> stderr ;
2010-04-17 13:49:58 +01:00
}
2010-05-06 12:25:53 +01:00
/**
* Returns the exit code returned by the process .
*
* @ return integer The exit status code
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
*
2011-03-24 08:13:58 +00:00
* @ api
2010-05-06 12:25:53 +01:00
*/
public function getExitCode ()
2010-04-17 13:49:58 +01:00
{
2012-09-09 15:38:21 +01:00
if ( $this -> isSigchildEnabled () && ! $this -> enhanceSigchildCompatibility ) {
throw new RuntimeException ( 'This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method' );
}
2012-03-21 16:58:02 +00:00
$this -> updateStatus ();
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 .
*
* @ 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 ()
{
2012-09-09 15:38:21 +01:00
$exitcode = $this -> getExitCode ();
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 .
*
* @ return Boolean true if the process ended successfully , false otherwise
2011-03-24 08:13:58 +00:00
*
* @ api
2010-09-16 07:55:44 +01:00
*/
public function isSuccessful ()
{
2012-09-09 15:38:21 +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 .
*
* @ return Boolean
2011-03-24 08:13:58 +00:00
*
2012-09-09 15:38:21 +01:00
* @ throws RuntimeException In case -- enable - sigchild is activated
*
2011-03-24 08:13:58 +00:00
* @ api
2010-05-06 12:25:53 +01:00
*/
public function hasBeenSignaled ()
{
2012-09-09 15:38:21 +01:00
if ( $this -> isSigchildEnabled ()) {
throw new RuntimeException ( 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved' );
}
2012-03-21 16:58:02 +00:00
$this -> updateStatus ();
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 .
*
* @ return integer
2011-03-24 08:13:58 +00:00
*
2012-09-09 15:38:21 +01:00
* @ throws RuntimeException In case -- enable - sigchild is activated
*
2011-03-24 08:13:58 +00:00
* @ api
2010-05-06 12:25:53 +01:00
*/
public function getTermSignal ()
{
2012-09-09 15:38:21 +01:00
if ( $this -> isSigchildEnabled ()) {
throw new RuntimeException ( 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved' );
}
2012-03-21 16:58:02 +00:00
$this -> updateStatus ();
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 .
*
* @ return Boolean
2011-03-24 08:13:58 +00:00
*
* @ api
2010-05-06 12:25:53 +01:00
*/
public function hasBeenStopped ()
{
2012-03-21 16:58:02 +00:00
$this -> updateStatus ();
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 .
*
* @ return integer
2011-03-24 08:13:58 +00:00
*
* @ api
2010-05-06 12:25:53 +01:00
*/
public function getStopSignal ()
{
2012-03-21 16:58:02 +00:00
$this -> updateStatus ();
return $this -> processInformation [ 'stopsig' ];
}
/**
2012-03-23 11:59:10 +00:00
* Checks if the process is currently running .
*
* @ return Boolean 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 ;
}
$this -> updateStatus ();
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-03-23 11:59:10 +00:00
* Stops the process .
*
2013-01-08 19:11:30 +00:00
* @ param integer | float $timeout The timeout in seconds
2012-03-23 11:59:10 +00:00
*
2012-10-19 17:43:59 +01:00
* @ return integer The exit - code of the process
2012-03-21 16:58:02 +00:00
*/
2012-03-23 11:59:10 +00:00
public function stop ( $timeout = 10 )
2012-03-23 11:06:08 +00:00
{
2012-03-21 16:58:02 +00:00
$timeoutMicro = ( int ) $timeout * 10E6 ;
2012-03-23 11:06:08 +00:00
if ( $this -> isRunning ()) {
2012-03-21 16:58:02 +00:00
proc_terminate ( $this -> process );
$time = 0 ;
while ( 1 == $this -> isRunning () && $time < $timeoutMicro ) {
$time += 1000 ;
usleep ( 1000 );
}
2012-04-08 19:27:37 +01:00
foreach ( $this -> pipes as $pipe ) {
fclose ( $pipe );
}
$this -> pipes = array ();
2012-03-21 16:58:02 +00:00
$exitcode = proc_close ( $this -> process );
$this -> exitcode = - 1 === $this -> processInformation [ 'exitcode' ] ? $exitcode : $this -> processInformation [ 'exitcode' ];
2012-04-22 20:52:08 +01:00
if ( defined ( 'PHP_WINDOWS_VERSION_BUILD' )) {
foreach ( $this -> fileHandles as $fileHandle ) {
fclose ( $fileHandle );
}
$this -> fileHandles = array ();
}
2012-03-21 16:58:02 +00:00
}
$this -> status = self :: STATUS_TERMINATED ;
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
*
* @ 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
{
2010-05-06 12:25:53 +01:00
$this -> stdout .= $line ;
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
*
* @ param string $line The line to append
*/
2010-05-06 12:25:53 +01:00
public function addErrorOutput ( $line )
{
$this -> stderr .= $line ;
}
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 ()
{
return $this -> commandline ;
}
2012-10-06 05:39:50 +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
*
* @ param string $commandline The command to execute
*/
2011-03-24 08:10:42 +00:00
public function setCommandLine ( $commandline )
{
$this -> commandline = $commandline ;
}
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 process timeout .
2012-10-06 05:39:50 +01:00
*
2012-10-06 05:44:21 +01:00
* @ return integer | 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 ;
}
2012-07-11 08:39:13 +01:00
/**
* Sets the process timeout .
*
* To disable the timeout , set this value to null .
*
2012-10-06 05:39:50 +01:00
* @ param integer | null $timeout The timeout in seconds
2012-10-06 05:44:21 +01: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 )
{
2012-07-11 08:39:13 +01:00
if ( null === $timeout ) {
2012-07-11 13:21:05 +01:00
$this -> timeout = null ;
return ;
2012-07-11 08:39:13 +01:00
}
2012-07-10 14:21:23 +01:00
$timeout = ( integer ) $timeout ;
if ( $timeout < 0 ) {
throw new \InvalidArgumentException ( 'The timeout value must be a valid positive integer.' );
}
2011-04-02 09:08:01 +01:00
$this -> timeout = $timeout ;
}
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
*
* @ return string The current working directory
*/
2011-04-02 09:08:01 +01:00
public function getWorkingDirectory ()
{
2013-01-08 12:27:17 +00:00
// 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 ;
}
2011-04-02 09:08:01 +01:00
return $this -> cwd ;
}
2012-10-06 05:39:50 +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
*/
2011-04-02 09:08:01 +01:00
public function setWorkingDirectory ( $cwd )
{
$this -> cwd = $cwd ;
}
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-06 05:39:50 +01:00
/**
2012-10-06 13:46:45 +01:00
* Sets the environment variables .
2012-10-06 05:39:50 +01:00
*
* @ param array $env The new environment variables
*/
2011-04-02 09:08:01 +01:00
public function setEnv ( array $env )
{
$this -> env = $env ;
}
2012-10-06 05:39:50 +01:00
/**
2012-10-06 13:46:45 +01:00
* Gets the contents of STDIN .
2012-10-06 05:39:50 +01:00
*
* @ return string The current contents
*/
2011-04-02 09:08:01 +01:00
public function getStdin ()
{
return $this -> stdin ;
}
2012-10-06 05:39:50 +01:00
/**
2012-10-06 13:46:45 +01:00
* Sets the contents of STDIN .
2012-10-06 05:39:50 +01:00
*
* @ param string $stdin The new contents
*/
2011-04-02 09:08:01 +01:00
public function setStdin ( $stdin )
{
$this -> stdin = $stdin ;
}
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
*/
2011-04-02 09:08:01 +01:00
public function getOptions ()
{
return $this -> options ;
}
2012-10-06 05:39:50 +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
*/
2011-04-02 09:08:01 +01:00
public function setOptions ( array $options )
{
$this -> options = $options ;
}
2012-02-16 18:16:07 +00:00
2012-10-06 05:44:21 +01:00
/**
* Gets whether or not Windows compatibility is enabled
*
* This is true by default .
*
* @ return Boolean
*/
2012-02-16 18:16:07 +00:00
public function getEnhanceWindowsCompatibility ()
{
return $this -> enhanceWindowsCompatibility ;
}
2012-10-06 05:44:21 +01:00
/**
* Sets whether or not Windows compatibility is enabled
*
* @ param Boolean $enhance
*/
2012-02-16 18:16:07 +00:00
public function setEnhanceWindowsCompatibility ( $enhance )
{
$this -> enhanceWindowsCompatibility = ( Boolean ) $enhance ;
}
2012-03-21 16:58:02 +00:00
2012-09-09 15:38:21 +01:00
/**
* Return whether sigchild compatibility mode is activated or not
*
* @ return Boolean
*/
public function getEnhanceSigchildCompatibility ()
{
return $this -> enhanceSigchildCompatibility ;
}
/**
* Activate 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
*/
public function setEnhanceSigchildCompatibility ( $enhance )
{
$this -> enhanceSigchildCompatibility = ( Boolean ) $enhance ;
}
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-01-08 19:11:30 +00:00
* @ param callback | null $callback The user defined PHP callback
2012-03-23 11:59:10 +00:00
*
2013-01-08 19:11:30 +00:00
* @ return callback A PHP callable
2012-03-21 16:58:02 +00:00
*/
2012-03-23 11:59:10 +00:00
protected function buildCallback ( $callback )
{
2012-03-21 16:58:02 +00:00
$that = $this ;
$out = self :: OUT ;
$err = self :: ERR ;
2012-03-23 11:59:10 +00:00
$callback = function ( $type , $data ) use ( $that , $callback , $out , $err ) {
2012-03-21 16:58:02 +00:00
if ( $out == $type ) {
$that -> addOutput ( $data );
} else {
$that -> addErrorOutput ( $data );
}
if ( null !== $callback ) {
call_user_func ( $callback , $type , $data );
}
};
return $callback ;
}
/**
2012-03-23 11:59:10 +00:00
* Updates the status of the process .
2012-03-21 16:58:02 +00:00
*/
protected function updateStatus ()
{
2012-03-23 11:59:10 +00:00
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' ];
2012-03-21 16:58:02 +00:00
}
}
}
2013-01-08 19:11:30 +00:00
/**
* Updates the current error output of the process ( STDERR ) .
*/
2012-03-23 11:59:10 +00:00
protected function updateErrorOutput ()
{
2012-03-23 11:06:08 +00:00
if ( isset ( $this -> pipes [ self :: STDERR ]) && is_resource ( $this -> pipes [ self :: STDERR ])) {
2012-03-21 16:58:02 +00:00
$this -> addErrorOutput ( stream_get_contents ( $this -> pipes [ self :: STDERR ]));
}
}
2013-01-08 19:11:30 +00:00
/**
* Updates the current output of the process ( STDOUT ) .
*/
2012-03-23 11:59:10 +00:00
protected function updateOutput ()
{
2012-04-22 20:52:08 +01:00
if ( defined ( 'PHP_WINDOWS_VERSION_BUILD' ) && isset ( $this -> fileHandles [ self :: STDOUT ]) && is_resource ( $this -> fileHandles [ self :: STDOUT ])) {
2012-04-23 12:43:49 +01:00
fseek ( $this -> fileHandles [ self :: STDOUT ], $this -> readBytes [ self :: STDOUT ]);
2012-04-22 20:52:08 +01:00
$this -> addOutput ( stream_get_contents ( $this -> fileHandles [ self :: STDOUT ]));
} elseif ( isset ( $this -> pipes [ self :: STDOUT ]) && is_resource ( $this -> pipes [ self :: STDOUT ])) {
2012-03-21 16:58:02 +00:00
$this -> addOutput ( stream_get_contents ( $this -> pipes [ self :: STDOUT ]));
}
}
2012-05-24 18:53:48 +01:00
2012-09-09 15:38:21 +01:00
/**
* Return 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' );
}
2012-05-24 18:53:48 +01:00
/**
* Handles the windows file handles fallbacks
*
* @ param mixed $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 ]);
}
}
}
2010-04-17 13:49:58 +01:00
}