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\Application;
use Aphiria\Console\Commands\CommandRegistry;
use Aphiria\DependencyInjection\Container;

require_once __DIR__ . '/vendor/autoload.php';

$commands = new CommandRegistry();

// Register your commands here...

global $argv;
exit((new Application($commands, new Container()))->handle($argv));

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 DX 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.

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\{ArgumentTypes, Input, OptionTypes};
use Aphiria\Console\Output\IOutput;

#[
    Command('greet', description: 'Greets a person'),
    Argument('name', type: ArgumentTypes::REQUIRED, description: 'The name to greet'),
    Option('yell', type: OptionTypes::OPTIONAL_VALUE, 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. You specify the type by bitwise OR-ing the different arguments types.

use Aphiria\Console\Input\Argument;
use Aphiria\Console\Input\ArgumentTypes;

// The argument will be required and an array
$type = ArgumentTypes::REQUIRED | ArgumentTypes::IS_ARRAY;
// 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:

  1. Short, eg "-h"
  2. Long, eg "--help"

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, option types can be specified by bitwise OR-ing types together.

use Aphiria\Console\Input\Option;
use Aphiria\Console\Input\OptionTypes;

$type = OptionTypes::IS_ARRAY | OptionTypes::REQUIRED_VALUE;
$option = new Option('foo', $type, 'f', 'The foo option');

Output

Outputs allow you to write messages to an end user. The different outputs include:

  1. Aphiria\Console\Output\ConsoleOutput
    • Writes output to the console, and is the default output
  2. Aphiria\Console\Output\SilentOutput
    • Used when we don't want any messages to be written

Each output offers a few methods:

  1. readLine()
    • Reads a line of input
  2. write()
    • Writes a message to the existing line
  3. writeln()
    • Writes a message to a new line
  4. 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\Application;
use Aphiria\Console\Commands\Command;
use Aphiria\Console\Commands\CommandRegistry;
use Aphiria\DependencyInjection\Container;

$commands = new CommandRegistry();
$greetingCommand = new Command('greet', arguments: [/* ... */], options: [/* ... */]);
$commands->registerCommand($greetingCommand, GreetingCommandHandler::class);

// Actually run the application
global $argv;
exit((new Application($commands, new Container()))->handle($argv));

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 ICommandBus into your command handler:

use Aphiria\Console\Commands\Attributes\Command;
use Aphiria\Console\Commands\ICommandBus;
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 ICommandBus $commandBus) {}

    public function handle(Input $input, IOutput $output)
    {
        $this->commandBus->handle('foo arg1 --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\{ArgumentTypes, OptionTypes};

 #[
    Command('greet', 'Greets a person'),
    Argument('name', ArgumentTypes::REQUIRED, 'The name to greet'),
    Option('yell', OptionTypes::OPTIONAL_VALUE, '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 App:

use Aphiria\Application\Builders\IApplicationBuilder;
use Aphiria\Application\IModule;
use Aphiria\Framework\Application\AphiriaComponents;

final class App implements IModule
{
    use AphiriaComponents;

    public function build(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

There are a few useful functions for customizing the padding formatter:

// Set the end-of-line character
$paddingFormatter->setEolChar("\n");

// Set whether or not to pad after strings
$paddingFormatter->setPadAfter(true);

// Set the string to use for padding
$paddingFormatter->setPaddingString(' ');

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 |
+--------+---------+

There are a few useful functions for customizing the look of tables:

// Set the string to use to pad cells (defaults to a space)
$table->setCellPaddingString(' ');

// Set the end-of-line character (defaults to LF)
$table->setEolChar("\n");

// Set the horizontal border character
$table->setHorizontalBorderChar('-');

// Set the vertical border character
$table->setVerticalBorderChar('|');

// Set the border intersection character
$table->setIntersectionChar('+');

// Set whether or not to pad after strings
$table->setPadAfter(true);

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->setProgress(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 characters used in your progress bar via:

$formatter->completedProgressChar = '*';
$formatter->remainingProgressChar = '-';

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:

To specify the format, pass it into ProgressBarFormatter:

$formatter = new ProgressBarFormatter(
    $output,
    80,
    '%bar% - Time remaining: %timeRemaining%'
);

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:

Custom Elements

You can create your own style elements. Elements are registered to ElementRegistry.

use Aphiria\Console\Output\Compilers\Elements\{Colors, Element, ElementRegistry, Style, TextStyles};
use Aphiria\Console\Output\Compilers\OutputCompiler;
use Aphiria\Console\Output\ConsoleOutput;

$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 (assume it's already set up)
global $argv;
exit($app->handle($argv, $output));

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

If you're using the skeleton app, you can register all framework commands in App:

use Aphiria\Application\Builders\IApplicationBuilder;
use Aphiria\Application\IModule;
use Aphiria\Framework\Application\AphiriaComponents;

final class App implements IModule
{
    use AphiriaComponents;

    public function build(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']);