Introduction of Windwalker Commands

The Nested Command Structure

          Console Application (RootCommand)
                         |
              ----------------------
              |                    |
          CommandA              CommandB
              |                    |
        ------------          ------------
        |          |          |          |
    CommandC   CommandD    CommandE   CommandF

If we type:

php cli/console 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
    }
}

Declaring Command Class

This is an example FlowerCommand declaration:

use Windwalker\Console\Command\Command;

class FlowerCommand extends Command
{
    protected $name  = 'flower';
    protected $usage = 'flower command [--option]';
    protected $help  = 'flower help information';
    protected $description = 'This is first level flower command.';

    public function initialise()
    {
        // We can also set help message in initialise method
        $this->description('This is first level flower command.')
            ->usage('flower <command> [--option]')
            ->help('flower help information');
    }

    public function doExecute()
    {
        $this->out('This is Flower Command executing.');
    }
}

Register Command in Console:

We can add our Commands in src/Windwalker/Console/Application.php:

// src/Windwalker/Console/Application.php

public function registerCommands()
{
    parent::registerCommands();

    /*
     * Register Commands
     * --------------------------------------------
     * Register your own commands here, make sure you have call the parent, some important
     * system command has registered at parent::registerCommands().
     */

    // Your commands here.
    $this->addCommand(new FlowerCommand());
}

Then type php windwlaker -h will see our new command:

Commands:

  migration    Database migration system.
  seed         The data seeder help you create fake data.
  build        Some useful tools for building system.
  flower       This is first level flower command.

Auto Registering

If we create commands in package Command folder, every commands will be auto registered to Console:

// src/Flower/Command/FlowerCommand.php

namespace Flower\Command;

use Windwalker\Console\Command\Command;

// This class will auto register to console because it is located in package's Command folder
class FlowerCommand extends Command
{
    // ...
}

Get Arguments and Options

We can use this code to get arguments and options, setting them in FooCommand.

// src/Flower/Command/FlowerCommand.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 windwlaker flower Asika --yell

## OR

$ php windwlaker flower Asika -y

The getOption() method will auto detect option aliases, then we can get:

HELLO: ASIKA

Note: We have to use $this->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...

FlowerCommand is the first level command in our command tree, if we want to add several commands under FlowerCommand, we can use addCommand() method. Now we add two sakura and rose command under FlowerCommand.

Create Command Classes

We declare SakuraCommand and RoseCommand class first.

// src/Flower/Command/Flower/SakuraCommand.php

namespace Flower\Command\Flower;

use Windwalker\Console\Command\Command;

class SakuraCommand extends Command
{
    protected $name = 'sakura';
    protected $usage = 'sakura command [--option]';
    protected $help  = 'sakura help';
    protected $description = 'This is second level sakura 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 Sakura Command executing.');

        $arg1 = $this->getArgument(0);

        if ($arg1) {
            $this->out('Argument1: ' . $arg1);
        }
    }
}

Then register them to FlowerCommand:

<?php
// src/Flower/Command/FlowerCommand.php

use Flower\Command\Flower\SakuraCommand;
use Flower\Command\Flower\RoseCommand;

// ...

public function initialise()
{
    $this->addCommand(new SakuraCommand())
        ->addCommand(new RoseCommand());
}

OK, let's typing:

php windwlaker flower sakura

We will get:

This is Sakura Command executing code.

And typing

$ cli/console.php flower sakura bloom

will get:

This is Sakura Command executing code.
Argument1: bloom

Get Child by Path

$command = $console->getCommand('flower/sakura'); // SakuraCommand

// OR

$command = $command->getChild('foo/bar/baz');

The Prompter

Prompter is a set of dialog tools help us asking questions from 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

If you found a typo or error, please help us improve this document.