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.1ext-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 oflocalhostport- An option with a value, with a default value of8080domain- 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.