Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 453972a

Browse files
committed
[Processs] Add Process\Command
1 parent a469c56 commit 453972a

File tree

4 files changed

+217
-54
lines changed

4 files changed

+217
-54
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Process;
13+
14+
/**
15+
* @author Romain Neutron <[email protected]>
16+
*/
17+
class Command
18+
{
19+
private $parts = array();
20+
private $appended = array();
21+
private $piped;
22+
private $redirects = array();
23+
24+
public function __construct($parts = null, $escape = true)
25+
{
26+
if (null !== $parts) {
27+
$this->add($parts, $escape);
28+
}
29+
}
30+
31+
public function add($parts, $escape = true, $prepend = false)
32+
{
33+
if (!is_array($parts)) {
34+
$parts = array($parts);
35+
}
36+
37+
if ($prepend) {
38+
$this->parts = ($escape ? array_map([self, 'escape'], $parts) : $parts) + $this->parts;
39+
} else {
40+
$this->parts += $escape ? array_map([self, 'escape'], $parts) : $parts;
41+
}
42+
43+
return $this;
44+
}
45+
46+
public function append(Command $command)
47+
{
48+
$this->appended[] = $command;
49+
50+
return $this;
51+
}
52+
53+
public function pipe(Command $command)
54+
{
55+
$this->piped = $command;
56+
57+
return $command;
58+
}
59+
60+
public function redirect($fd, $target = null, $append = false)
61+
{
62+
$this->redirects[$fd] = ['target' => $target, 'append' => $append];
63+
64+
return $this;
65+
}
66+
67+
public function prepareForexecution()
68+
{
69+
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
70+
return 'cmd /V:ON /E:ON /C "('.$this.')"';
71+
}
72+
73+
return (string) $this;
74+
}
75+
76+
public function __toString()
77+
{
78+
return implode(' ', $this->parts).(count($this->appended > 0) ? ';' : '').implode('; ', $this->appended).$this->getredirects().($this->piped ? ' | '.$this->piped : '');
79+
}
80+
81+
public static function fromString($commandline)
82+
{
83+
$command = new self();
84+
85+
$command->add($commandline, false);
86+
87+
return $command;
88+
}
89+
90+
private function getredirects()
91+
{
92+
$redirects = '';
93+
94+
foreach ($this->redirects as $fd => $props) {
95+
if (null === $props['target']) {
96+
$props['target'] = defined('PHP_WINDOWS_VERSION_BUILD') ? 'NUL' : '/dev/null';
97+
}
98+
$redirects .= ' '.$fd.'>'.($props['append'] ? '>' : '').self::escape($props['target']);
99+
}
100+
101+
return $redirects ? '('.$redirects.')' : '';
102+
}
103+
104+
/**
105+
* Escapes a string to be used as a shell argument.
106+
*
107+
* @param string $argument The argument that will be escaped
108+
*
109+
* @return string The escaped argument
110+
*
111+
* @internal Method is a static public to provide BC to ProcessUtils until Symfony 3.0
112+
* This method will be a private non-static as of Symfony 3.0
113+
*/
114+
public static function escape($argument)
115+
{
116+
//Fix for PHP bug #43784 escapeshellarg removes % from given string
117+
//Fix for PHP bug #49446 escapeshellarg doesn't work on Windows
118+
//@see https://bugs.php.net/bug.php?id=43784
119+
//@see https://bugs.php.net/bug.php?id=49446
120+
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
121+
if ('' === $argument) {
122+
return escapeshellarg($argument);
123+
}
124+
125+
$escapedArgument = '';
126+
$quote = false;
127+
foreach (preg_split('/(")/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) {
128+
if ('"' === $part) {
129+
$escapedArgument .= '\\"';
130+
} elseif (self::isSurroundedBy($part, '%')) {
131+
// Avoid environment variable expansion
132+
$escapedArgument .= '^%"'.substr($part, 1, -1).'"^%';
133+
} else {
134+
// escape trailing backslash
135+
if ('\\' === substr($part, -1)) {
136+
$part .= '\\';
137+
}
138+
$quote = true;
139+
$escapedArgument .= $part;
140+
}
141+
}
142+
if ($quote) {
143+
$escapedArgument = '"'.$escapedArgument.'"';
144+
}
145+
146+
return $escapedArgument;
147+
}
148+
149+
return escapeshellarg($argument);
150+
}
151+
152+
private static function isSurroundedBy($arg, $char)
153+
{
154+
return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1];
155+
}
156+
}

src/Symfony/Component/Process/Process.php

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ public function __construct($commandline, $cwd = null, array $env = null, $input
148148
throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
149149
}
150150

151+
if (!$commandline instanceof Command) {
152+
$commandline = Command::fromString($commandline);
153+
}
154+
151155
$this->commandline = $commandline;
152156
$this->cwd = $cwd;
153157

@@ -274,21 +278,29 @@ public function start($callback = null)
274278
$this->callback = $this->buildCallback($callback);
275279
$descriptors = $this->getDescriptors();
276280

277-
$commandline = $this->commandline;
281+
$commandline = clone $this->commandline;
282+
283+
if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
284+
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
285+
$descriptors = array_merge($descriptors, array(array('pipe', 'w')));
286+
287+
$commandline
288+
->redirect(3, '/dev/null')
289+
->append(new Command('code=$?', false))
290+
->append(new Command('echo $code >&3', false))
291+
->append(new Command('exit $code', false));
292+
}
278293

