-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[WIP] [Console] Make creating single command app easier #9609
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 |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<?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; | ||
|
||
use Symfony\Component\Console\Command\Command; | ||
use Symfony\Component\Console\Input\InputInterface; | ||
|
||
/** | ||
* Application providing access to just one command. | ||
* | ||
* When a console application only consists of one | ||
* command, having to specify this command's name as first | ||
* argument is superfluous. | ||
* This class simplifies creating and using this | ||
* kind of applications. | ||
* | ||
* Usage: | ||
* | ||
* $cmd = new SimpleCommand(); | ||
* $app = new SingleCommandApplication($cmd, '1.2'); | ||
* $app->run(); | ||
* | ||
* @author Stefaan Lippens <soxofaan@gmail.com> | ||
*/ | ||
class SingleCommandApplication extends Application | ||
{ | ||
/** | ||
* Name of the single accessible command of this application | ||
* @var string | ||
*/ | ||
private $commandName; | ||
|
||
/** | ||
* Constructor to build a "single command" application, given a command. | ||
* | ||
* The application will adopt the same name as the command. | ||
* | ||
* @param Command $command The single (accessible) command for this application | ||
* @param string $version The version of the application | ||
*/ | ||
public function __construct(Command $command, $version = 'UNKNOWN') | ||
{ | ||
parent::__construct($command->getName(), $version); | ||
|
||
// Add the given command as single (accessible) command. | ||
$this->add($command); | ||
$this->commandName = $command->getName(); | ||
|
||
// Override the Application's definition so that it does not | ||
// require a command name as first argument. | ||
$this->getDefinition()->setArguments(); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function getCommandName(InputInterface $input) | ||
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. why this method and why its dependence on $input? 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. it overwrites a method of the parent class, so it cannot change its signature. 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. i see it now, thanks @stof |
||
{ | ||
return $this->commandName; | ||
} | ||
|
||
/** | ||
* Adds a command object. | ||
* | ||
* This function overrides (public) Application::add() | ||
* but should should only be used internally. | ||
* Will raise \LogicException when called | ||
* after the single accessible command is set up | ||
* (from the constructor). | ||
* | ||
* @param Command $command A Command object | ||
* | ||
* @return Command The registered command | ||
* | ||
* @throws \LogicException | ||
*/ | ||
public function add(Command $command) | ||
{ | ||
// Fail if we already set up the single accessible command. | ||
if ($this->commandName) { | ||
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. no need to check, as the constructor requires setting a command 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. yes, but the constructor calls add() So we have to allow three add() calls and throw exception for subsequent add() calls |
||
throw new \LogicException("You should not add additional commands to a SingleCommandApplication instance."); | ||
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. I added this exception based on what @stof said at #9564 (comment) |
||
} | ||
|
||
return parent::add($command); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<?php | ||
|
||
use Symfony\Component\Console\Command\Command; | ||
use Symfony\Component\Console\Input\InputInterface; | ||
use Symfony\Component\Console\Input\InputOption; | ||
use Symfony\Component\Console\Input\InputArgument; | ||
use Symfony\Component\Console\Output\OutputInterface; | ||
|
||
class FooScaCommand extends Command | ||
{ | ||
protected function configure() | ||
{ | ||
$this | ||
->setName('foosca') | ||
->setDescription('The foosca command'); | ||
$this->addArgument( | ||
'items', | ||
InputArgument::IS_ARRAY, | ||
'Items to process' | ||
); | ||
$this->addOption( | ||
'bar', | ||
'b', | ||
InputOption::VALUE_NONE, | ||
'Enable barring' | ||
); | ||
} | ||
|
||
protected function execute(InputInterface $input, OutputInterface $output) | ||
{ | ||
$bar = $input->getOption('bar'); | ||
$output->writeln('<info>FooSca</info>' . ($bar ? ' (barred)': ' (basic)')); | ||
|
||
foreach ($input->getArgument('items') as $item) { | ||
$output->writeln('Item: ' . $item); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
Usage: | ||
foosca [-b|--bar] [items1] ... [itemsN] | ||
|
||
Arguments: | ||
items Items to process | ||
|
||
Options: | ||
--bar (-b) Enable barring | ||
--help (-h) Display this help message. | ||
--quiet (-q) Do not output any message. | ||
--verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug | ||
--version (-V) Display this application version. | ||
--ansi Force ANSI output. | ||
--no-ansi Disable ANSI output. | ||
--no-interaction (-n) Do not ask any interactive question. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
<?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; | ||
|
||
use Symfony\Component\Console\Input\ArgvInput; | ||
use Symfony\Component\Console\Input\InputInterface; | ||
use Symfony\Component\Console\Output\StreamOutput; | ||
use Symfony\Component\Console\SingleCommandApplication; | ||
|
||
class SingleCommandApplicationTest extends \PHPUnit_Framework_TestCase | ||
{ | ||
protected static $fixturesPath; | ||
|
||
public static function setUpBeforeClass() | ||
{ | ||
self::$fixturesPath = realpath(__DIR__ . '/Fixtures/'); | ||
require_once self::$fixturesPath.'/FooCommand.php'; | ||
require_once self::$fixturesPath . '/FooScaCommand.php'; | ||
} | ||
|
||
public function testConstructor() | ||
{ | ||
$application = new SingleCommandApplication(new \FooScaCommand(), 'v2.3'); | ||
$this->assertEquals( | ||
'foosca', | ||
$application->getName(), | ||
'__construct() takes the application name as its first argument' | ||
); | ||
$this->assertEquals( | ||
'v2.3', | ||
$application->getVersion(), | ||
'__construct() takes the application version as its second argument' | ||
); | ||
$this->assertEquals( | ||
array('help', 'list', 'foosca'), | ||
array_keys($application->all()), | ||
'__construct() registered the help and list commands by default' | ||
); | ||
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. we do not wrap long lines in symfony core afaik 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. I've seen this in several places in Symfony. But actually the auto formatting feature of my editor (PhpStorm) chose to do it this way (set up for PSR-2). I don't disagree. 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. We use it in the docs, not in the core code 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. some examples: Symfony\Component\Console\Application::getHelp() 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. @wouterj could you please show counter examples? would be good to compare |
||
} | ||
|
||
/** | ||
* @dataProvider provideRunData | ||
*/ | ||
public function testRun(InputInterface $input, $expectedOutput, $expectedStatusCode=0) | ||
{ | ||
// Set up application. | ||
$application = new SingleCommandApplication(new \FooScaCommand(), '1.234'); | ||
$application->setAutoExit(false); | ||
$application->setCatchExceptions(false); | ||
|
||
// Set up output for application to render to. | ||
$stream = fopen('php://memory', 'w', false); | ||
$output = new StreamOutput($stream); | ||
|
||
// Run application with given input. | ||
$statusCode = $application->run($input, $output); | ||
|
||
// Get generated output (and normalize newlines) | ||
rewind($stream); | ||
$display = stream_get_contents($stream); | ||
$display = str_replace(PHP_EOL, "\n", $display); | ||
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. FYI: I dropped usage of |
||
|
||
$this->assertEquals($expectedStatusCode, $statusCode); | ||
$this->assertEquals($expectedOutput, $display); | ||
} | ||
|
||
public function provideRunData() | ||
{ | ||
$data = array(); | ||
$data[] = array( | ||
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. weird, why not just building an array of arrays? 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. I think it is more readable. 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. yes it is less of an issue, let's move to multidimensional array |
||
new ArgvInput(array('cli.php')), | ||
"FooSca (basic)\n", | ||
); | ||
|
||
$data[] = array( | ||
new ArgvInput(array('cli.php', 'qwe')), | ||
"FooSca (basic)\nItem: qwe\n", | ||
); | ||
|
||
$data[] = array( | ||
new ArgvInput(array('cli.php', '--bar', 'qwe')), | ||
"FooSca (barred)\nItem: qwe\n", | ||
); | ||
|
||
$data[] = array( | ||
new ArgvInput(array('cli.php', '--bar', 'qwe', 'rty')), | ||
"FooSca (barred)\nItem: qwe\nItem: rty\n", | ||
); | ||
|
||
$data[] = array( | ||
new ArgvInput(array('cli.php', 'list')), | ||
"FooSca (basic)\nItem: list\n", | ||
); | ||
|
||
$data[] = array( | ||
new ArgvInput(array('cli.php', 'help')), | ||
"FooSca (basic)\nItem: help\n", | ||
); | ||
|
||
$data[] = array( | ||
new ArgvInput(array('cli.php', '--help')), | ||
file_get_contents(__DIR__ . '/Fixtures/' . '/application_run_foosca_help.txt'), | ||
); | ||
|
||
$data[] = array( | ||
new ArgvInput(array('cli.php', '-h')), | ||
file_get_contents(__DIR__ . '/Fixtures/' . '/application_run_foosca_help.txt'), | ||
); | ||
|
||
$data[] = array( | ||
new ArgvInput(array('cli.php', '--version')), | ||
"foosca version 1.234\n" | ||
); | ||
|
||
$data[] = array( | ||
new ArgvInput(array('cli.php', '-V')), | ||
"foosca version 1.234\n" | ||
); | ||
|
||
return $data; | ||
} | ||
|
||
public function testAddingMoreCommands() | ||
{ | ||
$app = new SingleCommandApplication(new \FooScaCommand()); | ||
$this->setExpectedException('LogicException'); | ||
$app->add(new \FooCommand()); | ||
} | ||
} |
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.
I dropped the $name argument from my previous version here, in favour of reusing the command's name, for the sake of simplicity.