Overview
Description
Caldera Console is an abstraction layer for CLI applications.
As with the other Caldera components it has been built to be swappable and modular.
Installation
The easisest way to install it is to use Composer:
composer require vecode/caldera-console
Requires
php: >=8.1
ext-mbstring: *
psr/container: ^2.0
Basic usage
At its core, the console package consist in a series of helpers to make it easier to build CLI applications, by enabling you to interact with the users in a streamlined way.
There are three core components, the Console
class and two traits that give I/O funcionality to your classes: HasInput
and HasOutput
.
Let's discover first the two traits.
HasOutput
With the HasOutput
trait you can add some helpers for generating output on the console, primarily aimed to display messages to the user; just add the trait to your classes:
use Caldera\Console\HasOutput;
class MyConsoleApp {
use HasOutput;
}
The most basic method is blank
, that outputs a blank line:
$app = new MyConsoleApp();
$app->blank();
However, what's the joy in showing blank lines? Enter the line
method, that shows any message and ensures that it ends in a new line:
$app->line('Hey! This is a line!');
Combined with blank
you can create space around your lines:
$app->blank()->line('Hey! This is a line!');
Note that those methods are chainable. But it get's better, as there are four additional methods that allow you to print colored text! (if your terminal supports it):
$app->info('Showing some info for you');
$app->success('That thing is now done!');
$app->warning('Are you sure you want to continue?');
$app->error('Oops! an error ocurred');
Modern terminals allow some colors and Caldera Console includes a colorization helper called, colorize
:
$app->line( $app->colorize('Whoa, I am some purple text!', Color::fgPurple) );
As you can see, the colorize
method takes an string and a color (or two, one for the text and other for its background), that uses the Color
enum, which defines a set of text and background colors. Please only use colors that start with fg
for the text and bg
for the background or you will get an error:
$app->line( $app->colorize('Whoa, I am some purple text!', Color::bgRed, Color::fgYellow) ); # Throws an exception
All of the above methods allow you to use placeholders in your strings, for example:
$app->error('An error ocurred: {error}', ['error' => $e->getMessage()]);
If you don't pass a value for a given placeholder, you will get an error:
$app->error('An error ocurred: {error}'); # Throws an exception
Finally, you can show progress bars easily with a single call to the progressBar
method:
$app->progressBar(65); # Show a 65% bar
If you call the progressBar
again with another value without showing more output it will update the current value:
$app->progressBar(65); # Show a 65% bar
# <do some processing>
$app->progressBar(75); # Update the bar to show a 75% progress
HasInput
If you want to also receive input from the user, add the HasInput
trait too:
use Caldera\Console\HasInput;
use Caldera\Console\HasOutput;
class MyConsoleApp {
use HasInput, HasOutput;
}
Note that HasInput
requires the HasOutput
trait as it may need to show messages as a result of some actions.
The most common use case is reading a line of text, and for that we have the getString
method:
$name = $app->getString('Hello, enter your name:');
It will show a prompt and return the entered text. If you are prompting for passwords it would be a good idea to hide the entered text, and for that you just have to specify an extra paameter:
$password = $app->getString('Now please enter your password:', true);
Another thing usually done is confirming actions, and for that you just need a key, not an entire string. The getKey
method does that:
$key = $app->getKey(['y', 'n'], 'You are about to delete your data, continue (y/n):');
This is specially useful because you pass an array of valid characters, and the function will keep prompting for an answer until the user presses a valid key.
The Console class
Those traits supercharge your classes should you need to interact with the console. But there is also a powerful class that leverages your interaction possibilities: enter the Console
class.
As you may have guessed, it already has both the HasInput
and HasOutput
traits, but its special power is that it allows you to process the input arguments and call specialized classes that handle particular prompts.
For example, you can code your app and ask there for input, show some output and call it a day. But if the logic starts to get beefy a single monolithic class just won't do.
So for complex apps, it is better to isolate the procedures into self-contained classes that we call Commands
. And the best part is that you can define custom prompts for each one, allowing a series of arguments that may be optional or required.
So let's go at it.
Creating commands
Let's define a simple command here:
use Caldera\Console\Command\AbstractCommand;
use Caldera\Console\HasInput;
use Caldera\Console\HasOutput;
class GreetCommand extends AbstractCommand {
use HasInput, HasOutput;
protected string $signature = 'greet';
protected string $arguments = '{name?}';
public function handle() {
$name = $this->argument('name');
if (! $name ) {
$name = $this->getString('Please tell me your name:');
}
$this->info("Hey {name}, what's up?", ['name' => $name]);
}
}
This simple command greets the user by its name, to invoke would do something like this:
php myapp greet John
First you define a $signature
, that is the name of the command. In this case is greet
, so that you just run your app with a greet
argument to identify the command that you want to run.
Also note that there is something called $arguments
: this defines which arguments your command accepts, its type and related constraints. We added a single one, called name
, but you need to use curly-braces so it will be {name}
. There is also a question-mark there {name?}
and that makes the argument optional.
Then you need a handle
method, that will be called when the command is invoked.
In this example, we try to get name
from the arguments, but as it is optional, it may be blank, so we use the getString
method to prompt the user for its name if not provided. Then we simply greet the user using the info
method.
Your commands must implement the CommandInterface
interface, the easiest way to do it is by extending AbstractCommand
.
Arguments
Arguments are described by using curly-braces to delimit items, for example:
protected string $arguments = '{first_name} {last_name?}';
This will accept two parameters, one required (first_name
) and the other optional (last_name
).
You can also define default values by using the =
symbol:
protected string $arguments = '{first_name} {last_name?} {prefix=Mr?}';
That will have three arguments, with prefix
being optional and with a default value of Mr
.
Also you can have arrays of arguments, for example:
protected string $arguments = '{name*}';
That way you will be able to accept multiple items:
php myapp greet John Mary Peter
public function handle() {
$name = $this->argument('name');
$this->info("Hey {name}, what's up?", ['name' => implode(', ', $name)]);
}
Please keep in mind that array arguments should always be the last ones and naturally you can have only one. Also, arguments have an order.
Options
Options are another kind of arguments, they usually are optional and can be boolean or have an specific value, for example:
protected string $signature = 'server:listen';
protected string $arguments = '{action} {--v|verbose} {--h|host=localhost?} {--h|port=8080?} {--d|domain*}';
This command accepts up to five arguments:
action
- A textual argumentverbose
- A boolean option (true if present, false otherwise)host
- An option with a value, with a default value oflocalhost
port
- An option with a value, with a default value of8080
domain
- An array option
You can call it this way:
php myapp server:listen run --verbose --host=192.168.1.110 --port=8888 --domain=example.org --domain=example.net
Or this way:
php myapp server:listen run -v -h 192.168.1.110 -p 8888 -d example.org -d example.net
Note how you can define option arguments with a shortcut, also note that shortcuts require just a single dash in front of them.
In fact, you can use full options with a =
or a space: --domain=example.org
and --domain example.org
are equivalent.
And shortcuts with an space, or nothing: -p 8080
and -d8080
are the same.
Registering commands
Back to the Console
class, to register your commands there are two ways: one by one or in bulk.
For a single command just call the command
method:
use MyApp\Commands\GreetCommand;
$console = new Console();
$console->command(GreetCommand::class);
For adding more than one command you can just add a directory with the path
method and let the Console
class autodiscover all the classes when needed:
$console->path('<path-to-commands-dir>/Commands');
Once the paths are added they will be inspected to autoload the available commands.
Calling commands
To call a command use the call
method:
$console->call('greet', ['John']);
It accepts two parameters, the command name and an array of arguments:
$console->call('server:listen', ['run', '-v', '-h 192.168.1.110', '--domain=example.org']);
This way you can easily wire your Console
instance to directly accept CLI arguments:
use Caldera\Console\Console;
# Remove the app command
array_shift($argv);
# Get the command name, if any
$command = array_shift($argv);
# Create a Console instance and call the command
$console = new Console();
$console->call($command, $argv);
Advanced usage
Resolving command classes
By default, the Console
class will try to instantiate the registered CommandInterface
implementation for a given command signature.
But if you are using a ContainerInterface
implementation that deals with autowiring, it is better to offload that job to it; so if you are sure that your Container
can create scoped classes AND autowire them you may want to pass it to the Console
constructor:
$console = new Console($container); # $container is the ContainerInterface implementation instance
By the way, the Caldera Container supports creation of scoped classes and autowiring.