Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 44735cd

Browse filesBrowse files
committed
feature #10627 [Console] added a Process helper (fabpot, romainneutron)
This PR was merged into the 2.3-dev branch. Discussion ---------- [Console] added a Process helper | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | n/a | License | MIT | Doc PR | symfony/symfony-docs#3930 This PR replaces #10609. I've fixed minor errors, added tests and allowed to pass an array of arguments to escape as a command Commits ------- edc1bfe [Console] Add process helper tests faffe7e [Console] added a Process helper
2 parents 8f0b984 + edc1bfe commit 44735cd
Copy full SHA for 44735cd

File tree

Expand file treeCollapse file tree

6 files changed

+376
-0
lines changed
Filter options
Expand file treeCollapse file tree

6 files changed

+376
-0
lines changed

‎src/Symfony/Component/Console/Application.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Application.php
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
use Symfony\Component\Console\Descriptor\TextDescriptor;
1515
use Symfony\Component\Console\Descriptor\XmlDescriptor;
16+
use Symfony\Component\Console\Helper\DebugFormatterHelper;
17+
use Symfony\Component\Console\Helper\ProcessHelper;
1618
use Symfony\Component\Console\Helper\QuestionHelper;
1719
use Symfony\Component\Console\Input\InputInterface;
1820
use Symfony\Component\Console\Input\ArgvInput;
@@ -962,6 +964,8 @@ protected function getDefaultHelperSet()
962964
new DialogHelper(),
963965
new ProgressHelper(),
964966
new TableHelper(),
967+
new DebugFormatterHelper(),
968+
new ProcessHelper(),
965969
new QuestionHelper(),
966970
));
967971
}

‎src/Symfony/Component/Console/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/CHANGELOG.md
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
CHANGELOG
22
=========
33

4+
2.6.0
5+
-----
6+
7+
* added a Process helper
8+
* added a DebugFormatter helper
9+
410
2.5.0
511
-----
612

