Lightweight console library 03/12/2014
Deprecated: This article has been deprecated.
TL;DR: Konzolo can be used to create minimalistic CLI applications, or to implement the command design pattern.
After implementing a same feature in many projects, I usually have the reflex to create a library out of it. Konzolo is one of them :) .
In this article we'll see its features:
Create a command
Let's create a "hello world" command:
<?php
namespace Acme\Demo\Command;
use Gnugat\Konzolo\Command;
use Gnugat\Konzolo\Input;
class HelloWorldCommand implements Command
{
public function execute(Input $input)
{
$name = $input->getArgument('name');
echo "Hello $name\n";
return Command::EXIT_SUCCESS;
}
}
Note: If the name argument is missing, an exception will be thrown. Keep reading to know more about those exceptions.
We only have to implement the execute
method, which receives a convenient
Input
class and returns 0 on success (actually this is optional).
Binding up an application
Now that we have a command, let's put it in an application:
<?php
// File: console.php
use Acme\Demo\Command\HelloWorldCommand;
use Gnugat\Konzolo\Application;
use Gnugat\Konzolo\Input;
require __DIR__.'/vendor/autoload.php';
$input = new Input($argv[1]); // command name (acme:hello-world)
if (isset($argv[2])) {
$input->setArgument('name', $argv[2]);
}
$application = new Application();
$application->addCommand('acme:hello-world', new HelloWorldCommand());
$application->run($input);
You can then run it:
php console.php acme:hello-world Igor
Input constraint
If you need to validate the input, you can create a constraint:
<?php
namespace Acme\Demo\Validation;
use Gnugat\Konzolo\Exception\InvalidInputException;
use Gnugat\Konzolo\Input;
use Gnugat\Konzolo\Validation\InputConstraint;
class NoWorldNameConstraint implements InputConstraint
{
public function throwIfInvalid(Input $input)
{
$name = $input->getArgument('name');
if ('World' === $name) {
throw new InvalidInputException($input, 'The "name" parameter must not be set to "World"');
}
}
}
This constraint can be used directly in the command, as a dependency:
<?php
namespace Acme\Demo\Command;
use Acme\Demo\Validation\NoWorldNameConstraint;
use Gnugat\Konzolo\Command;
use Gnugat\Konzolo\Input;
class HelloWorldCommand implements Command
{
private $noWorldNameConstraint;
public function __construct(NoWorldNameConstraint $noWorldNameConstraint)
{
$this->noWorldNameConstraint = $noWorldNameConstraint;
}
public function execute(Input $input)
{
$this->noWorldNameConstraint->throwIfInvalid($input);
$name = $input->getArgument('name');
echo "Hello $name\n";
return Command::EXIT_SUCCESS;
}
}
And then inject it:
<?php
// File: console.php
use Acme\Demo\Command\HelloWorldCommand;
use Acme\Demo\Validation\NoWorldNameConstraint;
use Gnugat\Konzolo\Application;
use Gnugat\Konzolo\Input;
require __DIR__.'/vendor/autoload.php';
$input = new Input($argv[1]); // command name (acme:hello-world)
if (isset($argv[2])) {
$input->setArgument('name', $argv[2]);
}
$application = new Application();
$application->addCommand('acme:hello-world', new HelloWorldCommand(new NoWorldNameConstraint()));
$application->run($input);
Input validator
More conveniently, the command can depend on a validator:
<?php
namespace Acme\Demo\Command;
use Gnugat\Konzolo\Command;
use Gnugat\Konzolo\Input;
use Gnugat\Konzolo\Validation\InputValidator;
class HelloWorldCommand implements Command
{
private $validator;
public function __construct(InputValidator $validator)
{
$this->validator = $validator;
}
public function execute(Input $input)
{
$this->validator->throwIfInvalid($input);
$name = $input->getArgument('name');
echo "Hello $name\n";
return Command::EXIT_SUCCESS;
}
}
You can add many constraint in a validator, and set priorities:
<?php
// File: console.php
use Acme\Demo\Command\HelloWorldCommand;
use Acme\Demo\Validation\NoWorldNameConstraint;
use Gnugat\Konzolo\Application;
use Gnugat\Konzolo\Input;
use Gnugat\Konzolo\Validation\InputValidator;
require __DIR__.'/vendor/autoload.php';
$input = new Input($argv[1]); // command name (acme:hello-world)
if (isset($argv[2])) {
$input->setArgument('name', $argv[2]);
}
$helloWorldValidator = new InputValidator();
$helloWorldValidator->addConstraint(new NoWorldNameConstraint(), 42);
$application = new Application();
$application->addCommand('acme:hello-world', new HelloWorldCommand($helloWorldValidator));
$application->run($input);
Note: The highest the priority, the soonest the constraint will be executed. For example, a constraint with priority 1337 will be executed before another one with priority 23 (even if this second one has been added first in the validator).
Exceptions
Konzolo's exceptions all implement the Gnugat\Konzolo\Exception\Exception
interface.
This means you can catch every single one of them using this type. They also
extend at the standard \Exception
class, so if you don't care about Konzolo
specific exceptions, you can catch them all!
This is usefull for example in Symfony2: you can create a Konzolo exception listener.
You can find more about the different kind of exceptions and their specific methods in its dedicated documentation.
Conclusion
We have seen how to create commands and validate their inputs.
Our examples showed how to create a CLI application, but Konzolo is mainly aimed at being used in applications (not only CLI ones). For example, Redaktilo uses internally a system of Command/CommandInvoker, using an array as input and sanitizer as a validation mechanism. All this logic can now be externalized, thanks to Konzolo!
I'd like to keep Konzolo as small as possible, but here's a list of possible features it could see in the future:
Command finder
Currently we can find commands by their exact names. But wouldn't it be nice if we could just provide part of a name? Or an alias?
Input Factories
Creating input manually isn't always what we need. A factory that creates one from an array could improve the situation.