Commandment provides a unified system for building and dispatching console actions, mirroring the dispatcher and middleware stack of Harvest.
Install via Composer:
composer require decodelabs/commandment
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
To run your Action, create a Dispatcher
and a Request
object, then call the dispatch()
method:
use DecodeLabs\Archetype;
use DecodeLabs\Commandment\Dispatcher;
use DecodeLabs\Monarch;
$dispatcher = new Dispatcher(
Monarch::getService(Archetype::class)
);
$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 MyThing\PotatoPeeler;
use MyThing\PotatoMasher;
$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;
}
}
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 MyThing\Middleware\MyMiddleware;
$dispatcher->addMiddleware(new MyMiddleware());
$request = $dispatcher->newRequest('my-action', ['input ...']);
$dispatcher->dispatch($request);
Commandment is licensed under the MIT License. See LICENSE for the full license text.