decodelabs/commandment

Console command interface

v0.2.0 2025-05-20 13:18 UTC

This package is auto-updated.

Last update: 2025-05-20 13:21:05 UTC


README

PHP from Packagist Latest Version Total Downloads GitHub Workflow Status PHPStan License

Console command interface

Commandment provides a unified system for building and dispatching console actions, mirroring the dispatcher and middleware stack of Harvest.

Installation

Install via Composer:

composer require decodelabs/commandment

Usage

Build your Action to interact with the command line. The Request object provides the raw console arguments and the means to parse them in a structured way.

Use Argument attributes on your Action class to define the arguments you want to accept. Constructor arguments are automatically injected into your Action class - import the Terminus Session to write to the output stream to keep your Actions portable.

namespace MyThing\Action;

use DecodeLabs\Commandment\Action;
use DecodeLabs\Commandment\Argument;
use DecodeLabs\Commandment\Request;
use DecodeLabs\Terminus\Session;

#[Argument\Value(
    name: 'input',
    description: 'Input value',
    required: true,
    default: 'default'
)]
#[Argument\Flag(
    name: 'verbose',
    shortcut: 'v',
    description: 'Enable verbose output'
)]
#[Argument\Option(
    name: 'potatoes',
    shortcut: 'p',
    description: 'How many potatoes?',
    default: 5
)]
class MyAction implements Action
{
    public function __construct(
        protected Session $io
    ) {
    }

    public function execute(
        Request $request
    ): bool {
        $this->io->writeLine('Hello world!');

        $this->io->writeLine('Input: '. $request->parameters->tryString('input'));

        if($request->parameters->asBool('verbose')) {
            $this->io->writeLine('Verbose output enabled');

            for($potato = 0; $potato < $request->parameters->asInt('potatoes'); $potato++) {
                $this->io->writeLine('Potato #'. ($potato + 1));
            }
        }

        return true;
    }
}
effigy my-action "this is my input" -v --potatoes=3

Dispatching

To run your Action, create a Dispatcher and a Request object, then call the dispatch() method:

use DecodeLabs\Commandment\Dispatcher;

$dispatcher = new Dispatcher();

$request = $dispatcher->newRequest(
    command: 'my-action',
    arguments: [
        'this is my input',
        '-v',
        '--potatoes=3'
    ],
    attributes: [
        'arbitrary' => 'data'
    ],
    server: [
        'override' => '$_SERVER'
    ]
);

$dispatcher->dispatch($request);

If you want to provide extra objects for dependency injection, you can add them to the Slingshot instance, either on the Dispatcher or on the Request object:

use DecodeLabs\Commandment\Dispatcher;
use MyThing\PotatoPeeler;
use MyThing\PotatoMasher;

$dispatcher = new Dispatcher();

$dispatcher->slingshot->addType(new PotatoPeeler());

$request = $dispatcher->newRequest('my-action', ['input ...']);
$request->slingshot->addType(new PotatoMasher());
$request->slingshot->addParameter([
    'arbitrary' => 'data'
]);

$dispatcher->dispatch($request);

You can then reference these types in your Action constructor:

namespace MyThing\Action;
use DecodeLabs\Commandment\Action;
use DecodeLabs\Commandment\Request;
use DecodeLabs\Terminus\Session;
use MyThing\PotatoPeeler;
use MyThing\PotatoMasher;

class MyAction implements Action
{
    public function __construct(
        protected Session $io,
        protected PotatoPeeler $peeler,
        protected PotatoMasher $masher,
        protected string $arbitrary
    ) {
    }

    public function execute(
        Request $request
    ): bool {
        // Do the thing
        return true;
    }
}

Middleware

Commandment supports simple middleware which can be used to modify the request before the Action is executed. It doesn't need to handle the $next middleware like traditional middleware as the CLI context doesn't require traditional response handling. Instead, just return a modified Request object.

use DecodeLabs\Commandment\Middleware;

class MyMiddleware implements Middleware
{
    public function handle(
        Request $request,
    ): Request {
        // Do something with the request

        $request = $request->rewrite(
            command: 'redirected-action',
            arguments: [
                'new-argument'
            ],
        );

        return $request;
    }
}

Add the middleware to the dispatcher before dispatching:

use DecodeLabs\Commandment\Dispatcher;
use MyThing\Middleware\MyMiddleware;

$dispatcher = new Dispatcher();
$dispatcher->addMiddleware(new MyMiddleware());
$request = $dispatcher->newRequest('my-action', ['input ...']);
$dispatcher->dispatch($request);

Licensing

Commandment is licensed under the MIT License. See LICENSE for the full license text.