8000 ProcessBuilder: add method to use the sh wrapper · symfony/symfony@b30a57a · GitHub
[go: up one dir, main page]

Skip to content

Commit b30a57a

Browse files
committed
ProcessBuilder: add method to use the sh wrapper
On Linux, processes built and executed with the Process component are automatically wrapped by PHP inside a call to `sh`. This makes it hard to use methods like `getPid()` or `stop()` since they work with the `sh` process but not with the process the user intended to start. The solution is to prepend the command being executed with [exec](https://en.wikipedia.org/wiki/Exec_%28operating_system%29) (as described in #5759). The `ProcessBuilder` now has two methods `enableShellWrapper()` and `disableShellWrapper()` to enable or disable the shell wrapper.
1 parent 73c7eae commit b30a57a

File tree

2 files changed

+149
-0
lines changed

2 files changed

+149
-0
lines changed

src/Symfony/Component/Process/ProcessBuilder.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Process\Exception\InvalidArgumentException;
1515
use Symfony\Component\Process\Exception\LogicException;
16+
use Symfony\Component\Process\Exception\RuntimeException;
1617

1718
/**
1819
* Process builder.
@@ -30,6 +31,12 @@ class ProcessBuilder
3031
private $inheritEnv = true;
3132
private $prefix = array();
3233
private $outputDisabled = false;
34+
private $shellWrapperEnabled = false;
35+
36+
/**
37+
* @var bool
38+
*/
39+
private static $sigchild;
3340

3441
/**
3542
* Constructor
@@ -251,6 +258,40 @@ public function enableOutput()
251258
return $this;
252259
}
253260

261+
/**
262+
* Do not enable the shell wrapper (i.e. do not prepend the executed command with "exec").
263+
*
264+
* @return ProcessBuilder
265+
*/
266+
public function disableShellWrapper()
267+
{
268+
$this->shellWrapperEnabled = false;
269+
270+
return $this;
271+
}
272+
273+
/**
274+
* Enable the shell wrapper (i.e. prepend the executed command with "exec").
275+
*
276+
* @return ProcessBuilder
277+
*
278+
* @throws RuntimeException if the OS is Windows or if PHP was built with --enable-sigchild
279+
*/
280+
public function enableShellWrapper()
281+
{
282+
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
283+
throw new RuntimeException('The shell wrapper is not supported on Windows platforms.');
284+
}
285+
286+
if ($this->isSigchildEnabled()) {
287+
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The shell wrapper cannot be enabled.');
288+
}
289+
290+
$this->shellWrapperEnabled = true;
291+
292+
return $this;
293+
}
294+
254295
/**
255296
* Creates a Process instance and returns it.
256297
*
@@ -269,6 +310,10 @@ public function getProcess()
269310
$arguments = array_merge($this->prefix, $this->arguments);
270311
$script = implode(' ', array_map(array(__NAMESPACE__.'\\ProcessUtils', 'escapeArgument'), $arguments));
271312

313+
if ($this->shellWrapperEnabled) {
314+
$script = 'exec '.$script;
315+
}
316+
272317
if ($this->inheritEnv) {
273318
// include $_ENV for BC purposes
274319
$env = array_replace($_ENV, $_SERVER, $this->env);
@@ -284,4 +329,25 @@ public function getProcess()
284329

285330
return $process;
286331
}
332+
333+
/**
334+
* Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
335+
*
336+
* @return bool
337+
*/
338+
private function isSigchildEnabled()
339+
{
340+
if (null !== self::$sigchild) {
341+
return self::$sigchild;
342+
}
343+
344+
if (!function_exists('phpinfo')) {
345+
return self::$sigchild = false;
346+
}
347+
348+
ob_start();
349+
phpinfo(INFO_GENERAL);
350+
351+
return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
352+
}
287353
}

src/Symfony/Component/Process/Tests/ProcessBuilderTest.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@
1111

1212
namespace Symfony\Component\Process\Tests;
1313

14+
use Symfony\Component\Process\Exception\RuntimeException;
1415
use Symfony\Component\Process\ProcessBuilder;
1516

1617
class ProcessBuilderTest extends \PHPUnit_Framework_TestCase
1718
{
19+
private static $sigchild;
20+
1821
public function testInheritEnvironmentVars()
1922
{
2023
$_ENV['MY_VAR_1'] = 'foo';
@@ -222,4 +225,84 @@ public function testInvalidInput()
222225
$builder = ProcessBuilder::create();
223226
$builder->setInput(array());
224227
}
228+
229+
public function testShouldNotPrependCommandLineWithExecByDefault()
230+
{
231+
$process = ProcessBuilder::create()
232+
->add('foo')
233+
->getProcess();
234+
235+
$this->assertNotRegExp('/^exec /', $process->getCommandLine());
236+
}
237+
238+
public function testShouldPrependCommandLineWithExec()
239+
{
240+
if ($this->isSigchildEnabled()) {
241+
$this->markTestSkipped('->enableShellWrapper() is not supported in sigchild environment');
242+
}
243+
244+
$process = ProcessBuilder::create()
245+
->add('foo')
246+
->enableShellWrapper()
247+
->getProcess();
248+
249+
$this->assertRegExp('/^exec /', $process->getCommandLine());
250+
}
251+
252+
/**
253+
* @expectedException RuntimeException
254+
* @expectedExceptionMessage The shell wrapper is not supported on Windows platforms.
255+
*/
256+
public function testEnableShellWrapperOnWindowsThrowsException()
257+
{
258+
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
259+
$this->markTestSkipped('->enableShellWrapper() is not supported on Windows');
260+
}
261+
262+
ProcessBuilder::create()->enableShellWrapper();
263+
}
264+
265+
/**
266+
* @expectedException RuntimeException
267+
* @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The shell wrapper cannot be enabled.
268+
*/
269+
public function testEnableShellWrapperInSigchildEnvironmentThrowsException()
270+
{
271+
if (!$this->isSigchildEnabled()) {
272+
$this->markTestSkipped('->enableShellWrapper() is not supported in sigchild environment');
273+
}
274+
275+
ProcessBuilder::create()->enableShellWrapper();
276+
}
277+
278+
public function testShouldNotPrependCommandLineWithExec()
279+
{
280+
$process = ProcessBuilder::create()
281+
->add('foo')
282+
->disableShellWrapper()
283+
->getProcess();
284+
285+
$this->assertNotRegExp('/^exec /', $process->getCommandLine());
286+
}
287+
288+
/**
289+
* Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
290+
*
291+
* @return bool
292+
*/
293+
private function isSigchildEnabled()
294+
{
295+
if (null !== self::$sigchild) {
296+
return self::$sigchild;
297+
}
298+
299+
if (!function_exists('phpinfo')) {
300+
return self::$sigchild = false;
301+
}
302+
303+
ob_start();
304+
phpinfo(INFO_GENERAL);
305+
306+
return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
307+
}
225308
}

0 commit comments

Comments
 (0)
0