Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 453 lines (397 sloc) 12.262 kb
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
02e9773 @fabpot replaced symfony-project.org by symfony.com
fabpot authored
6 * (c) Fabien Potencier <fabien@symfony.com>
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
d1f5d99 @blue-eyes normalized license messages in PHP files
blue-eyes authored
12 namespace Symfony\Component\Process;
13
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
14 /**
15 * Process is a thin wrapper around proc_* functions to ease
b271113 @rande [Process] remove fork references as proc_* functions start a command fro...
rande authored
16 * start independent PHP processes.
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
17 *
02e9773 @fabpot replaced symfony-project.org by symfony.com
fabpot authored
18 * @author Fabien Potencier <fabien@symfony.com>
393cc2a @fabpot [Process] tagged the guaranteed BC API
fabpot authored
19 *
20 * @api
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
21 */
22 class Process
23 {
1236c70 @fabpot [Process] added some constant to avoid harcoded strings
fabpot authored
24 const ERR = 'err';
25 const OUT = 'out';
26
4ad2d88 @fabpot [Process] moved protected to private
fabpot authored
27 private $commandline;
28 private $cwd;
29 private $env;
30 private $stdin;
31 private $timeout;
32 private $options;
33 private $exitcode;
34 private $status;
35 private $stdout;
36 private $stderr;
b4bfbdc @Seldaek [Process] Add windows compatibility to Process component
Seldaek authored
37 private $enhanceWindowsCompatibility = true;
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
38
39 /**
f381eee @fabpot [Process] added Process::getExitCodeText() (closes #2818)
fabpot authored
40 * Exit codes translation table.
41 *
42 * @var array
43 */
44 static public $exitCodes = array(
45 0 => 'OK',
46 1 => 'General error',
47 2 => 'Misuse of shell builtins',
48
49 126 => 'Invoked command cannot execute',
50 127 => 'Command not found',
51 128 => 'Invalid exit argument',
52
53 // signals
54 129 => 'Hangup',
55 130 => 'Interrupt',
56 131 => 'Quit and dump core',
57 132 => 'Illegal instruction',
58 133 => 'Trace/breakpoint trap',
59 134 => 'Process aborted',
60 135 => 'Bus error: "access to undefined portion of memory object"',
61 136 => 'Floating point exception: "erroneous arithmetic operation"',
62 137 => 'Kill (terminate immediately)',
63 138 => 'User-defined 1',
64 139 => 'Segmentation violation',
65 140 => 'User-defined 2',
66 141 => 'Write to pipe with no one reading',
67 142 => 'Signal raised by alarm',
68 143 => 'Termination (request to terminate)',
69 // 144 - not defined
70 145 => 'Child process terminated, stopped (or continued*)',
71 146 => 'Continue if stopped',
72 147 => 'Stop executing temporarily',
73 148 => 'Terminal stop signal',
74 149 => 'Background process attempting to read from tty ("in")',
75 150 => 'Background process attempting to write to tty ("out")',
76 151 => 'Urgent data available on socket',
77 152 => 'CPU time limit exceeded',
78 153 => 'File size limit exceeded',
79 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
80 155 => 'Profiling timer expired',
81 // 156 - not defined
82 157 => 'Pollable event',
83 // 158 - not defined
84 159 => 'Bad syscall',
85 );
86
87 /**
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
88 * Constructor.
89 *
90 * @param string $commandline The command line to run
91 * @param string $cwd The working directory
232fb3c @Seldaek Fix env inheritance and added tests
Seldaek authored
92 * @param array $env The environment variables or null to inherit
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
93 * @param string $stdin The STDIN content
94 * @param integer $timeout The timeout in seconds
95 * @param array $options An array of options for proc_open
96 *
97 * @throws \RuntimeException When proc_open is not installed
393cc2a @fabpot [Process] tagged the guaranteed BC API
fabpot authored
98 *
99 * @api
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
100 */
ec62915 @schniper Fix process creation under Win7 Ultimate (app/console assetic:dump ./web...
schniper authored
101 public function __construct($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array())
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
102 {
103 if (!function_exists('proc_open')) {
104 throw new \RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
105 }
106
107 $this->commandline = $commandline;
108 $this->cwd = null === $cwd ? getcwd() : $cwd;
ec62915 @schniper Fix process creation under Win7 Ultimate (app/console assetic:dump ./web...
schniper authored
109 if (null !== $env) {
110 $this->env = array();
111 foreach ($env as $key => $value) {
112 $this->env[(binary) $key] = (binary) $value;
113 }
114 } else {
115 $this->env = null;
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
116 }
117 $this->stdin = $stdin;
118 $this->timeout = $timeout;
ec62915 @schniper Fix process creation under Win7 Ultimate (app/console assetic:dump ./web...
schniper authored
119 $this->options = array_merge(array('suppress_errors' => true, 'binary_pipes' => true, 'bypass_shell' => false), $options);
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
120 }
121
122 /**
64a393a @fabpot [Process] tweaked php doc and fixed PhpProcess::run() return value
fabpot authored
123 * Runs the process.
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
124 *
125 * The callback receives the type of output (out or err) and
126 * some bytes from the output in real-time. It allows to have feedback
b271113 @rande [Process] remove fork references as proc_* functions start a command fro...
rande authored
127 * from the independent process during execution.
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
128 *
17e4357 @fabpot [Process] changed run() behavior to always populate getOutput() and getE...
fabpot authored
129 * The STDOUT and STDERR are also available after the process is finished
130 * via the getOutput() and getErrorOutput() methods.
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
131 *
132 * @param Closure|string|array $callback A PHP callback to run whenever there is some
133 * output available on STDOUT or STDERR
134 *
135 * @return integer The exit status code
136 *
137 * @throws \RuntimeException When process can't be launch or is stopped
393cc2a @fabpot [Process] tagged the guaranteed BC API
fabpot authored
138 *
139 * @api
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
140 */
141 public function run($callback = null)
142 {
17e4357 @fabpot [Process] changed run() behavior to always populate getOutput() and getE...
fabpot authored
143 $this->stdout = '';
144 $this->stderr = '';
145 $that = $this;
379b35a @fabpot [Process] fixed usage of constants in closure
fabpot authored
146 $out = self::OUT;
147 $err = self::ERR;
148 $callback = function ($type, $data) use ($that, $callback, $out, $err)
17e4357 @fabpot [Process] changed run() behavior to always populate getOutput() and getE...
fabpot authored
149 {
379b35a @fabpot [Process] fixed usage of constants in closure
fabpot authored
150 if ($out == $type) {
a6f47e2 @lenar Make run() fully non-blocking and fix potential other problems
lenar authored
151 $that->addOutput($data);
17e4357 @fabpot [Process] changed run() behavior to always populate getOutput() and getE...
fabpot authored
152 } else {
a6f47e2 @lenar Make run() fully non-blocking and fix potential other problems
lenar authored
153 $that->addErrorOutput($data);
17e4357 @fabpot [Process] changed run() behavior to always populate getOutput() and getE...
fabpot authored
154 }
155
156 if (null !== $callback) {
a6f47e2 @lenar Make run() fully non-blocking and fix potential other problems
lenar authored
157 call_user_func($callback, $type, $data);
17e4357 @fabpot [Process] changed run() behavior to always populate getOutput() and getE...
fabpot authored
158 }
159 };
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
160
2eff085 @fabpot [Process] removed workaround as it seems to not work anymore after the r...
fabpot authored
161 $descriptors = array(array('pipe', 'r'), array('pipe', 'w'), array('pipe', 'w'));
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
162
b4bfbdc @Seldaek [Process] Add windows compatibility to Process component
Seldaek authored
163 $commandline = $this->commandline;
164
165 if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->enhanceWindowsCompatibility) {
166 $commandline = 'cmd /V:ON /E:ON /C "'.$commandline.'"';
167 }
168
169 $process = proc_open($commandline, $descriptors, $pipes, $this->cwd, $this->env, $this->options);
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
170
171 if (!is_resource($process)) {
172 throw new \RuntimeException('Unable to launch a new process.');
173 }
174
a6f47e2 @lenar Make run() fully non-blocking and fix potential other problems
lenar authored
175 foreach ($pipes as $pipe) {
176 stream_set_blocking($pipe, false);
177 }
9d379d2 @fabpot fixed CS
fabpot authored
178
a6f47e2 @lenar Make run() fully non-blocking and fix potential other problems
lenar authored
179 if (null === $this->stdin) {
180 fclose($pipes[0]);
9d379d2 @fabpot fixed CS
fabpot authored
181 $writePipes = null;
a6f47e2 @lenar Make run() fully non-blocking and fix potential other problems
lenar authored
182 } else {
183 $writePipes = array($pipes[0]);
184 $stdinLen = strlen($this->stdin);
185 $stdinOffset = 0;
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
186 }
a6f47e2 @lenar Make run() fully non-blocking and fix potential other problems
lenar authored
187 unset($pipes[0]);
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
188
a6f47e2 @lenar Make run() fully non-blocking and fix potential other problems
lenar authored
189 while ($pipes || $writePipes) {
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
190 $r = $pipes;
a6f47e2 @lenar Make run() fully non-blocking and fix potential other problems
lenar authored
191 $w = $writePipes;
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
192 $e = null;
193
194 $n = @stream_select($r, $w, $e, $this->timeout);
195
66903a7 @Seldaek CS: Unified strict equality comparisons, put var on the right side
Seldaek authored
196 if (false === $n) {
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
197 break;
198 } elseif ($n === 0) {
199 proc_terminate($process);
200
201 throw new \RuntimeException('The process timed out.');
a6f47e2 @lenar Make run() fully non-blocking and fix potential other problems
lenar authored
202 }
203
204 if ($w) {
205 $written = fwrite($writePipes[0], (binary) substr($this->stdin, $stdinOffset), 8192);
206 if (false !== $written) {
207 $stdinOffset += $written;
208 }
209 if ($stdinOffset >= $stdinLen) {
210 fclose($writePipes[0]);
211 $writePipes = null;
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
212 }
a6f47e2 @lenar Make run() fully non-blocking and fix potential other problems
lenar authored
213 }
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
214
a6f47e2 @lenar Make run() fully non-blocking and fix potential other problems
lenar authored
215 foreach ($r as $pipe) {
216 $type = array_search($pipe, $pipes);
217 $data = fread($pipe, 8192);
218 if (strlen($data) > 0) {
379b35a @fabpot [Process] fixed usage of constants in closure
fabpot authored
219 call_user_func($callback, $type == 1 ? $out : $err, $data);
a6f47e2 @lenar Make run() fully non-blocking and fix potential other problems
lenar authored
220 }
221 if (false === $data || feof($pipe)) {
222 fclose($pipe);
223 unset($pipes[$type]);
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
224 }
225 }
226 }
227
228 $this->status = proc_get_status($process);
229
dfa1184 @fabpot [Console] fixed Process exit code
fabpot authored
230 $time = 0;
231 while (1 == $this->status['running'] && $time < 1000000) {
232 $time += 1000;
233 usleep(1000);
234 $this->status = proc_get_status($process);
235 }
236
83186a5 @fabpot [Process] fixed CS
fabpot authored
237 $exitcode = proc_close($process);
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
238
239 if ($this->status['signaled']) {
240 throw new \RuntimeException(sprintf('The process stopped because of a "%s" signal.', $this->status['stopsig']));
241 }
83186a5 @fabpot [Process] fixed CS
fabpot authored
242
243 return $this->exitcode = $this->status['running'] ? $exitcode : $this->status['exitcode'];
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
244 }
245
246 /**
247 * Returns the output of the process (STDOUT).
248 *
249 * This only returns the output if you have not supplied a callback
250 * to the run() method.
251 *
252 * @return string The process output
393cc2a @fabpot [Process] tagged the guaranteed BC API
fabpot authored
253 *
254 * @api
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
255 */
256 public function getOutput()
257 {
258 return $this->stdout;
259 }
260
261 /**
262 * Returns the error output of the process (STDERR).
263 *
264 * This only returns the error output if you have not supplied a callback
265 * to the run() method.
266 *
267 * @return string The process error output
393cc2a @fabpot [Process] tagged the guaranteed BC API
fabpot authored
268 *
269 * @api
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
270 */
271 public function getErrorOutput()
272 {
273 return $this->stderr;
274 }
275
276 /**
277 * Returns the exit code returned by the process.
278 *
279 * @return integer The exit status code
393cc2a @fabpot [Process] tagged the guaranteed BC API
fabpot authored
280 *
281 * @api
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
282 */
283 public function getExitCode()
284 {
285 return $this->exitcode;
286 }
287
288 /**
f381eee @fabpot [Process] added Process::getExitCodeText() (closes #2818)
fabpot authored
289 * Returns a string representation for the exit code returned by the process.
290 *
291 * This method relies on the Unix exit code status standardization
292 * and might not be relevant for other operating systems.
293 *
294 * @return string A string representation for the exit status code
295 *
296 * @see http://tldp.org/LDP/abs/html/exitcodes.html
297 * @see http://en.wikipedia.org/wiki/Unix_signal
298 */
299 public function getExitCodeText()
300 {
301 return isset(self::$exitCodes[$this->exitcode]) ? self::$exitCodes[$this->exitcode] : 'Unknown error';
302 }
303
304 /**
bcc019c @fabpot [Process] added a Process:isSucessful() method
fabpot authored
305 * Checks if the process ended successfully.
306 *
307 * @return Boolean true if the process ended successfully, false otherwise
393cc2a @fabpot [Process] tagged the guaranteed BC API
fabpot authored
308 *
309 * @api
bcc019c @fabpot [Process] added a Process:isSucessful() method
fabpot authored
310 */
311 public function isSuccessful()
312 {
313 return 0 == $this->exitcode;
314 }
315
316 /**
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
317 * Returns true if the child process has been terminated by an uncaught signal.
318 *
319 * It always returns false on Windows.
320 *
321 * @return Boolean
393cc2a @fabpot [Process] tagged the guaranteed BC API
fabpot authored
322 *
323 * @api
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
324 */
325 public function hasBeenSignaled()
326 {
327 return $this->status['signaled'];
328 }
329
330 /**
331 * Returns the number of the signal that caused the child process to terminate its execution.
332 *
333 * It is only meaningful if hasBeenSignaled() returns true.
334 *
335 * @return integer
393cc2a @fabpot [Process] tagged the guaranteed BC API
fabpot authored
336 *
337 * @api
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
338 */
339 public function getTermSignal()
340 {
341 return $this->status['termsig'];
342 }
343
344 /**
345 * Returns true if the child process has been stopped by a signal.
346 *
347 * It always returns false on Windows.
348 *
349 * @return Boolean
393cc2a @fabpot [Process] tagged the guaranteed BC API
fabpot authored
350 *
351 * @api
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
352 */
353 public function hasBeenStopped()
354 {
355 return $this->status['stopped'];
356 }
357
358 /**
359 * Returns the number of the signal that caused the child process to stop its execution
360 *
361 * It is only meaningful if hasBeenStopped() returns true.
362 *
363 * @return integer
393cc2a @fabpot [Process] tagged the guaranteed BC API
fabpot authored
364 *
365 * @api
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
366 */
367 public function getStopSignal()
368 {
369 return $this->status['stopsig'];
370 }
371
372 public function addOutput($line)
373 {
374 $this->stdout .= $line;
375 }
376
377 public function addErrorOutput($line)
378 {
379 $this->stderr .= $line;
380 }
4ad2d88 @fabpot [Process] moved protected to private
fabpot authored
381
64a1663 @fabpot [Process] fixed previous commit
fabpot authored
382 public function getCommandLine()
383 {
384 return $this->commandline;
385 }
386
4ad2d88 @fabpot [Process] moved protected to private
fabpot authored
387 public function setCommandLine($commandline)
388 {
389 $this->commandline = $commandline;
390 }
5d60173 @fabpot [Process] added some missing accessors/mutators
fabpot authored
391
392 public function getTimeout()
393 {
394 return $this->timeout;
395 }
396
397 public function setTimeout($timeout)
398 {
399 $this->timeout = $timeout;
400 }
401
402 public function getWorkingDirectory()
403 {
404 return $this->cwd;
405 }
406
407 public function setWorkingDirectory($cwd)
408 {
409 $this->cwd = $cwd;
410 }
411
412 public function getEnv()
413 {
414 return $this->env;
415 }
416
417 public function setEnv(array $env)
418 {
419 $this->env = $env;
420 }
421
422 public function getStdin()
423 {
424 return $this->stdin;
425 }
426
427 public function setStdin($stdin)
428 {
429 $this->stdin = $stdin;
430 }
431
432 public function getOptions()
433 {
434 return $this->options;
435 }
436
437 public function setOptions(array $options)
438 {
439 $this->options = $options;
440 }
b4bfbdc @Seldaek [Process] Add windows compatibility to Process component
Seldaek authored
441
442 public function getEnhanceWindowsCompatibility()
443 {
444 return $this->enhanceWindowsCompatibility;
445 }
446
447 public function setEnhanceWindowsCompatibility($enhance)
448 {
449 $this->enhanceWindowsCompatibility = (Boolean) $enhance;
450 }
451
6f25f5b @fabpot renamed Symfony\Components to Symfony\Component
fabpot authored
452 }
Something went wrong with that request. Please try again.