-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Console] added a Process helper #10627
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,12 @@ | ||
CHANGELOG | ||
========= | ||
|
||
2.6.0 | ||
----- | ||
|
||
* added a Process helper | ||
* added a DebugFormatter helper | ||
|
||
2.5.0 | ||
----- | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Console\Helper; | ||
|
||
/** | ||
* Helps outputting debug information when running an external program from a command. | ||
* | ||
* An external program can be a Process, an HTTP request, or anything else. | ||
* | ||
* @author Fabien Potencier <fabien@symfony.com> | ||
*/ | ||
class DebugFormatterHelper extends Helper | ||
{ | ||
private $colors = array('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so there is no way to change these colors? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are the only colors supported in the Console component: https://github.com/symfony/Console/blob/master/Formatter/OutputFormatterStyle.php#L23 and AFAIK, these are the only colors widely supported (some terminals support more colors, but then you would tie your program to users of these terminals) |
||
private $started = array(); | ||
private $count = -1; | ||
|
||
/** | ||
* Starts a debug formatting session | ||
* | ||
* @param string $id The id of the formatting session | ||
* @param string $message The message to display | ||
* @param string $prefix The prefix to use | ||
* | ||
* @return string | ||
*/ | ||
public function start($id, $message, $prefix = 'RUN') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add the phpdoc for the methods |
||
{ | ||
$this->started[$id] = array('border' => ++$this->count % count($this->colors)); | ||
|
||
return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message); | ||
} | ||
|
||
/** | ||
* Adds progress to a formatting session | ||
* | ||
* @param string $id The id of the formatting session | ||
* @param string $buffer The message to display | ||
* @param bool $error Whether to consider the buffer as error | ||
* @param string $prefix The prefix for output | ||
* @param string $errorPrefix The prefix for error output | ||
* | ||
* @return string | ||
*/ | ||
public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR') | ||
{ | ||
$message = ''; | ||
|
||
if ($error) { | ||
if (isset($this->started[$id]['out'])) { | ||
$message .= "\n"; | ||
unset($this->started[$id]['out']); | ||
} | ||
if (!isset($this->started[$id]['err'])) { | ||
$message .= sprintf("%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix); | ||
$this->started[$id]['err'] = true; | ||
} | ||
|
||
$message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer); | ||
} else { | ||
if (isset($this->started[$id]['err'])) { | ||
$message .= "\n"; | ||
unset($this->started[$id]['err']); | ||
} | ||
if (!isset($this->started[$id]['out'])) { | ||
$message .= sprintf("%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix); | ||
$this->started[$id]['out'] = true; | ||
} | ||
|
||
$message .= str_replace("\n", sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer); | ||
} | ||
|
||
return $message; | ||
} | ||
|
||
/** | ||
* Stops a formatting session | ||
* | ||
* @param string $id The id of the formatting session | ||
* @param string $message The message to display | ||
* @param bool $successful Whether to consider the result as success | ||
* @param string $prefix The prefix for the end output | ||
* | ||
* @return string | ||
*/ | ||
public function stop($id, $message, $successful, $prefix = 'RES') | ||
{ | ||
$trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; | ||
|
||
if ($successful) { | ||
return sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message); | ||
} | ||
|
||
$message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message); | ||
|
||
unset($this->started[$id]['out'], $this->started[$id]['err']); | ||
|
||
return $message; | ||
} | ||
|
||
/** | ||
* @param string $id The id of the formatting session | ||
* | ||
* @return string | ||
*/ | ||
private function getBorder($id) | ||
{ | ||
return sprintf('<bg=%s> </>', $this->colors[$this->started[$id]['border']]); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getName() | ||
{ | ||
return 'debug_formatter'; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Console\Helper; | ||
|
||
use Symfony\Component\Console\Output\OutputInterface; | ||
use Symfony\Component\Process\Exception\ProcessFailedException; | ||
use Symfony\Component\Process\Process; | ||
use Symfony\Component\Process\ProcessBuilder; | ||
|
||
/** | ||
* The ProcessHelper class provides helpers to run external processes. | ||
* | ||
* @author Fabien Potencier <fabien@symfony.com> | ||
*/ | ||
class ProcessHelper extends Helper | ||
{ | ||
/** | ||
* Runs an external process. | ||
* | ||
* @param OutputInterface $output An OutputInterface instance | ||
* @param string|array|Process $cmd An instance of Process or an array of arguments to escape and run or a command to run | ||
* @param string|null $error An error message that must be displayed if something went wrong | ||
* @param callable|null $callback A PHP callback to run whenever there is some | ||
* output available on STDOUT or STDERR | ||
* | ||
* @return Process The process that ran | ||
*/ | ||
public function run(OutputInterface $output, $cmd, $error = null, $callback = null) | ||
{ | ||
$formatter = $this->getHelperSet()->get('debug_formatter'); | ||
|
||
if (is_array($cmd)) { | ||
$process = ProcessBuilder::create($cmd)->getProcess(); | ||
} elseif ($cmd instanceof Process) { | ||
$process = $cmd; | ||
} else { | ||
$process = new Process($cmd); | ||
} | ||
|
||
if ($output->isVeryVerbose()) { | ||
$output->write($formatter->start(spl_object_hash($process), $process->getCommandLine())); | ||
} | ||
|
||
if ($output->isDebug()) { | ||
$callback = $this->wrapCallback($output, $process, $callback); | ||
} | ||
|
||
$process->run($callback); | ||
|
||
if ($output->isVeryVerbose()) { | ||
$message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode()); | ||
$output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful())); | ||
} | ||
|
||
if (!$process->isSuccessful() && null !== $error) { | ||
$output->writeln(sprintf('<error>%s</error>', $error)); | ||
} | ||
|
||
return $process; | ||
} | ||
|
||
/** | ||
* Runs the process. | ||
* | ||
* This is identical to run() except that an exception is thrown if the process | ||
* exits with a non-zero exit code. | ||
* | ||
* @param OutputInterface $output An OutputInterface instance | ||
* @param string|Process $cmd An instance of Process or a command to run | ||
* @param string|null $error An error message that must be displayed if something went wrong | ||
* @param callable|null $callback A PHP callback to run whenever there is some | ||
* output available on STDOUT or STDERR | ||
* | ||
* @return Process The process that ran | ||
* | ||
* @throws ProcessFailedException | ||
* | ||
* @see run() | ||
*/ | ||
public function mustRun(OutputInterface $output, $cmd, $error = null, $callback = null) | ||
{ | ||
$process = $this->run($output, $cmd, $error, $callback); | ||
|
||
if (!$process->isSuccessful()) { | ||
throw new ProcessFailedException($process); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does not seem to exist?! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. however, the use statement is indeed missing |
||
} | ||
|
||
return $process; | ||
} | ||
|
||
/** | ||
* Wraps a Process callback to add debugging output. | ||
* | ||
* @param OutputInterface $output An OutputInterface interface | ||
* @param Process $process The Process | ||
* @param callable|null $callback A PHP callable | ||
* | ||
* @return callable | ||
*/ | ||
public function wrapCallback(OutputInterface $output, Process $process, $callback = null) | ||
{ | ||
$formatter = $this->getHelperSet()->get('debug_formatter'); | ||
|
||
return function ($type, $buffer) use ($output, $process, $callback, $formatter) { | ||
$output->write($formatter->progress(spl_object_hash($process), $buffer, Process::ERR === $type)); | ||
|
||
if (null !== $callback) { | ||
call_user_func($callback, $type, $buffer); | ||
} | ||
}; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getName() | ||
{ | ||
return 'process'; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Console\Tests\Helper; | ||
|
||
use Symfony\Component\Console\Helper\DebugFormatterHelper; | ||
use Symfony\Component\Console\Helper\HelperSet; | ||
use Symfony\Component\Console\Helper\Helper; | ||
use Symfony\Component\Console\Output\StreamOutput; | ||
use Symfony\Component\Console\Helper\ProcessHelper; | ||
use Symfony\Component\Process\Process; | ||
|
||
class ProcessHelperTest extends \PHPUnit_Framework_TestCase | ||
{ | ||
/** | ||
* @dataProvider provideCommandsAndOutput | ||
*/ | ||
public function testVariousProcessRuns($expected, $cmd, $verbosity, $error) | ||
{ | ||
$helper = new ProcessHelper(); | ||
$helper->setHelperSet(new HelperSet(array(new DebugFormatterHelper()))); | ||
$output = $this->getOutputStream($verbosity); | ||
$helper->run($output, $cmd, $error); | ||
$this->assertEquals($expected, $this->getOutput($output)); | ||
} | ||
|
||
public function testPassedCallbackIsExecuted() | ||
{ | ||
$helper = new ProcessHelper(); | ||
$helper->setHelperSet(new HelperSet(array(new DebugFormatterHelper()))); | ||
$output = $this->getOutputStream(StreamOutput::VERBOSITY_NORMAL); | ||
|
||
$executed = false; | ||
$callback = function () use (&$executed) { $executed = true; }; | ||
|
||
$helper->run($output, 'php -r "echo 42;"', null, $callback); | ||
$this->assertTrue($executed); | ||
} | ||
|
||
public function provideCommandsAndOutput() | ||
{ | ||
$successOutputVerbose = <<<EOT | ||
RUN php -r "echo 42;" | ||
RES Command ran successfully | ||
|
||
EOT; | ||
$successOutputDebug = <<<EOT | ||
RUN php -r "echo 42;" | ||
OUT 42 | ||
RES Command ran successfully | ||
|
||
EOT; | ||
$successOutputProcessDebug = <<<EOT | ||
RUN 'php' '-r' 'echo 42;' | ||
OUT 42 | ||
RES Command ran successfully | ||
|
||
EOT; | ||
$syntaxErrorOutputVerbose = <<<EOT | ||
RUN php -r "fwrite(STDERR, 'error message');usleep(50000);fwrite(STDOUT, 'out message');exit(252);" | ||
RES 252 Command did not run successfully | ||
|
||
EOT; | ||
$syntaxErrorOutputDebug = <<<EOT | ||
RUN php -r "fwrite(STDERR, 'error message');usleep(50000);fwrite(STDOUT, 'out message');exit(252);" | ||
ERR error message | ||
OUT out message | ||
RES 252 Command did not run successfully | ||
|
||
EOT; | ||
|
||
$errorMessage = 'An error occurred'; | ||
|
||
return array( | ||
array('', 'php -r "echo 42;"', StreamOutput::VERBOSITY_VERBOSE, null), | ||
array($successOutputVerbose, 'php -r "echo 42;"', StreamOutput::VERBOSITY_VERY_VERBOSE, null), | ||
array($successOutputDebug, 'php -r "echo 42;"', StreamOutput::VERBOSITY_DEBUG, null), | ||
array('', 'php -r "syntax error"', StreamOutput::VERBOSITY_VERBOSE, null), | ||
array($syntaxErrorOutputVerbose, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, null), | ||
array($syntaxErrorOutputDebug, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, null), | ||
array($errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERBOSE, $errorMessage), | ||
array($syntaxErrorOutputVerbose.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, $errorMessage), | ||
array($syntaxErrorOutputDebug.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, $errorMessage), | ||
array($successOutputProcessDebug, array('php', '-r', 'echo 42;'), StreamOutput::VERBOSITY_DEBUG, null), | ||
array($successOutputDebug, new Process('php -r "echo 42;"'), StreamOutput::VERBOSITY_DEBUG, null), | ||
); | ||
} | ||
|
||
private function getOutputStream($verbosity) | ||
{ | ||
return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, false); | ||
} | ||
|
||
private function getOutput(StreamOutput $output) | ||
{ | ||
rewind($output->getStream()); | ||
|
||
return stream_get_contents($output->getStream()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those changes can't land in
2.5
as it was already released, it can land only in2.6+
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's right :) it's fixed