source: branches/2.5/prototype/library/Symfony/Component/Process/Process.php @ 7578

Revision 7578, 30.4 KB checked in by angelo, 12 years ago (diff)

Ticket #3197 - Reduzir tempo de carregamento do modulo Expresso Mail

Line 
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Process;
13
14use Symfony\Component\Process\Exception\InvalidArgumentException;
15use Symfony\Component\Process\Exception\RuntimeException;
16
17/**
18 * Process is a thin wrapper around proc_* functions to ease
19 * start independent PHP processes.
20 *
21 * @author Fabien Potencier <fabien@symfony.com>
22 *
23 * @api
24 */
25class Process
26{
27    const ERR = 'err';
28    const OUT = 'out';
29
30    const STATUS_READY = 'ready';
31    const STATUS_STARTED = 'started';
32    const STATUS_TERMINATED = 'terminated';
33
34    const STDIN = 0;
35    const STDOUT = 1;
36    const STDERR = 2;
37
38    private $commandline;
39    private $cwd;
40    private $env;
41    private $stdin;
42    private $timeout;
43    private $options;
44    private $exitcode;
45    private $fallbackExitcode;
46    private $processInformation;
47    private $stdout;
48    private $stderr;
49    private $enhanceWindowsCompatibility;
50    private $enhanceSigchildCompatibility;
51    private $pipes;
52    private $process;
53    private $status = self::STATUS_READY;
54    private $incrementalOutputOffset;
55    private $incrementalErrorOutputOffset;
56
57    private $fileHandles;
58    private $readBytes;
59
60    private static $sigchild;
61
62    /**
63     * Exit codes translation table.
64     *
65     * User-defined errors must use exit codes in the 64-113 range.
66     *
67     * @var array
68     */
69    public static $exitCodes = array(
70        0 => 'OK',
71        1 => 'General error',
72        2 => 'Misuse of shell builtins',
73
74        126 => 'Invoked command cannot execute',
75        127 => 'Command not found',
76        128 => 'Invalid exit argument',
77
78        // signals
79        129 => 'Hangup',
80        130 => 'Interrupt',
81        131 => 'Quit and dump core',
82        132 => 'Illegal instruction',
83        133 => 'Trace/breakpoint trap',
84        134 => 'Process aborted',
85        135 => 'Bus error: "access to undefined portion of memory object"',
86        136 => 'Floating point exception: "erroneous arithmetic operation"',
87        137 => 'Kill (terminate immediately)',
88        138 => 'User-defined 1',
89        139 => 'Segmentation violation',
90        140 => 'User-defined 2',
91        141 => 'Write to pipe with no one reading',
92        142 => 'Signal raised by alarm',
93        143 => 'Termination (request to terminate)',
94        // 144 - not defined
95        145 => 'Child process terminated, stopped (or continued*)',
96        146 => 'Continue if stopped',
97        147 => 'Stop executing temporarily',
98        148 => 'Terminal stop signal',
99        149 => 'Background process attempting to read from tty ("in")',
100        150 => 'Background process attempting to write to tty ("out")',
101        151 => 'Urgent data available on socket',
102        152 => 'CPU time limit exceeded',
103        153 => 'File size limit exceeded',
104        154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
105        155 => 'Profiling timer expired',
106        // 156 - not defined
107        157 => 'Pollable event',
108        // 158 - not defined
109        159 => 'Bad syscall',
110    );
111
112    /**
113     * Constructor.
114     *
115     * @param string  $commandline The command line to run
116     * @param string  $cwd         The working directory
117     * @param array   $env         The environment variables or null to inherit
118     * @param string  $stdin       The STDIN content
119     * @param integer $timeout     The timeout in seconds
120     * @param array   $options     An array of options for proc_open
121     *
122     * @throws RuntimeException When proc_open is not installed
123     *
124     * @api
125     */
126    public function __construct($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array())
127    {
128        if (!function_exists('proc_open')) {
129            throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
130        }
131
132        $this->commandline = $commandline;
133        $this->cwd = null === $cwd ? getcwd() : $cwd;
134        if (null !== $env) {
135            $this->env = array();
136            foreach ($env as $key => $value) {
137                $this->env[(binary) $key] = (binary) $value;
138            }
139        } else {
140            $this->env = null;
141        }
142        $this->stdin = $stdin;
143        $this->setTimeout($timeout);
144        $this->enhanceWindowsCompatibility = true;
145        $this->enhanceSigchildCompatibility = !defined('PHP_WINDOWS_VERSION_BUILD') && $this->isSigchildEnabled();
146        $this->options = array_replace(array('suppress_errors' => true, 'binary_pipes' => true), $options);
147    }
148
149    public function __destruct()
150    {
151        // stop() will check if we have a process running.
152        $this->stop();
153    }
154
155    public function __clone()
156    {
157        $this->exitcode = null;
158        $this->fallbackExitcode = null;
159        $this->processInformation = null;
160        $this->stdout = null;
161        $this->stderr = null;
162        $this->pipes = null;
163        $this->process = null;
164        $this->status = self::STATUS_READY;
165        $this->fileHandles = null;
166        $this->readBytes = null;
167    }
168
169    /**
170     * Runs the process.
171     *
172     * The callback receives the type of output (out or err) and
173     * some bytes from the output in real-time. It allows to have feedback
174     * from the independent process during execution.
175     *
176     * The STDOUT and STDERR are also available after the process is finished
177     * via the getOutput() and getErrorOutput() methods.
178     *
179     * @param callable $callback A PHP callback to run whenever there is some
180     *                           output available on STDOUT or STDERR
181     *
182     * @return integer The exit status code
183     *
184     * @throws RuntimeException When process can't be launch or is stopped
185     *
186     * @api
187     */
188    public function run($callback = null)
189    {
190        $this->start($callback);
191
192        return $this->wait($callback);
193    }
194
195    /**
196     * Starts the process and returns after sending the STDIN.
197     *
198     * This method blocks until all STDIN data is sent to the process then it
199     * returns while the process runs in the background.
200     *
201     * The termination of the process can be awaited with wait().
202     *
203     * The callback receives the type of output (out or err) and some bytes from
204     * the output in real-time while writing the standard input to the process.
205     * It allows to have feedback from the independent process during execution.
206     * If there is no callback passed, the wait() method can be called
207     * with true as a second parameter then the callback will get all data occurred
208     * in (and since) the start call.
209     *
210     * @param callable $callback A PHP callback to run whenever there is some
211     *                           output available on STDOUT or STDERR
212     *
213     * @throws RuntimeException When process can't be launch or is stopped
214     * @throws RuntimeException When process is already running
215     */
216    public function start($callback = null)
217    {
218        if ($this->isRunning()) {
219            throw new RuntimeException('Process is already running');
220        }
221
222        $this->stdout = '';
223        $this->stderr = '';
224        $this->incrementalOutputOffset = 0;
225        $this->incrementalErrorOutputOffset = 0;
226        $callback = $this->buildCallback($callback);
227
228        //Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
229        //Workaround for this problem is to use temporary files instead of pipes on Windows platform.
230        //@see https://bugs.php.net/bug.php?id=51800
231        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
232            $this->fileHandles = array(
233                self::STDOUT => tmpfile(),
234            );
235            $this->readBytes = array(
236                self::STDOUT => 0,
237            );
238            $descriptors = array(array('pipe', 'r'), $this->fileHandles[self::STDOUT], array('pipe', 'w'));
239        } else {
240            $descriptors = array(
241                array('pipe', 'r'), // stdin
242                array('pipe', 'w'), // stdout
243                array('pipe', 'w'), // stderr
244            );
245
246            if ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
247                // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
248                $descriptors = array_merge($descriptors, array(array('pipe', 'w')));
249
250                $this->commandline = '('.$this->commandline.') 3>/dev/null; code=$?; echo $code >&3; exit $code';
251            }
252        }
253
254        $commandline = $this->commandline;
255
256        if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->enhanceWindowsCompatibility) {
257            $commandline = 'cmd /V:ON /E:ON /C "'.$commandline.'"';
258            if (!isset($this->options['bypass_shell'])) {
259                $this->options['bypass_shell'] = true;
260            }
261        }
262
263        $this->process = proc_open($commandline, $descriptors, $this->pipes, $this->cwd, $this->env, $this->options);
264
265        if (!is_resource($this->process)) {
266            throw new RuntimeException('Unable to launch a new process.');
267        }
268        $this->status = self::STATUS_STARTED;
269
270        foreach ($this->pipes as $pipe) {
271            stream_set_blocking($pipe, false);
272        }
273
274        if (null === $this->stdin) {
275            fclose($this->pipes[0]);
276            unset($this->pipes[0]);
277
278            return;
279        }
280
281        $writePipes = array($this->pipes[0]);
282        unset($this->pipes[0]);
283        $stdinLen = strlen($this->stdin);
284        $stdinOffset = 0;
285
286        while ($writePipes) {
287            if (defined('PHP_WINDOWS_VERSION_BUILD')) {
288                $this->processFileHandles($callback);
289            }
290
291            $r = $this->pipes;
292            $w = $writePipes;
293            $e = null;
294
295            $n = @stream_select($r, $w, $e, $this->timeout);
296
297            if (false === $n) {
298                break;
299            }
300            if ($n === 0) {
301                proc_terminate($this->process);
302
303                throw new RuntimeException('The process timed out.');
304            }
305
306            if ($w) {
307                $written = fwrite($writePipes[0], (binary) substr($this->stdin, $stdinOffset), 8192);
308                if (false !== $written) {
309                    $stdinOffset += $written;
310                }
311                if ($stdinOffset >= $stdinLen) {
312                    fclose($writePipes[0]);
313                    $writePipes = null;
314                }
315            }
316
317            foreach ($r as $pipe) {
318                $type = array_search($pipe, $this->pipes);
319                $data = fread($pipe, 8192);
320                if (strlen($data) > 0) {
321                    call_user_func($callback, $type == 1 ? self::OUT : self::ERR, $data);
322                }
323                if (false === $data || feof($pipe)) {
324                    fclose($pipe);
325                    unset($this->pipes[$type]);
326                }
327            }
328        }
329
330        $this->updateStatus();
331    }
332
333    /**
334     * Restarts the process.
335     *
336     * Be warned that the process is cloned before being started.
337     *
338     * @param callable $callback A PHP callback to run whenever there is some
339     *                           output available on STDOUT or STDERR
340     *
341     * @return Process The new process
342     *
343     * @throws \RuntimeException When process can't be launch or is stopped
344     * @throws \RuntimeException When process is already running
345     *
346     * @see start()
347     */
348    public function restart($callback = null)
349    {
350        if ($this->isRunning()) {
351            throw new \RuntimeException('Process is already running');
352        }
353
354        $process = clone $this;
355        $process->start($callback);
356
357        return $process;
358    }
359
360    /**
361     * Waits for the process to terminate.
362     *
363     * The callback receives the type of output (out or err) and some bytes
364     * from the output in real-time while writing the standard input to the process.
365     * It allows to have feedback from the independent process during execution.
366     *
367     * @param callable $callback A valid PHP callback
368     *
369     * @return int The exitcode of the process
370     *
371     * @throws RuntimeException
372     */
373    public function wait($callback = null)
374    {
375        $this->updateStatus();
376        $callback = $this->buildCallback($callback);
377        while ($this->pipes || (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles)) {
378            if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles) {
379                $this->processFileHandles($callback, !$this->pipes);
380            }
381
382            if ($this->pipes) {
383                $r = $this->pipes;
384                $w = null;
385                $e = null;
386
387                $n = @stream_select($r, $w, $e, $this->timeout);
388
389                if (false === $n) {
390                    $this->pipes = array();
391
392                    continue;
393                }
394                if (0 === $n) {
395                    proc_terminate($this->process);
396
397                    throw new RuntimeException('The process timed out.');
398                }
399
400                foreach ($r as $pipe) {
401                    $type = array_search($pipe, $this->pipes);
402                    $data = fread($pipe, 8192);
403
404                    if (strlen($data) > 0) {
405                        // last exit code is output and caught to work around --enable-sigchild
406                        if (3 == $type) {
407                            $this->fallbackExitcode = (int) $data;
408                        } else {
409                            call_user_func($callback, $type == 1 ? self::OUT : self::ERR, $data);
410                        }
411                    }
412                    if (false === $data || feof($pipe)) {
413                        fclose($pipe);
414                        unset($this->pipes[$type]);
415                    }
416                }
417            }
418        }
419        $this->updateStatus();
420        if ($this->processInformation['signaled']) {
421            throw new RuntimeException(sprintf('The process stopped because of a "%s" signal.', $this->processInformation['stopsig']));
422        }
423
424        $time = 0;
425        while ($this->isRunning() && $time < 1000000) {
426            $time += 1000;
427            usleep(1000);
428        }
429
430        $exitcode = proc_close($this->process);
431
432        if ($this->processInformation['signaled']) {
433            throw new RuntimeException(sprintf('The process stopped because of a "%s" signal.', $this->processInformation['stopsig']));
434        }
435
436        $this->exitcode = $this->processInformation['running'] ? $exitcode : $this->processInformation['exitcode'];
437
438        if (-1 == $this->exitcode && null !== $this->fallbackExitcode) {
439            $this->exitcode = $this->fallbackExitcode;
440        }
441
442        return $this->exitcode;
443    }
444
445    /**
446     * Returns the current output of the process (STDOUT).
447     *
448     * @return string The process output
449     *
450     * @api
451     */
452    public function getOutput()
453    {
454        $this->updateOutput();
455
456        return $this->stdout;
457    }
458
459    /**
460     * Returns the output incrementally.
461     *
462     * In comparison with the getOutput method which always return the whole
463     * output, this one returns the new output since the last call.
464     *
465     * @return string The process output since the last call
466     */
467    public function getIncrementalOutput()
468    {
469        $data = $this->getOutput();
470
471        $latest = substr($data, $this->incrementalOutputOffset);
472        $this->incrementalOutputOffset = strlen($data);
473
474        return $latest;
475    }
476
477    /**
478     * Returns the current error output of the process (STDERR).
479     *
480     * @return string The process error output
481     *
482     * @api
483     */
484    public function getErrorOutput()
485    {
486        $this->updateErrorOutput();
487
488        return $this->stderr;
489    }
490
491    /**
492     * Returns the errorOutput incrementally.
493     *
494     * In comparison with the getErrorOutput method which always return the
495     * whole error output, this one returns the new error output since the last
496     * call.
497     *
498     * @return string The process error output since the last call
499     */
500    public function getIncrementalErrorOutput()
501    {
502        $data = $this->getErrorOutput();
503
504        $latest = substr($data, $this->incrementalErrorOutputOffset);
505        $this->incrementalErrorOutputOffset = strlen($data);
506
507        return $latest;
508    }
509
510    /**
511     * Returns the exit code returned by the process.
512     *
513     * @return integer The exit status code
514     *
515     * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
516     *
517     * @api
518     */
519    public function getExitCode()
520    {
521        if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) {
522            throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method');
523        }
524
525        $this->updateStatus();
526
527        return $this->exitcode;
528    }
529
530    /**
531     * Returns a string representation for the exit code returned by the process.
532     *
533     * This method relies on the Unix exit code status standardization
534     * and might not be relevant for other operating systems.
535     *
536     * @return string A string representation for the exit status code
537     *
538     * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
539     *
540     * @see http://tldp.org/LDP/abs/html/exitcodes.html
541     * @see http://en.wikipedia.org/wiki/Unix_signal
542     */
543    public function getExitCodeText()
544    {
545        $exitcode = $this->getExitCode();
546
547        return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
548    }
549
550    /**
551     * Checks if the process ended successfully.
552     *
553     * @return Boolean true if the process ended successfully, false otherwise
554     *
555     * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
556     *
557     * @api
558     */
559    public function isSuccessful()
560    {
561        return 0 == $this->getExitCode();
562    }
563
564    /**
565     * Returns true if the child process has been terminated by an uncaught signal.
566     *
567     * It always returns false on Windows.
568     *
569     * @return Boolean
570     *
571     * @throws RuntimeException In case --enable-sigchild is activated
572     *
573     * @api
574     */
575    public function hasBeenSignaled()
576    {
577        if ($this->isSigchildEnabled()) {
578            throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
579        }
580
581        $this->updateStatus();
582
583        return $this->processInformation['signaled'];
584    }
585
586    /**
587     * Returns the number of the signal that caused the child process to terminate its execution.
588     *
589     * It is only meaningful if hasBeenSignaled() returns true.
590     *
591     * @return integer
592     *
593     * @throws RuntimeException In case --enable-sigchild is activated
594     *
595     * @api
596     */
597    public function getTermSignal()
598    {
599        if ($this->isSigchildEnabled()) {
600            throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
601        }
602
603        $this->updateStatus();
604
605        return $this->processInformation['termsig'];
606    }
607
608    /**
609     * Returns true if the child process has been stopped by a signal.
610     *
611     * It always returns false on Windows.
612     *
613     * @return Boolean
614     *
615     * @api
616     */
617    public function hasBeenStopped()
618    {
619        $this->updateStatus();
620
621        return $this->processInformation['stopped'];
622    }
623
624    /**
625     * Returns the number of the signal that caused the child process to stop its execution.
626     *
627     * It is only meaningful if hasBeenStopped() returns true.
628     *
629     * @return integer
630     *
631     * @api
632     */
633    public function getStopSignal()
634    {
635        $this->updateStatus();
636
637        return $this->processInformation['stopsig'];
638    }
639
640    /**
641     * Checks if the process is currently running.
642     *
643     * @return Boolean true if the process is currently running, false otherwise
644     */
645    public function isRunning()
646    {
647        if (self::STATUS_STARTED !== $this->status) {
648            return false;
649        }
650
651        $this->updateStatus();
652
653        return $this->processInformation['running'];
654    }
655
656    /**
657     * Checks if the process has been started with no regard to the current state.
658     *
659     * @return Boolean true if status is ready, false otherwise
660     */
661    public function isStarted()
662    {
663        return $this->status != self::STATUS_READY;
664    }
665
666    /**
667     * Checks if the process is terminated.
668     *
669     * @return Boolean true if process is terminated, false otherwise
670     */
671    public function isTerminated()
672    {
673        $this->updateStatus();
674
675        return $this->status == self::STATUS_TERMINATED;
676    }
677
678    /**
679     * Gets the process status.
680     *
681     * The status is one of: ready, started, terminated.
682     *
683     * @return string The current process status
684     */
685    public function getStatus()
686    {
687        $this->updateStatus();
688
689        return $this->status;
690    }
691
692    /**
693     * Stops the process.
694     *
695     * @param float $timeout The timeout in seconds
696     *
697     * @return integer The exit-code of the process
698     *
699     * @throws RuntimeException if the process got signaled
700     */
701    public function stop($timeout=10)
702    {
703        $timeoutMicro = (int) $timeout*10E6;
704        if ($this->isRunning()) {
705            proc_terminate($this->process);
706            $time = 0;
707            while (1 == $this->isRunning() && $time < $timeoutMicro) {
708                $time += 1000;
709                usleep(1000);
710            }
711
712            foreach ($this->pipes as $pipe) {
713                fclose($pipe);
714            }
715            $this->pipes = array();
716
717            $exitcode = proc_close($this->process);
718            $this->exitcode = -1 === $this->processInformation['exitcode'] ? $exitcode : $this->processInformation['exitcode'];
719
720            if (defined('PHP_WINDOWS_VERSION_BUILD')) {
721                foreach ($this->fileHandles as $fileHandle) {
722                    fclose($fileHandle);
723                }
724                $this->fileHandles = array();
725            }
726        }
727        $this->status = self::STATUS_TERMINATED;
728
729        return $this->exitcode;
730    }
731
732    /**
733     * Adds a line to the STDOUT stream.
734     *
735     * @param string $line The line to append
736     */
737    public function addOutput($line)
738    {
739        $this->stdout .= $line;
740    }
741
742    /**
743     * Adds a line to the STDERR stream.
744     *
745     * @param string $line The line to append
746     */
747    public function addErrorOutput($line)
748    {
749        $this->stderr .= $line;
750    }
751
752    /**
753     * Gets the command line to be executed.
754     *
755     * @return string The command to execute
756     */
757    public function getCommandLine()
758    {
759        return $this->commandline;
760    }
761
762    /**
763     * Sets the command line to be executed.
764     *
765     * @param string $commandline The command to execute
766     *
767     * @return self The current Process instance
768     */
769    public function setCommandLine($commandline)
770    {
771        $this->commandline = $commandline;
772
773        return $this;
774    }
775
776    /**
777     * Gets the process timeout.
778     *
779     * @return integer|null The timeout in seconds or null if it's disabled
780     */
781    public function getTimeout()
782    {
783        return $this->timeout;
784    }
785
786    /**
787     * Sets the process timeout.
788     *
789     * To disable the timeout, set this value to null.
790     *
791     * @param integer|null $timeout The timeout in seconds
792     *
793     * @return self The current Process instance
794     *
795     * @throws \InvalidArgumentException if the timeout is negative
796     */
797    public function setTimeout($timeout)
798    {
799        if (null === $timeout) {
800            $this->timeout = null;
801
802            return $this;
803        }
804
805        $timeout = (integer) $timeout;
806
807        if ($timeout < 0) {
808            throw new InvalidArgumentException('The timeout value must be a valid positive integer.');
809        }
810
811        $this->timeout = $timeout;
812
813        return $this;
814    }
815
816    /**
817     * Gets the working directory.
818     *
819     * @return string The current working directory
820     */
821    public function getWorkingDirectory()
822    {
823        return $this->cwd;
824    }
825
826    /**
827     * Sets the current working directory.
828     *
829     * @param string $cwd The new working directory
830     *
831     * @return self The current Process instance
832     */
833    public function setWorkingDirectory($cwd)
834    {
835        $this->cwd = $cwd;
836
837        return $this;
838    }
839
840    /**
841     * Gets the environment variables.
842     *
843     * @return array The current environment variables
844     */
845    public function getEnv()
846    {
847        return $this->env;
848    }
849
850    /**
851     * Sets the environment variables.
852     *
853     * @param array $env The new environment variables
854     *
855     * @return self The current Process instance
856     */
857    public function setEnv(array $env)
858    {
859        $this->env = $env;
860
861        return $this;
862    }
863
864    /**
865     * Gets the contents of STDIN.
866     *
867     * @return string The current contents
868     */
869    public function getStdin()
870    {
871        return $this->stdin;
872    }
873
874    /**
875     * Sets the contents of STDIN.
876     *
877     * @param string $stdin The new contents
878     *
879     * @return self The current Process instance
880     */
881    public function setStdin($stdin)
882    {
883        $this->stdin = $stdin;
884
885        return $this;
886    }
887
888    /**
889     * Gets the options for proc_open.
890     *
891     * @return array The current options
892     */
893    public function getOptions()
894    {
895        return $this->options;
896    }
897
898    /**
899     * Sets the options for proc_open.
900     *
901     * @param array $options The new options
902     *
903     * @return self The current Process instance
904     */
905    public function setOptions(array $options)
906    {
907        $this->options = $options;
908
909        return $this;
910    }
911
912    /**
913     * Gets whether or not Windows compatibility is enabled
914     *
915     * This is true by default.
916     *
917     * @return Boolean
918     */
919    public function getEnhanceWindowsCompatibility()
920    {
921        return $this->enhanceWindowsCompatibility;
922    }
923
924    /**
925     * Sets whether or not Windows compatibility is enabled
926     *
927     * @param Boolean $enhance
928     *
929     * @return self The current Process instance
930     */
931    public function setEnhanceWindowsCompatibility($enhance)
932    {
933        $this->enhanceWindowsCompatibility = (Boolean) $enhance;
934
935        return $this;
936    }
937
938    /**
939     * Return whether sigchild compatibility mode is activated or not
940     *
941     * @return Boolean
942     */
943    public function getEnhanceSigchildCompatibility()
944    {
945        return $this->enhanceSigchildCompatibility;
946    }
947
948    /**
949     * Activate sigchild compatibility mode
950     *
951     * Sigchild compatibility mode is required to get the exit code and
952     * determine the success of a process when PHP has been compiled with
953     * the --enable-sigchild option
954     *
955     * @param Boolean $enhance
956     *
957     * @return self The current Process instance
958     */
959    public function setEnhanceSigchildCompatibility($enhance)
960    {
961        $this->enhanceSigchildCompatibility = (Boolean) $enhance;
962
963        return $this;
964    }
965
966    /**
967     * Builds up the callback used by wait().
968     *
969     * The callbacks adds all occurred output to the specific buffer and calls
970     * the user callback (if present) with the received output.
971     *
972     * @param callable $callback The user defined PHP callback
973     *
974     * @return mixed A PHP callable
975     */
976    protected function buildCallback($callback)
977    {
978        $that = $this;
979        $out = self::OUT;
980        $err = self::ERR;
981        $callback = function ($type, $data) use ($that, $callback, $out, $err) {
982            if ($out == $type) {
983                $that->addOutput($data);
984            } else {
985                $that->addErrorOutput($data);
986            }
987
988            if (null !== $callback) {
989                call_user_func($callback, $type, $data);
990            }
991        };
992
993        return $callback;
994    }
995
996    /**
997     * Updates the status of the process.
998     */
999    protected function updateStatus()
1000    {
1001        if (self::STATUS_STARTED !== $this->status) {
1002            return;
1003        }
1004
1005        $this->processInformation = proc_get_status($this->process);
1006        if (!$this->processInformation['running']) {
1007            $this->status = self::STATUS_TERMINATED;
1008            if (-1 !== $this->processInformation['exitcode']) {
1009                $this->exitcode = $this->processInformation['exitcode'];
1010            }
1011        }
1012    }
1013
1014    protected function updateErrorOutput()
1015    {
1016        if (isset($this->pipes[self::STDERR]) && is_resource($this->pipes[self::STDERR])) {
1017            $this->addErrorOutput(stream_get_contents($this->pipes[self::STDERR]));
1018        }
1019    }
1020
1021    protected function updateOutput()
1022    {
1023        if (defined('PHP_WINDOWS_VERSION_BUILD') && isset($this->fileHandles[self::STDOUT]) && is_resource($this->fileHandles[self::STDOUT])) {
1024            fseek($this->fileHandles[self::STDOUT], $this->readBytes[self::STDOUT]);
1025            $this->addOutput(stream_get_contents($this->fileHandles[self::STDOUT]));
1026        } elseif (isset($this->pipes[self::STDOUT]) && is_resource($this->pipes[self::STDOUT])) {
1027            $this->addOutput(stream_get_contents($this->pipes[self::STDOUT]));
1028        }
1029    }
1030
1031    /**
1032     * Return whether PHP has been compiled with the '--enable-sigchild' option or not
1033     *
1034     * @return Boolean
1035     */
1036    protected function isSigchildEnabled()
1037    {
1038        if (null !== self::$sigchild) {
1039            return self::$sigchild;
1040        }
1041
1042        ob_start();
1043        phpinfo(INFO_GENERAL);
1044
1045        return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
1046    }
1047
1048    /**
1049     * Handles the windows file handles fallbacks
1050     *
1051     * @param callable $callback A valid PHP callback
1052     * @param Boolean $closeEmptyHandles if true, handles that are empty will be assumed closed
1053     */
1054    private function processFileHandles($callback, $closeEmptyHandles = false)
1055    {
1056        $fh = $this->fileHandles;
1057        foreach ($fh as $type => $fileHandle) {
1058            fseek($fileHandle, $this->readBytes[$type]);
1059            $data = fread($fileHandle, 8192);
1060            if (strlen($data) > 0) {
1061                $this->readBytes[$type] += strlen($data);
1062                call_user_func($callback, $type == 1 ? self::OUT : self::ERR, $data);
1063            }
1064            if (false === $data || ($closeEmptyHandles && '' === $data && feof($fileHandle))) {
1065                fclose($fileHandle);
1066                unset($this->fileHandles[$type]);
1067            }
1068        }
1069    }
1070}
Note: See TracBrowser for help on using the repository browser.