+127Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Console\Helper;
13+
14+
/**
15+
* Helps outputting debug information when running an external program from a command.
16+
*
17+
* An external program can be a Process, an HTTP request, or anything else.
18+
*
19+
* @author Fabien Potencier <fabien@symfony.com>
20+
*/
21+
class DebugFormatterHelper extends Helper
22+
{
23+
private $colors = array('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white');
24+
private $started = array();
25+
private $count = -1;
26+
27+
/**
28+
* Starts a debug formatting session
29+
*
30+
* @param string $id The id of the formatting session
31+
* @param string $message The message to display
32+
* @param string $prefix The prefix to use
33+
*
34+
* @return string
35+
*/
36+
public function start($id, $message, $prefix = 'RUN')
37+
{
38+
$this->started[$id] = array('border' => ++$this->count % count($this->colors));
39+
40+
return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
41+
}
42+
43+
/**
44+
* Adds progress to a formatting session
45+
*
46+
* @param string $id The id of the formatting session
47+
* @param string $buffer The message to display
48+
* @param bool $error Whether to consider the buffer as error
49+
* @param string $prefix The prefix for output
50+
* @param string $errorPrefix The prefix for error output
51+
*
52+
* @return string
53+
*/
54+
public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR')
55+
{
56+
$message = '';
57+
58+
if ($error) {
59+
if (isset($this->started[$id]['out'])) {
60+
$message .= "\n";
61+
unset($this->started[$id]['out']);
62+
}
63+
if (!isset($this->started[$id]['err'])) {
64+
$message .= sprintf("%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix);
65+
$this->started[$id]['err'] = true;
66+
}
67+
68+
$message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer);
69+
} else {
70+
if (isset($this->started[$id]['err'])) {
71+
$message .= "\n";
72+
unset($this->started[$id]['err']);
73+
}
74+
if (!isset($this->started[$id]['out'])) {
75+
$message .= sprintf("%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix);
76+
$this->started[$id]['out'] = true;
77+
}
78+
79+
$message .= str_replace("\n", sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer);
80+
}
81+
82+
return $message;
83+
}
84+
85+
/**
86+
* Stops a formatting session
87+
*
88+
* @param string $id The id of the formatting session
89+
* @param string $message The message to display
90+
* @param bool $successful Whether to consider the result as success
91+
* @param string $prefix The prefix for the end output
92+
*
93+
* @return string
94+
*/
95+
public function stop($id, $message, $successful, $prefix = 'RES')
96+
{
97+
$trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : '';
98+
99+
if ($successful) {
100+
return sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
101+
}
102+
103+
$message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
104+
105+
unset($this->started[$id]['out'], $this->started[$id]['err']);
106+
107+
return $message;
108+
}
109+
110+
/**
111+
* @param string $id The id of the formatting session
112+
*
113+
* @return string
114+
*/
115+
private function getBorder($id)
116+
{
117+
return sprintf('<bg=%s> </>', $this->colors[$this->started[$id]['border']]);
118+
}
119+
120+
/**
121+
* {@inheritdoc}
122+
*/
123+
public function getName()
124+
{
125+
return 'debug_formatter';
126+
}
127+
}
+129Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Console\Helper;
13+
14+
use Symfony\Component\Console\Output\OutputInterface;
15+
use Symfony\Component\Process\Exception\ProcessFailedException;
16+
use Symfony\Component\Process\Process;
17+
use Symfony\Component\Process\ProcessBuilder;
18+
19+
/**
20+
* The ProcessHelper class provides helpers to run external processes.
21+
*
22+
* @author Fabien Potencier <fabien@symfony.com>
23+
*/
24+
class ProcessHelper extends Helper
25+
{
26+
/**
27+
* Runs an external process.
28+
*
29+
* @param OutputInterface $output An OutputInterface instance
30+
* @param string|array|Process $cmd An instance of Process or an array of arguments to escape and run or a command to run
31+
* @param string|null $error An error message that must be displayed if something went wrong
32+
* @param callable|null $callback A PHP callback to run whenever there is some
33+
* output available on STDOUT or STDERR
34+
*
35+
* @return Process The process that ran
36+
*/
37+
public function run(OutputInterface $output, $cmd, $error = null, $callback = null)
38+
{
39+
$formatter = $this->getHelperSet()->get('debug_formatter');
40+
41+
if (is_array($cmd)) {
42+
$process = ProcessBuilder::create($cmd)->getProcess();
43+
} elseif ($cmd instanceof Process) {
44+
$process = $cmd;
45+
} else {
46+
$process = new Process($cmd);
47+
}
48+
49+
if ($output->isVeryVerbose()) {
50+
$output->write($formatter->start(spl_object_hash($process), $process->getCommandLine()));
51+
}
52+
53+
if ($output->isDebug()) {
54+
$callback = $this->wrapCallback($output, $process, $callback);
55+
}
56+
57+
$process->run($callback);
58+
59+
if ($output->isVeryVerbose()) {
60+
$message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode());
61+
$output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
62+
}
63+
64+
if (!$process->isSuccessful() && null !== $error) {
65+
$output->writeln(sprintf('<error>%s</error>', $error));
66+
}
67+
68+
return $process;
69+
}
70+
71+
/**
72+
* Runs the process.
73+
*
74+
* This is identical to run() except that an exception is thrown if the process
75+
* exits with a non-zero exit code.
76+
*
77+
* @param OutputInterface $output An OutputInterface instance
78+
* @param string|Process $cmd An instance of Process or a command to run
79+
* @param string|null $error An error message that must be displayed if something went wrong
80+
* @param callable|null $callback A PHP callback to run whenever there is some
81+
* output available on STDOUT or STDERR
82+
*
83+
* @return Process The process that ran
84+
*
85+
* @throws ProcessFailedException
86+
*
87+
* @see run()
88+
*/
89+
public function mustRun(OutputInterface $output, $cmd, $error = null, $callback = null)
90+
{
91+
$process = $this->run($output, $cmd, $error, $callback);
92+
93+
if (!$process->isSuccessful()) {
94+
throw new ProcessFailedException($process);
95+
}
96+
97+
return $process;
98+
}
99+
100+
/**
101+
* Wraps a Process callback to add debugging output.
102+
*
103+
* @param OutputInterface $output An OutputInterface interface
104+
* @param Process $process The Process
105+
* @param callable|null $callback A PHP callable
106+
*
107+
* @return callable
108+
*/
109+
public function wrapCallback(OutputInterface $output, Process $process, $callback = null)
110+
{
111+
$formatter = $this->getHelperSet()->get('debug_formatter');
112+
113+
return function ($type, $buffer) use ($output, $process, $callback, $formatter) {
114+
$output->write($formatter->progress(spl_object_hash($process), $buffer, Process::ERR === $type));
115+
116+
if (null !== $callback) {
117+
call_user_func($callback, $type, $buffer);
118+
}
119+
};
120+
}
121+
122+
/**
123+
* {@inheritdoc}
124+
*/
125+
public function getName()
126+
{
127+
return 'process';
128+
}
129+
}
+108Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Console\Tests\Helper;
13+
14+
use Symfony\Component\Console\Helper\DebugFormatterHelper;
15+
use Symfony\Component\Console\Helper\HelperSet;
16+
use Symfony\Component\Console\Helper\Helper;
17+
use Symfony\Component\Console\Output\StreamOutput;
18+
use Symfony\Component\Console\Helper\ProcessHelper;
19+
use Symfony\Component\Process\Process;
20+
21+
class ProcessHelperTest extends \PHPUnit_Framework_TestCase
22+
{
23+
/**
24+
* @dataProvider provideCommandsAndOutput
25+
*/
26+
public function testVariousProcessRuns($expected, $cmd, $verbosity, $error)
27+
{
28+
$helper = new ProcessHelper();
29+
$helper->setHelperSet(new HelperSet(array(new DebugFormatterHelper())));
30+
$output = $this->getOutputStream($verbosity);
31+
$helper->run($output, $cmd, $error);
32+
$this->assertEquals($expected, $this->getOutput($output));
33+
}
34+
35+
public function testPassedCallbackIsExecuted()
36+
{
37+
$helper = new ProcessHelper();
38+
$helper->setHelperSet(new HelperSet(array(new DebugFormatterHelper())));
39+
$output = $this->getOutputStream(StreamOutput::VERBOSITY_NORMAL);
40+
41+
$executed = false;
42+
$callback = function () use (&$executed) { $executed = true; };
43+
44+
$helper->run($output, 'php -r "echo 42;"', null, $callback);
45+
$this->assertTrue($executed);
46+
}
47+
48+
public function provideCommandsAndOutput()
49+
{
50+
$successOutputVerbose = <<<EOT
51+
RUN php -r "echo 42;"
52+
RES Command ran successfully
53+
54+
EOT;
55+
$successOutputDebug = <<<EOT
56+
RUN php -r "echo 42;"
57+
OUT 42
58+
RES Command ran successfully
59+
60+
EOT;
61+
$successOutputProcessDebug = <<<EOT
62+
RUN 'php' '-r' 'echo 42;'
63+
OUT 42
64+
RES Command ran successfully
65+
66+
EOT;
67+
$syntaxErrorOutputVerbose = <<<EOT
68+
RUN php -r "fwrite(STDERR, 'error message');usleep(50000);fwrite(STDOUT, 'out message');exit(252);"
69+
RES 252 Command did not run successfully
70+
71+
EOT;
72+
$syntaxErrorOutputDebug = <<<EOT
73+
RUN php -r "fwrite(STDERR, 'error message');usleep(50000);fwrite(STDOUT, 'out message');exit(252);"
74+
ERR error message
75+
OUT out message
76+
RES 252 Command did not run successfully
77+
78+
EOT;
79+
80+
$errorMessage = 'An error occurred';
81+
82+
return array(
83+
array('', 'php -r "echo 42;"', StreamOutput::VERBOSITY_VERBOSE, null),
84+
array($successOutputVerbose, 'php -r "echo 42;"', StreamOutput::VERBOSITY_VERY_VERBOSE, null),
85+
array($successOutputDebug, 'php -r "echo 42;"', StreamOutput::VERBOSITY_DEBUG, null),
86+
array('', 'php -r "syntax error"', StreamOutput::VERBOSITY_VERBOSE, null),
87+
array($syntaxErrorOutputVerbose, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, null),
88+
array($syntaxErrorOutputDebug, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, null),
89+
array($errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERBOSE, $errorMessage),
90+
array($syntaxErrorOutputVerbose.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, $errorMessage),
91+
array($syntaxErrorOutputDebug.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, $errorMessage),
92+
array($successOutputProcessDebug, array('php', '-r', 'echo 42;'), StreamOutput::VERBOSITY_DEBUG, null),
93+
array($successOutputDebug, new Process('php -r "echo 42;"'), StreamOutput::VERBOSITY_DEBUG, null),
94+
);
95+
}
96+
97+
private function getOutputStream($verbosity)
98+
{
99+
return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, false);
100+
}
101+
102+
private function getOutput(StreamOutput $output)
103+
{
104+
rewind($output->getStream());
105+
106+
return stream_get_contents($output->getStream());
107+
}
108+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.