Skip to content

Commit 5c7a718

Browse files
authored
Merge pull request #5 from danmichaelo/master
Various changes
2 parents fbf162a + cee2d60 commit 5c7a718

File tree

4 files changed

+104
-34
lines changed

4 files changed

+104
-34
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ or
6464
}
6565
```
6666

67+
By default, all arguments are escaped using
68+
[escapeshellarg](https://secure.php.net/manual/en/function.escapeshellarg.php).
69+
If you need to pass unescaped arguments, use `{!name!}`, like so:
70+
71+
```php
72+
Command::exec('echo {!path!}', ['path' => '$PATH']);
73+
```
74+
6775
## Testing
6876

6977
``` bash

src/Command.php

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,27 @@ private function __construct()
1313
/**
1414
* Execute command with params.
1515
*
16-
* @param string $commandLine
16+
* @param string $command
1717
* @param array $params
1818
*
1919
* @return bool|string
2020
*
2121
* @throws \Exception
2222
*/
23-
public static function exec($commandLine, array $params = array())
23+
public static function exec($command, array $params = array(), $mergeStdErr=true)
2424
{
25-
if (empty($commandLine)) {
26-
throw new \Exception('Command line is empty');
25+
if (empty($command)) {
26+
throw new \InvalidArgumentException('Command line is empty');
2727
}
2828

29-
$commandLine = self::bindParams($commandLine, $params);
29+
$command = self::bindParams($command, $params);
3030

31-
exec($commandLine, $output, $code);
31+
if ($mergeStdErr) {
32+
// Redirect stderr to stdout to include it in $output
33+
$command .= ' 2>&1';
34+
}
35+
36+
exec($command, $output, $code);
3237

3338
if (count($output) === 0) {
3439
$output = $code;
@@ -37,7 +42,7 @@ public static function exec($commandLine, array $params = array())
3742
}
3843

3944
if ($code !== 0) {
40-
throw new \Exception($output . ' Command line: ' . $commandLine);
45+
throw new CommandException($command, $output, $code);
4146
}
4247

4348
return $output;
@@ -46,38 +51,26 @@ public static function exec($commandLine, array $params = array())
4651
/**
4752
* Bind params to command.
4853
*
49-
* @param string $commandLine
54+
* @param string $command
5055
* @param array $params
5156
*
5257
* @return string
5358
*/
54-
public static function bindParams($commandLine, array $params)
59+
public static function bindParams($command, array $params)
5560
{
56-
if (count($params) > 0) {
57-
$wrapper = function ($string) {
58-
return '{' . $string . '}';
59-
};
60-
$converter = function ($var) {
61-
if (is_array($var)) {
62-
$var = implode(' ', $var);
63-
}
64-
65-
return $var;
66-
};
61+
$wrappers = array();
62+
$converters = array();
63+
foreach ($params as $key => $value) {
64+
65+
// Escaped
66+
$wrappers[] = '{' . $key . '}';
67+
$converters[] = escapeshellarg(is_array($value) ? implode(' ', $value) : $value);
6768

68-
$commandLine = str_replace(
69-
array_map(
70-
$wrapper,
71-
array_keys($params)
72-
),
73-
array_map(
74-
$converter,
75-
array_values($params)
76-
),
77-
$commandLine
78-
);
69+
// Unescaped
70+
$wrappers[] = '{!' . $key . '!}';
71+
$converters[] = is_array($value) ? implode(' ', $value) : $value;
7972
}
8073

81-
return $commandLine;
74+
return str_replace($wrappers, $converters, $command);
8275
}
8376
}

src/CommandException.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
namespace pastuhov\Command;
3+
4+
class CommandException extends \RuntimeException
5+
{
6+
protected $command;
7+
protected $output;
8+
protected $returnCode;
9+
10+
public function __construct($command, $output, $returnCode)
11+
{
12+
$this->command = $command;
13+
$this->output = $output;
14+
$this->returnCode = $returnCode;
15+
16+
if ($this->returnCode == 127) {
17+
$message = 'Command not found: "' . $this->getCommand() . '"';
18+
} else {
19+
$message = 'Command "' . $this->getCommand() . '" exited with code ' . $this->getReturnCode() . ': ' . $this->getOutput();
20+
}
21+
22+
parent::__construct($message);
23+
}
24+
25+
public function getCommand()
26+
{
27+
return $this->command;
28+
}
29+
30+
public function getOutput()
31+
{
32+
return $this->output;
33+
}
34+
35+
public function getReturnCode()
36+
{
37+
return $this->returnCode;
38+
}
39+
}

tests/CommandTest.php

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function testExec()
3131
*/
3232
public function testExecException()
3333
{
34-
$this->setExpectedException('Exception');
34+
$this->setExpectedException('pastuhov\Command\CommandException');
3535

3636
$output = Command::exec(
3737
'echo111'
@@ -43,10 +43,40 @@ public function testExecException()
4343
*/
4444
public function testExecEmptyCommand()
4545
{
46-
$this->setExpectedException('Exception');
46+
$this->setExpectedException('InvalidArgumentException');
4747

4848
$output = Command::exec(
4949
''
5050
);
5151
}
52+
53+
/**
54+
* Test that arguments are escaped by default
55+
*/
56+
public function testArgumentsEscapedByDefault()
57+
{
58+
$output = Command::exec(
59+
'echo {phrase}',
60+
[
61+
'phrase' => 'hello $PATH',
62+
]
63+
);
64+
65+
$this->assertEquals('hello $PATH', $output);
66+
}
67+
68+
/**
69+
* Test that unescaped arguments can be passed
70+
*/
71+
public function testUnescapedArguments()
72+
{
73+
$output = Command::exec(
74+
'echo {!phrase!}',
75+
[
76+
'phrase' => 'hello $PATH',
77+
]
78+
);
79+
80+
$this->assertRegexp('/\//', $output);
81+
}
5282
}

0 commit comments

Comments
 (0)