Console
Basics
Console applications are great for administrative tasks and code generation. With Aphiria, you can easily create your own console commands, display question prompts, and use HTML-like syntax for output styling.
If you're already using the skeleton app, you can skip to the next section. Otherwise, let's create a file called aphiria in your project's root directory and paste the following code into it:
#!/usr/bin/env php
<?php
use Aphiria\Console\Commands\CommandRegistry;
use Aphiria\Console\ConsoleGateway;
use Aphiria\Console\Input\Compilers\InputCompiler;
use Aphiria\Console\StatusCode;
use Aphiria\DependencyInjection\Container;
use Aphiria\Framework\Console\ConsoleApplication;
require __DIR__ . '/vendor/autoload.php';
$commands = new CommandRegistry();
// Register your commands here...
$gateway = new ConsoleGateway($commands, new Container());
$input = new InputCompiler($commands)->compile($_SERVER['argv'] ?? []);
$app = new ConsoleApplication($gateway, $input);
$status = $app->run();
exit(\is_int($status) ? $status : $status instance StatusCode ? $status->value : StatusCode::Ok->value);
Now, you're set to start running commands.
Why Is This Library Included?
At first glance, including a console library in an API framework might seem weird. However, there are some tasks, such as clearing framework caches, that are most easily accomplished with console commands. We decided not to use another console library because we felt we could provide a better developer experience than most, eg by providing attribute support and a great fluent syntax for configuring commands.
Running Commands
To run commands, type php aphiria COMMAND_NAME
into a terminal from the directory that Aphiria is installed in.
Getting Help
To get help with any command, use the help command:
php aphiria help COMMAND_NAME
Creating Commands
In Aphiria, a command defines the name, arguments, and options that make up a command. Each command has a command handler with method handle()
, which is what actually processes a command. Command handlers can return a StatusCode
enum, an integer (useful for custom status codes), or nothing, which implies a successful status code.
Let's take a look at an example:
use Aphiria\Console\Commands\Attributes\{Argument, Command, Option};
use Aphiria\Console\Commands\ICommandHandler;
use Aphiria\Console\Input\{ArgumentType, Input, OptionType};
use Aphiria\Console\Output\IOutput;
#[
Command('greet', description: 'Greets a person'),
Argument('name', type: ArgumentType::Required, description: 'The name to greet'),
Option('yell', type: OptionType::OptionalValue, shortName: 'y', description: 'Yell the greeting?', defaultValue: 'yes')
]
final class GreetingCommandHandler implements ICommandHandler
{
public function handle(Input $input, IOutput $output)
{
$greeting = "Hello, {$input->arguments['name']}";
if ($input->options['yell'] === 'yes') {
$greeting = \strtoupper($greeting);
}
$output->writeln($greeting);
}
}
Input
The following properties are available to you in Input
:
$input->commandName; // The name of the command that was invoked
$input->arguments['argName']; // The value of "argName"
$input->options['optionName']; // The value of "optionName"
If you're checking to see if an option that does not have a value is set, use array_key_exists('optionName', $input->options)
- the value will be null
, and isset()
will return false
.
Note:
$input->options
stores option values by their long names. Do not try to access them by their short names.
Arguments
Console commands can accept arguments from the user. Arguments can be required, optional, and/or arrays. Array arguments allow a variable number of arguments to be passed in, like php aphiria foo arg1 arg2 arg3 ...
. The only catch is that array arguments must be the last argument defined for the command. If you need to specify that an argument can be multiple types, eg required and an array, just pass in an array of types.
use Aphiria\Console\Input\Argument;
use Aphiria\Console\Input\ArgumentType;
// The argument will be required and an array
$type = [ArgumentType::Required, ArgumentType::IsArray];
// The description argument is used by the help command
$argument = new Argument('foo', $type, 'The foo argument');
Note: Like array arguments, optional arguments must appear after any required arguments.
Options
You might want different behavior in your command depending on whether or not an option is set. This is possible using Option
. Options have two formats:
Short Names
Short option names are always a single letter. Multiple short options can be grouped together. For example, -rf
means that options with short codes "r" and "f" have been specified. The default value will be used for short options.
Long Names
Long option names can specify values in two ways: --foo=bar
or --foo bar
. If you only specify --foo
for an optional-value option, then the default value will be used.
Array Options
Options can be arrays, eg --foo=bar --foo=baz
will set the "foo" option to ["bar", "baz"]
.
Like arguments, multiple option types can be specified with an array of types.
use Aphiria\Console\Input\Option;
use Aphiria\Console\Input\OptionType;
$type = [OptionType::IsArray, OptionType::RequiredValue];
$option = new Option('foo', $type, 'f', 'The foo option');
Output
Outputs allow you to write messages to an end user. The different outputs include:
Aphiria\Console\Output\ConsoleOutput
- Writes output to the console, and is the default output
Aphiria\Console\Output\SilentOutput
- Used when we don't want any messages to be written
Each output offers a few methods:
readLine()
- Reads a line of input
write()
- Writes a message to the existing line
writeln()
- Writes a message to a new line
clear()
- Clears the current screen
- Only works in
ConsoleOutput
Manually Registering Commands
If you're using the skeleton app, this is already handled for you, and you can skip this section. Otherwise, you'll have to register commands so that your application knows about them. If you're using attributes, read this section to learn how to manually register attribute commands. If you're using the application builder library, refer to its documentation to learn how to manually register your commands to your app.
Let's manually register a command to the application:
use Aphiria\Console\Commands\Command;
use Aphiria\Console\Commands\CommandRegistry;
$commands = new CommandRegistry();
$greetingCommand = new Command('greet', arguments: [/* ... */], options: [/* ... */]);
$commands->registerCommand($greetingCommand, GreetingCommandHandler::class);
// Run the application (see above how to do this)...
To call this command, run this from the command line:
php aphiria greet Dave -y
This will output:
HELLO, DAVE
Note: Command handlers will only be resolved when they're called, which is especially useful when your handler is a class with expensive-to-instantiate dependencies, such as database connections.
Calling From Code
It's possible to call a command from another command by injecting ICommandHandler
into your command handler, which will be bound to an instance of ConsoleGateway
. Here, we'll call the bar
command with an argument and option:
use Aphiria\Console\Commands\Attributes\Command;
use Aphiria\Console\Commands\ICommandHandler;
use Aphiria\Console\Input\Input;
use Aphiria\Console\Output\IOutput;
#[Command('foo')]
final class FooCommandHandler implements ICommandHandler
{
public function __construct(private ICommandHandler $gateway) {}
public function handle(Input $input, IOutput $output)
{
$this->gateway->handle(new Input('bar', ['arg1' => null], ['option1' => 'value']), $output);
}
}
If you want to call the other command but not write its output, use the SilentOutput
output.
Note: If a command is being called by a lot of other commands, it might be best to refactor its actions into a separate class. This way, it can be used by multiple commands without the extra overhead of calling console commands through PHP code.
Command Attributes
It's convenient to define your command alongside your command handler so you don't have to jump back and forth remembering what arguments or options your command takes. Aphiria offers the option to do so via attributes.
Command Attribute Example
Let's look at an example that duplicates the greeting example from above:
use Aphiria\Console\Commands\Attributes\{Argument, Command, Option};
use Aphiria\Console\Input\{ArgumentType, OptionType};
#[
Command('greet', 'Greets a person'),
Argument('name', ArgumentType::Required, 'The name to greet'),
Option('yell', OptionType::OptionalValue, 'y', 'Yell the greeting', 'yes')
]
final class GreetingCommandHandler implements ICommandHandler
{
public function handle(Input $input, IOutput $output)
{
// ...
}
}
Scanning For Attributes
Before you can use attributes, you'll need to configure Aphiria to scan for them. If you're using the skeleton app, you can do so in GlobalModule
:
use Aphiria\Application\IApplicationBuilder;
use Aphiria\Framework\Application\AphiriaModule;
final class GlobalModule extends AphiriaModule
{
public function configure(IApplicationBuilder $appBuilder): void
{
$this->withCommandAttributes($appBuilder);
}
}
Otherwise, you can manually configure your app to scan for attributes:
use Aphiria\Console\Commands\Attributes\AttributeCommandRegistrant;
use Aphiria\Console\Commands\CommandRegistry;
// Assume we already have $container set up
$commands = new CommandRegistry();
$attributeCommandRegistrant = new AttributeCommandRegistrant(['PATH_TO_SCAN'], $container);
$attributeCommandRegistrant->registerCommands($commands);
Prompts
Prompts are great for asking users for input beyond what is accepted by arguments. For example, you might want to confirm with a user before doing an administrative task, or you might ask her to select from a list of possible choices.
Confirmation
To ask a user to confirm an action with a simple "y" or "yes", use a confirmation prompt.
use Aphiria\Console\Output\Prompts\Confirmation;
use Aphiria\Console\Output\Prompts\Prompt;
$prompt = new Prompt();
// This will return true if the answer began with "y" or "Y"
$prompt->ask(new Confirmation('Are you sure you want to continue?'), $output);
Multiple Choice
Multiple choice questions are great for listing choices that might otherwise be difficult for a user to remember.
use Aphiria\Console\Output\Prompts\MultipleChoice;
$choices = ['Boeing 747', 'Boeing 757', 'Boeing 787'];
$question = new MultipleChoice('Select your favorite airplane', $choices);
$prompt->ask($question, $output);
This will display:
Select your favorite airplane
1) Boeing 747
2) Boeing 757
3) Boeing 787
>
If the $choices
array is associative, then the keys will map to values rather than 1)...N).
Hiding Input
For security reasons, such as when entering a password, you might want to hide a user's input as they're typing it. To do so, just mark a question as hidden:
$question = new Question('Password', isHidden: true);
$prompt->ask($question, $output);
Formatters
Formatting your output helps make it more readable. Aphiria provides a few common formatters out of the box.
Padding
The PaddingFormatter
formatter allows you to create column-like output. It accepts an array of column values. The second parameter is a callback that will format each row's contents. Let's look at an example:
use Aphiria\Console\Output\Formatters\PaddingFormatter;
$paddingFormatter = new PaddingFormatter();
$rows = [
['George', 'Carlin', 'great'],
['Chris', 'Rock', 'good'],
['Jim', 'Gaffigan', 'pale']
];
$paddingFormatter->format($rows, fn ($row) => $row[0] . ' - ' . $row[1] . ' - ' . $row[2]);
This will return:
George - Carlin - great
Chris - Rock - good
Jim - Gaffigan - pale
You can pass in an options parameter to customize the output of the padding formatter:
use Aphiria\Console\Output\Formatters\PaddingFormatterOptions;
$options = new PaddingFormatterOptions(
paddingString: ' ',
padAfter: true,
eolChar: "\n"
);
$paddingFormatter->format($rows, fn ($row) => $row[0] . ' - ' . $row[1] . ' - ' . $row[2], $options);
Note: You can set a default set of options for
PaddingFormatter
andTableFormatter
in their constructors if you do not want to pass in options on every call toformat()
.
Tables
ASCII tables are a great way to show tabular data in a console. To create a table, use TableFormatter
:
use Aphiria\Console\Output\Formatters\TableFormatter;
$table = new TableFormatter();
$rows = [
['Sean', 'Connery'],
['Pierce', 'Brosnan']
];
$table->format($rows);
This will return:
+--------+---------+
| Sean | Connery |
| Pierce | Brosnan |
+--------+---------+
Headers can also be included in tables:
$headers = ['First', 'Last'];
$table->format($rows, $headers);
This will return:
+--------+---------+
| First | Last |
+--------+---------+
| Sean | Connery |
| Pierce | Brosnan |
+--------+---------+
Like PaddingFormatter
, you can specify some options to customize the look of tables:
use Aphiria\Console\Output\Formatters\TableFormatterOptions;
$options = new TableFormatterOptions(
cellPaddingString: ' ',
horizontalBorderChar: '-',
verticalBorderChar: '|',
intersectionChar: '+',
padAfter: true,
eolChar: "\n"
);
$table->format($rows, $headers, $options);
Progress Bars
Progress bars help visually indicate to a user the progress of a long-running task, like this one:
[=================50%--------------------] 50/100
Time remaining: 15 secs
Creating one is simple - you just create a ProgressBar
, and specify the formatter to use:
use Aphiria\Console\Output\Formatters\{ProgressBar, ProgressBarFormatter};
// Assume our output is already created
$formatter = new ProgressBarFormatter($output);
// Set the maximum number of "steps" in the task to 100
$progressBar = new ProgressBar(100, $formatter);
You can advance your progress bar:
$progressBar->advance();
// Or with a custom step
$progressBar->advance(2);
Alternatively, you can set a specific progress:
$progressBar->progress = 50;
To explicitly complete the progress bar, call
$progressBar->complete();
Each time progress is made, the formatter will be update.
Customizing Progress Bars
You may customize the look of your progress bar with some options:
use Aphiria\Console\Output\Formatters\ProgressBarFormatterOptions;
$options = new ProgressBarFormatterOptions(
progressBarWidth: 100,
outputFormat: '%bar% - Time remaining: %timeRemaining%',
completedProgressChar: '=',
remainingProgressChar: '-',
redrawFrequency: 1
);
$progressBar = new ProgressBar(100, $formatter, $options);
If you'd like to customize the format of the progress bar text, you may by specifying sprintf()
-encoded text. The following placeholders are built in for you to use in the $outputFormat
parameter of ProgressBarFormatterOptions
:
%progress%
- The current progress%maxSteps%
- The max number of steps%bar%
- The actual progress bar that's drawn%timeRemaining%
- The amount of time remaining%percent%
- The current progress as a percentage
Style Elements
Aphiria supports HTML-like style elements to perform basic output formatting like background color, foreground color, boldening, and underlining. For example, writing:
<b>Hello!</b>
...will output "Hello!". You can even nest elements:
<u>Hello, <b>Dave</b></u>
..., which will output an underlined string where "Dave" is both bold AND underlined.
Built-In Elements
The following elements come built-into Aphiria:
- <success></success>
- <info></info>
- <question></question>
- <comment></comment>
- <error></error>
- <fatal></fatal>
- <b></b>
- <u></u>
Custom Elements
You can create your own style elements. Elements are registered to ElementRegistry
.
use Aphiria\Console\Commands\CommandRegistry;
use Aphiria\Console\Output\Compilers\Elements\{Colors, Element, ElementRegistry, Style, TextStyles};
use Aphiria\Console\Output\Compilers\OutputCompiler;
use Aphiria\Console\Output\ConsoleOutput;
use Aphiria\DependencyInjection\Container;
use Aphiria\Framework\Console\ConsoleApplication;
$commands = new CommandRegistry();
// Register your commands here...
// Register a custom element
$elements = new ElementRegistry();
$elements->registerElement(
new Element('foo', new Style(Colors::BLACK, Colors::YELLOW, [TextStyles::BOLD])
);
$outputCompiler = new OutputCompiler($elements);
$output = new ConsoleOutput($outputCompiler);
// Now, pass it into the app
$gateway = new ConsoleGateway($commands, new Container());
$input = new InputCompiler($commands)->compile($_SERVER['argv'] ?? []);
$app = new ConsoleApplication($gateway, $input, $output);
$status = $app->run();
exit(\is_int($status) ? $status : $status instance StatusCode ? $status->value : StatusCode::Ok->value);
Overriding Built-In Elements
To override a built-in element, just re-register it:
$elements->registerElement(
new Element('success', new Style(Colors::GREEN, Colors::BLACK))
);
Built-In Commands
Aphiria provides some commands out of the box to make it easier to work with the framework.
Name | Description |
---|---|
app:serve |
Runs your application locally |
framework:flushcaches |
Flushes all the framework's caches, eg the binder metadata, constraints, command, route, and trie caches |
route:list |
Lists all the routes in your application |
If you're using the skeleton app, you can register all framework commands in GlobalModule
:
use Aphiria\Application\IApplicationBuilder;
use Aphiria\Framework\Application\AphiriaModule;
final class GlobalModule extends AphiriaModule
{
public function configure(IApplicationBuilder $appBuilder): void
{
$this->withFrameworkCommands($appBuilder);
}
}
To exclude some built-in commands so that you can override them with your own implementation, eg app:serve
, pass in an array of command names.
$this->withFrameworkCommands($appBuilder, ['app:serve']);