Skip to content

[WIP] [Console] added a Process helper #10609

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Symfony/Component/Console/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ CHANGELOG
2.5.0
-----

* added a Process helper
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about the DebugFormatterHelper ?

* deprecated TableHelper in favor of Table
* deprecated ProgressHelper in favor of ProgressBar
* added a way to set a default command instead of `ListCommand`
Expand Down
82 changes: 82 additions & 0 deletions src/Symfony/Component/Console/Helper/DebugFormatterHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?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\Helper\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');
private $started = array();
private $count = -1;

public function start($id, $message, $prefix = 'RUN')
{
$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);
}

public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR')
{
$message = '';

if ($error) {
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]['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;
}

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);
}

return sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
}

private function getBorder($id)
{
return sprintf('<bg=%s> </>', $this->colors[$this->started[$id]['border']]);
}

/**
* {@inheritDoc}
*/
public function getName()
{
return 'debug_formatter';
}
}
93 changes: 93 additions & 0 deletions src/Symfony/Component/Console/Helper/ProcessHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?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\Helper\Helper;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Process;

/**
* The Process 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|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 callback|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)
{
$verbose = $output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE;
$debug = $output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG;

$formatter = $this->getHelperSet()->get('debug_formatter');

$process = $cmd instanceof Process ? $cmd : new Process($cmd);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a bad idea to allow running non-sanitized commands like this.

I'd rather use the following to run a sanitize command:

$cmd instanceof Process ? $cmd : ProcessBuilder::create(is_array($cmd) ? $cmd : array($cmd))->getProcess();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ProcessBuilder::create(array($cmd)) does not allow running a full command. In this case $cmd must be a command without any of its arguments (something like ls only). This would make it quite useless IMO (people will always have to pass a Process explicitly for real use cases then). Currently, the need to pass the Process explicitly only when they need to use the advanced configuration options

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to run a full command you can use $heper->run($output, array('ls', '-a'))

I've to admit people would be able to run $heper->run($output, 'ls -a'), but it's not 100% safe


if ($verbose) {
$output->write($formatter->start(spl_object_hash($process), $process->getCommandLine()));
}

if ($debug) {
$callback = $this->wrapCallback($output, $process, $callback);
}

$process->run($callback);

if ($verbose) {
$message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run sucessfully', $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;
}

/**
* Wraps a Process callback to add debugging output.
*
* @param OutputInterface $output An OutputInterface interface
* @param callable|null $callback A PHP callable
*/
public function wrapCallback(OutputInterface $output, Process $process, $callback = null)
{
$formatter = $this->getHelperSet()->get('debug_formatter');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we must register this helper in the Application, it would currently fail.
Same applies on this ProcessHelper.


return function ($type, $buffer) use ($output, $process, $callback, $formatter) {
$output->write($formatter->progress(spl_object_hash($process), $buffer, 'err' === $type));

if (null !== $callback) {
$callback($type, $buffer);
}
};
}

/**
* {@inheritDoc}
*/
public function getName()
{
return 'process';
}
}
1 change: 1 addition & 0 deletions src/Symfony/Component/Console/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
},
"suggest": {
"symfony/event-dispatcher": "",
"symfony/process": "",
"psr/log": "For using the console logger"
},
"autoload": {
Expand Down