Console

Middleware

tempest/console has support for middleware, a well known concept within the context of web applications, which also makes building a lot of console features easier.

Console middleware can be applied both globally (to all console commands), or on a per-command basis. Global console middleware will be discovered automatically, and sorted based on their priority. Tempest comes with a bunch of console middleware out of the box.

Individual middleware can be added on top of this stack by passing it into the #[ConsoleCommand] attribute:

// app/ForceCommand.php

use Tempest\Console\HasConsole;
use Tempest\Console\ConsoleCommand;
use Tempest\Console\Middleware\ForceMiddleware;

final readonly class ForceCommand
{
    use HasConsole;

    #[ConsoleCommand(
        middleware: [ForceMiddleware::class]
    )]
    public function __invoke(): void { /* … */ }
}

Building your own middleware

You can create your own console middleware by implementing the ConsoleMiddleware interface:

// app/HelloWorldMiddleware.php

use Tempest\Console\HasConsole;
use Tempest\Console\ConsoleMiddleware;
use Tempest\Console\ConsoleMiddlewareCallable;

final readonly class HelloWorldMiddleware implements ConsoleMiddleware
{
    use HasConsole;

    public function __invoke(Invocation $invocation, ConsoleMiddlewareCallable $next): ExitCode|int
    {
        if ($invocation->argumentBag->get('hello')) {
            $this->writeln('Hello world!')
        }

        return $next($invocation);
    }
}

Middleware classes will be autowired by the container, so you can use the constructor to inject any dependency you'd like. The Invocation object contains everything you need about the context for the current console command invocation:

  • $invocation->argumentBag contains the argument bag with all the input provided by the user.
  • $invocation->consoleCommand an instance of the #[ConsoleCommand] attribute for the matched console command. This property will be null if you're not using ResolveOrRescueMiddleware or if your middleware runs before it.

Middleware priority

All console middleware classes get sorted based on their priority. By default, each middleware gets the "normal" priority, but you can override it using the #[Priority] attribute:

use Tempest\Core\Priority;

#[Priority(Priority::HIGH)]
final readonly class HelloWorldMiddleware implements ConsoleMiddleware
{ /* … */ }

Note that priority is defined using an integer. You can however use one of the built-in Priority constants: Priority::FRAMEWORK, Priority::HIGHEST, Priority::HIGH, Priority::NORMAL, Priority::LOW, Priority::LOWEST.

Middleware discovery

Global console middleware classes are discovered and sorted based on their priority. You can make a middleware class non-global by adding the #[DoNotDiscover] attribute:

use Tempest\Discovery\DoNotDiscover;

#[DoNotDiscover]
final readonly class HelloWorldMiddleware implements ConsoleMiddleware
{ /* … */ }

Built-in middleware

Let's take a look at the built-in middleware that Tempest provides.

OverviewMiddleware

Renders the console command overview when no specific command is provided.

./tempest

Tempest

General
 install [--force=false] - Interactively install Tempest in your project
 routes - List all registered routes
 serve [host='localhost:8000'] [publicDir='public/'] - Start a PHP development server
 deploy

// …

ResolveOrRescueMiddleware

Shows a list of similar commands when you mistyped a command:

./tempest migrate

Command migrate not found
Did you mean to run one of these?
[x] migrate:down
[ ] migrate:up

Press enter to confirm, ctrl+c to cancel

ConsoleExceptionMiddleware

Handles exceptions that while running a console command.

./tempest fail

Exception
A message from the exception default

18 }
19
20 function failingFunction(string $string)
21 {
22     throw new Exception("A message from the exception {$string}"); <
23 }

/Users/brent/Dev/tempest-console/tests/Fixtures/FailCommand.php:22

HelpMiddleware

Adds the global --help and -h flags to all commands.

./tempest serve --help
Usage serve [host='localhost:8000'] [publicDir='public/'] - Start a PHP development server

ForceMiddleware

Adds the global --force and -f flags to all commands. Using these flags will cause tempest to skip all $console->confirm() calls.

use Tempest\Console\Middleware\ForceMiddleware;

#[ConsoleCommand(
    middleware: [ForceMiddleware::class]
)]
public function __invoke(): void
{
    // This part will be skipped when the `-f` flag is applied
    if (! $this->console->confirm('continue?')) {
        return;
    }

    $this->console->writeln('continued');
}

CautionMiddleware

Adds a warning before running the command in production or staging.

use Tempest\Console\Middleware\CautionMiddleware;

#[ConsoleCommand(
    middleware: [CautionMiddleware::class]
)]
public function __invoke(): void
{
    $this->console->error('something cautionous');
}
Caution! Do you wish to continue? [yes/no]
something cautionous
CautionMiddleware — Tempest