Awesome
Windwalker Console
The Windwalker Console package provides an elegant and nested command structure for your cli application.
Installation via Composer
Add this to the require block in your composer.json
.
{
"require": {
"windwalker/console": "~3.0"
}
}
The Nested Command Structure
Console Application (RootCommand)
|
----------------------
| |
CommandA CommandB
| |
------------ ------------
| | | |
CommandC CommandD CommandE CommandF
If we type:
$ php cli/console.php commandA commandC foo bar -a -bc -d=e --flower=sakura
Then we will been direct to CommandC
class, and the following foo bar
will be arguments.
class CommandC extend AbstractCommand
{
public function execute()
{
$arg1 = $this->getArgument(0); // foo
$arg2 = $this->getArgument(0); // bar
$opt = $this->io->get('d') // e
$opt = $this->io->get('flower') // sakura
}
}
Initialising Console
Console is the main application help us create a command line program.
An example console application skeleton in cli/console.php
file:
<?php
// Load the Composer autoloader
include __DIR__ . '/../vendor/autoload.php';
use Windwalker\Console\Console;
$console = new Console;
$console->execute();
The execute()
will find commands matched the cli input argument. If there are not any command registered,
console will execute the Default Command
.
Default RootCommand
RootCommand
is a command object extends from base Command
. It provides some useful helpers,
we can list all commands by typing:
$ php cli/app.php
By default, the output is:
Windwalker Console - version: 1.0
------------------------------------------------------------
[console.php Help]
The default application command
Usage:
console.php <command> [option]
Options:
-h | --help Display this help message.
-q | --quiet Do not output any message.
-v | --verbose Increase the verbosity of messages.
--ansi Set 'off' to suppress ANSI colors on unsupported terminals.
Welcome to Windwalker Console.
Set Handler for RootCommand
We can add closure to every commands, that this command will execute this function first. Use setHandler()
on
$console
, the Console will auto pass the code to RootCommand:
<?php
// cli/console.php
// ...
$console->setHandler(
function($command)
{
$command->out('This is default command.');
return 0; // Return exit code.
}
);
$console->execute();
This code will do same action:
<?php
// cli/console.php
// ...
$console->getRootCommand()
->setHandler(
function($command)
{
$command->out('This is default command.');
return 0; // Return exit code.
}
);
$console->execute();
Retype $ php cli/console.php
and output:
This is default command.
If we want to get help again, just type:
$ cli/console.php help
# OR
$ cli/console.php --help
Note: Command only return integer between 0 and 255,
0
means success, while others means failure or other status. The exit code of Unix/Linux meaning please see: Exit Codes Meanings
Add Help Message to Console
Console includes some help message like: name
, version
, description
, usage
and help
.
If we add this messages to Console:
// cli/console.php
// ...
$console = new \Windwalker\Console\Console;
$console->setName('Example Console')
->setVersion('1.2.3')
->setUsage('console.php <commands> <arguments> [-h|--help] [-q|--quiet]')
->setDescription('Hello World')
->setHelp(
<<<HELP
Hello, this is an example console, if you want to do something, see above:
$ foo bar -h => foo bar --help
---------
$ foo bar yoo -q => foo bar yoo --quiet
HELP
);
// ...
The help will show:
Add First Level Command to Console
Now, we just use the default root command. But there are no first level command are available to call except HelpCommand
.
We can add a new command by this code:
<?php
// cli/console.php
$console->register('foo')
->setDescription('This is first level foo command.')
->setUsage('foo command [--option]')
->setHelp('foo help')
->setHandler(
function($command)
{
$command->out('This is Foo Command executing code.');
}
);
Then we type:
$ cli/console.php foo
We will get:
This is Foo Command executing code.
If we type help:
$ cli/console.php -h
The foo command description has auto added to default command arguments list.
Declaring Command Class
We can create our own command object instead setting it in runtime.
This is an example FooCommand declaration:
<?php
// src/Myapp/Command/FooCommand.php
namespace Myapp\Command;
use Windwalker\Console\Command\Command;
class FooCommand extends Command
{
protected $name = 'foo';
protected $usage = 'foo command [--option]';
protected $help = 'foo help';
protected $description = 'This is first level foo command.';
public function initialise()
{
// We can also set help message in initialise method
$this->setDescription('This is first level foo command.')
->setUsage('foo command [--option]')
->setHelp('foo help');
}
public function doExecute()
{
$this->out('This is Foo Command executing.');
}
}
Then we register it in Console:
<?php
// cli/console.php
$console->addCommand(new FooCommand);
Get Arguments and Options
We can use this code to get arguments and options, setting them in FooCommand
.
// src/Myapp/Command/FooCommand.php
public function initialise()
{
// Define options first that we can set option aliases.
$this->addOption(array('y', 'yell')) // First element `y` will be option name, others will be alias
->alias('Y') // Add a new alias
->defaultValue(0)
->description('Yell will make output upper case.');
// Global options will pass to every child.
$this->addGlobalOption('s')
->defaultValue(0)
->description('Yell will make output upper case.');
}
public function doExecute()
{
$name = #this->getArgument(0);
if (!$name)
{
$this->io->in('Please enter a name: ');
}
$reply = 'Hello ' . $name;
if ($this->getOption('y'))
{
$reply = strtoupper($reply);
}
if ($this->getOption('q'))
{
$reply = strtolower($reply);
}
$this->out($reply);
}
If we type:
$ php cli/console.php foo Asika --yell
# OR
$ php cli/console.php foo Asika -y
The getOption()
method will auto detect option aliases, then we can get:
HELLO: ASIKA
Note: We have to use
addOption()
to define options first, then the$this->getOption('x')
will be able to get the input option which we want. If we didn't do this, we have to use$this->io->get('x')
to get option value, but this way do not support option aliases.
Add Second Level Commands and more...
Now, FooCommand is the first level commands in our command tree, if we want to add several commands under FooCommand,
we can use addCommand()
method. Now we add two bar
and yoo
command under FooCommand
.
Adding command in runtime.
We can use addCommand()
to add a command as other commands' child.
If a command has one or more children, the arguments means to call children which the name equals to this argument.
If a command has no child, Command object will run handler closure if has set, or run doExecute()
if handler not set.
Then the remaining arguments will be able to get by $this->getArgument({offset})
.
<?php
// src/Myapp/Command/FooCommand.php
use Windwalker\Console\Option\Option;
//...
public function initialise()
{
$this->addCommand('bar')
->description('Bar description.');
$this->addCommand('yoo')
->description('Yoo description.')
->addOption(new Option(array('y', 'yell'), 0))
->addGlobalOption(new Option('s', 0, 'desc'));
}
Adding command by classes
We declare BarCommand
and YooCommand
class first.
<?php
// src/Myapp/Command/Foo/BarCommand.php
namespace Myapp\Command\Foo;
use Windwalker\Console\Command\Command;
class BarCommand extends Command
{
protected $name = 'bar';
protected $usage = 'bar command [--option]';
protected $help = 'bar help';
protected $description = 'This is second level bar command.';
public function initialise()
{
$this->addOption(new Option(array('y', 'yell'), 0))
->addGlobalOption(new Option('s', 0, 'desc'));
}
public function doExecute()
{
$this->out('This is Bar Command executing.');
$arg1 = $this->getArgument(0);
if ($arg1)
{
$this->out('Argument1: ' . $arg1);
}
}
}
Then register them to FooCommand
:
<?php
// src/Myapp/Command/FooCommand.php
use Myapp\Command\Foo\BarCommand;
use Myapp\Command\Foo\YooCommand;
//...
public function initialise()
{
$this->addCommand(new BarCommand)
->addCommand(new YooCommand);
}
OK, let's typing:
$ cli/console.php foo bar
We get:
This is Bar Command executing code.
And typing
$ cli/console.php foo bar sakura
get:
This is Bar Command executing code.
Argument1: sakura
Get Child by Path
$command = $console->getCommand('foo/bar'); // BarCommand
// OR
$command = $command->getChild('foo/bar/baz');
The Prompter
Prompter is a set of dialog tools help us asking questions for user.
$prompter = new \Windwalker\Console\Prompter\TextPrompter;
$name = $prompter->ask('Tell me your name:', 'default');
OR set question in constructor.
$prompter = new TextPrompter('Tell me your name: ', $this->io);
// If argument not exists, auto ask user.
$name = $this->getArgument(0, $prompter);
Validate Input Value
$prompter = new \Windwalker\Console\Prompter\ValidatePrompter;
$prompter->setAttempt(3);
$prompter->ask('Please enter username: ');
If we didn't type anything, ValidatePrompter will try ask us three times (We set this number by setAttempt()
).
Please enter username:
Not a valid value.
Please enter username:
Not a valid value.
Please enter username:
Not a valid value.
We can set closure to validate our rule:
$prompter->setAttempt(3)
->setNoValidMessage('No valid number.')
->setHandler(
function($value)
{
return $value == 9;
}
);
$prompter->ask('Please enter right number: ');
Result
Please enter right number: 1
No valid number.
Please enter right number: 2
No valid number.
Please enter right number: 3
No valid number.
If validate fail, we can choose shut down our process:
// ...
$prompter->failToClose(true, 'Number validate fail and close');
$prompter->ask('Please enter right number: ');
Result
Please enter right number:
No valid number.
Please enter right number:
No valid number.
Please enter right number:
No valid number.
Number validate fail and close
Select List
$options = array(
's' => 'sakura',
'r' => 'Rose',
'o' => 'Olive'
);
$prompter = new \Windwalker\Console\Prompter\SelectPrompter('Which do you want: ', $options);
$result = $prompter->ask();
$command->out('You choose: ' . $result);
Output
[s] - sakura
[r] - Rose
[o] - Olive
Which do you want: r
You choose: r
Boolean Prompter
BooleanPrompter convert input string to boolean type, the (y, yes, 1) weill be true
, (n, no, 0, null) will be false
.
$prompter = new \Windwalker\Console\Prompter\BooleanPrompter;
$result = $prompter->ask('Do you wan to do this [Y/n]: ');
var_dump($result);
Result
Do you wan to do this [Y/n]: y
bool(true)
Available Prompters
- TextPrompter
- SelectPrompter
- CallbackPrompter
- ValidatePrompter
- NotNullPrompter
- PasswordPrompter
Available Prompters
HelpCommand
HelpCommand
will auto generate help list for us.
When we use addCommand()
, addOption()
and set some description or other information to these objects, they will save all information in it. Then when we type $ cli/console.php help somethine
or $ cli/console.php somethine --help
, The HelpCommand will return the help message to us.
Every command has these information, you can use setter and getter to access them:
Name
(Command name. The name of RootCommand is file name.)Description
(Command description, will show after title in help output.)Usage
(Will show in help output of current command.)Help
(Will show in the help output bottom as a manual of current command)
The Console information:
Name
(Name of this application, will show as title in help output.)Description
(RootCommand description.)Usage
(RootCommand usage.)Help
(RootCommand help)
Use Your Own Descriptor
If you want to override the Descriptor
for your apps, you can do this:
<?php
use Myapp\Command\Descriptor\XmlDescriptorHelper;
use Myapp\Command\Descriptor\XmlCommandDescriptor;
use Myapp\Command\Descriptor\XmlOptionDescriptor;
// ...
$descriptor = new new XmlDescriptorHelper(
new XmlCommandDescriptor,
new XmlOptionDescriptor
);
$console->getRootCommand()
->getChild('help')
->setDescriptor($descriptor);
// ...
Use Command Without Console
We can using Command
without, please see Command README.
Credits
Windwalker Console incorporated many ideas from other CLI packages. Below is a short list of projects which Windwalker drew inspiration.