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

Skip to content

Commit 130608f

Browse files
committed
add method to ProcessBuilder 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 9d6c5d8 commit 130608f

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 @@ pub 10000 lic 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