279294
if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->enhanceWindowsCompatibility) {
280-
$commandline = 'cmd /V:ON /E:ON /C "('.$commandline.')';
281295
foreach ($this->processPipes->getFiles() as $offset => $filename) {
282-
$commandline .= ' '.$offset.'>'.ProcessUtils::escapeArgument($filename);
296+
$commandline->redirect($offset, $filename);
283297
}
284-
$commandline .= '"';
285-
286298
if (!isset($this->options['bypass_shell'])) {
287299
$this->options['bypass_shell'] = true;
288300
}
289301
}
290302

291-
$this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options);
303+
$this->process = proc_open($commandline->prepareForexecution(), $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options);
292304

293305
if (!is_resource($this->process)) {
294306
throw new RuntimeException('Unable to launch a new process.');
@@ -840,14 +852,38 @@ public function addErrorOutput($line)
840852
$this->stderr .= $line;
841853
}
842854

855+
/**
856+
* Gets the command to be executed.
857+
*
858+
* @return Command The command to execute
859+
*/
860+
public function getCommand()
861+
{
862+
return $this->commandline;
863+
}
864+
865+
/**
866+
* Gets the command to be executed.
867+
*
868+
* @param Command $command The command to execute
869+
*
870+
* @return Command The command to execute
871+
*/
872+
public function setCommand(Command $command)
873+
{
874+
return $this->commandline = $command;
875+
}
876+
843877
/**
844878
* Gets the command line to be executed.
845879
*
846880
* @return string The command to execute
881+
*
882+
* @deprecated Deprecated since Symfony 2.6 in favor of setCommand, to be removed in Symfony 3.0
847883
*/
848884
public function getCommandLine()
849885
{
850-
return $this->commandline;
886+
return (string) $this->commandline;
851887
}
852888

853889
/**
@@ -856,10 +892,12 @@ public function getCommandLine()
856892
* @param string $commandline The command to execute
857893
*
858894
* @return self The current Process instance
895+
*
896+
* @deprecated Deprecated since Symfony 2.6 in favor of setCommand, to be removed in Symfony 3.0
859897
*/
860898
public function setCommandLine($commandline)
861899
{
862-
$this->commandline = $commandline;
900+
$this->commandline = Command::fromString($commandline);
863901

864902
return $this;
865903
}
@@ -1256,13 +1294,6 @@ private function getDescriptors()
12561294
}
12571295
$descriptors = $this->processPipes->getDescriptors($this->outputDisabled);
12581296

1259-
if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
1260-
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
1261-
$descriptors = array_merge($descriptors, array(array('pipe', 'w')));
1262-
1263-
$this->commandline = '('.$this->commandline.') 3>/dev/null; code=$?; echo $code >&3; exit $code';
1264-
}
1265-
12661297
return $descriptors;
12671298
}
12681299

src/Symfony/Component/Process/ProcessUtils.php

Lines changed: 6 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
* This class contains static methods only and is not meant to be instantiated.
2020
*
2121
* @author Martin Hasoň <[email protected]>
22+
*
23+
* @internal
24+
* @deprecated Deprecated as of Symfony 2.6, to be removed in symfony 3.0
2225
*/
2326
class ProcessUtils
2427
{
@@ -35,43 +38,12 @@ private function __construct()
3538
* @param string $argument The argument that will be escaped
3639
*
3740
* @return string The escaped argument
41+
*
42+
* @deprecated Deprecated as of Symfony 2.6, to be removed in symfony 3.0
3843
*/
3944
public static function escapeArgument($argument)
4045
{
41-
//Fix for PHP bug #43784 escapeshellarg removes % from given string
42-
//Fix for PHP bug #49446 escapeshellarg doesn't work on Windows
43-
//@see https://bugs.php.net/bug.php?id=43784
44-
//@see https://bugs.php.net/bug.php?id=49446
45-
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
46-
if ('' === $argument) {
47-
return escapeshellarg($argument);
48-
}
49-
50-
$escapedArgument = '';
51-
$quote = false;
52-
foreach (preg_split('/(")/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) {
53-
if ('"' === $part) {
54-
$escapedArgument .= '\\"';
55-
} elseif (self::isSurroundedBy($part, '%')) {
56-
// Avoid environment variable expansion
57-
$escapedArgument .= '^%"'.substr($part, 1, -1).'"^%';
58-
} else {
59-
// escape trailing backslash
60-
if ('\\' === substr($part, -1)) {
61-
$part .= '\\';
62-
}
63-
$quote = true;
64-
$escapedArgument .= $part;
65-
}
66-
}
67-
if ($quote) {
68-
$escapedArgument = '"'.$escapedArgument.'"';
69-
}
70-
71-
return $escapedArgument;
72-
}
73-
74-
return escapeshellarg($argument);
46+
return Command::escape($argument);
7547
}
7648

7749
/**
@@ -103,9 +75,4 @@ public static function validateInput($caller, $input)
10375

10476
return $input;
10577
}
106-
107-
private static function isSurroundedBy($arg, $char)
108-
{
109-
return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1];
110-
}
11178
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Symfony\Component\Process\Tests;
4+
5+
class CommandTest extends \PHPUnit_Framework_TestCase
6+
{
7+
8+
}
9+

0 commit comments

Comments
 (0)