Search by

A minimalist, zero-magic CLI runtime for Waffle Commons: commands registered explicitly at boot, constructor-injected dependencies, plus the Waffle Maker code generators.

Package info

github.com/waffle-commons/console

pkg:composer/waffle-commons/console

Statistics

Installs: 46

Dependents: 1

Suggesters: 0

Stars: 1

Open Issues: 1

0.1.0-beta7 2026-11-05 01:39 UTC

README

Discord PHP Version Require PHP CI codecov Latest Stable Version Latest Unstable Version Total Downloads Packagist License

Waffle Console Component

Release: 0.1.0-beta7 Β |Β  CHANGELOG.md

A minimalist, zero-magic CLI runtime for the Waffle Framework (RFC-012). Commands are registered explicitly at boot β€” no auto-discovery β€” and resolve their dependencies through constructor injection.

πŸ“¦ Installation

composer require waffle-commons/console

🧱 Surface

Class Role
Waffle\Commons\Console\ConsoleApplication final implementation of ConsoleApplicationInterface. Owns the command registry and the run loop.
Waffle\Commons\Console\Command\AbstractCommand Base class β€” implements CommandInterface with shared helpers.
Waffle\Commons\Console\Command\CacheClearCommand cache:clear β€” flushes the configured CacheInterface backend.
Waffle\Commons\Console\Command\RouteListCommand route:list β€” renders the compiled route table.
Waffle\Commons\Console\Command\SecurityAuditCommand security:audit β€” audits ABAC and CSRF coverage for every registered route (#[Voter] / #[RequiresCsrfToken]).
Waffle\Commons\Console\Command\MigrateCommand db:migrate β€” applies pending SQL migrations through the contracts MigrationRunnerInterface, then resets the pool.
Waffle\Commons\Console\Command\MigrationStatusCommand db:status β€” lists every migration version (applied, pending or missing) with its application time and whether it has a down script ([OPS-03]).
Waffle\Commons\Console\Command\MigrationRollbackCommand db:rollback [--steps=N] β€” reverts the N most recent versions (1 by default) through their .down.sql scripts, after checking every one of them ([OPS-03]).
Waffle\Commons\Console\Command\QueueWorkCommand queue:work β€” the queue worker ([QUEUE-02]): hands each message to the MessageHandlerInterface of its class, retries with back-off, dead-letters after the last attempt, resets the container after every message, and drains on SIGTERM/SIGINT. See Queue worker.
Waffle\Commons\Console\Queue\WorkerOptions The worker's limits, read and validated from its options.
Waffle\Commons\Console\Command\MemoryAuditCommand igor:audit β€” streams the monorepo-wide Igor memory-leak audit through the contracts AuditRunnerInterface.
Waffle\Commons\Console\Command\DataWarmupCommand data:warmup β€” pre-compiles SQR trees / routing tables into OPcache shared memory through the contracts DataWarmerInterface (Beta-3).
Waffle\Commons\Console\Maker\Command\Make*Command The ten Waffle Maker scaffolders (RFC-020): make:controller, make:dto, make:entity, make:repository, make:middleware, make:voter, make:command, make:http-client, make:event-pair, and make:migration ([OPS-03]: a dated Version<YYYYMMDDNN>_<Name>.sql and its .down.sql).
Waffle\Commons\Console\Input\ArgvInput InputInterface implementation parsing argv.
Waffle\Commons\Console\Output\StreamOutput Default OutputInterface writing to STDOUT / STDERR.
Waffle\Commons\Console\Output\NullOutput Silent OutputInterface for tests / quiet runs.
Waffle\Commons\Console\Exception\ConsoleException Base exception (implements ConsoleExceptionInterface).
Waffle\Commons\Console\Exception\CommandNotFoundException Thrown when find($name) cannot resolve.
Waffle\Commons\Console\Exception\InvalidArgumentException Thrown on invalid CLI argument shape.

πŸš€ Quick start

The exact signature of ConsoleApplication::__construct, verbatim from src/ConsoleApplication.php:

public function __construct(
    private readonly string $name = Constant::DEFAULT_APP_NAME,
    private readonly string $version = '0.0.0',
    private readonly OutputInterface $output = new StreamOutput(),
    ?array $argv = null, // null β†’ reads $_SERVER['argv']
) { /* … */ }

And the run loop:

use Waffle\Commons\Console\ConsoleApplication;
use Waffle\Commons\Console\Command\CacheClearCommand;
use Waffle\Commons\Console\Command\RouteListCommand;

$app = new ConsoleApplication(name: 'My App', version: '1.0.0'); // your app's name/version

$app->add(new CacheClearCommand($cache));
$app->add(new RouteListCommand($router));

exit($app->run()); // argv read from constructor, returns int exit code

πŸ“¬ Queue worker (queue:work)

QueueWorkCommand depends on contracts only. The application wires the queue driver β€” for example the Redis Streams driver of waffle-commons/queue β€” and the message handlers in its bin/waffle.

bin/waffle queue:work [--queue=default] [--max-messages=1000] [--memory-limit=128] [--max-attempts=3] [--backoff=5] [--wait=1000]
  • Handling. Each message goes to the handler of its class; a handled message is acknowledged.
  • Retries. A failed delivery is released for another attempt after --backoff seconds, doubled at each further failure and capped at an hour. After --max-attempts failed deliveries, the message goes to the dead-letter store with its envelope β€” whose attempts count every failed delivery β€” and the reason, then is acknowledged. A message whose class has no handler fails like any other delivery.
  • Reset. The container is reset after every message, in a finally, as the HTTP worker resets it after every request.
  • Stopping. On SIGTERM or SIGINT (with ext-pcntl), the message in flight is finished and settled, then the worker exits 0; an idle worker stops within --wait milliseconds. A signal never interrupts a handler, so keep handlers shorter than the termination grace period. After --max-messages messages or at --memory-limit MB, the worker exits 0 so that its supervisor starts a fresh process. When the queue or the dead-letter store fails, it exits 1, and the message in flight is delivered again once the queue reclaims it.
  • Exit codes. 0 stopped or recycled Β· 1 the queue failed Β· 64 an invalid option Β· 78 no handler, or two handlers for one message class.
  • Tracing. Each message runs in a Consumer span, process <queue>, whose parent is the trace context the dispatcher recorded in the envelope.

πŸͺœ Public API

final class ConsoleApplication implements ConsoleApplicationInterface
{
    public function getName(): string;
    public function getVersion(): string;
    public function add(CommandInterface $command): void;
    public function has(string $name): bool;
    public function find(string $name): CommandInterface;  // throws CommandNotFoundException
    public function all(): array;
    public function run(): int;                            // returns ExitCode::*->value
}

run():

  1. With no arguments, prints the available-commands listing and exits with ExitCode::USAGE.
  2. The built-in list command name reprints the same listing with ExitCode::SUCCESS.
  3. -v / -vv / -vvv / --verbose / --very-verbose / --debug / --quiet flags adjust output verbosity via OutputInterface::setVerbosity(Verbosity).
  4. Dispatches to the resolved command's execute(InputInterface, OutputInterface): int.
  5. ConsoleExceptionInterface and any other Throwable are caught and returned as ExitCode::FAILURE, with the message printed to stderr.

🐘 PHP 8.5 features used

  • final class ConsoleApplication.
  • Constructor property promotion + private readonly on every dependency.
  • Default OutputInterface $output = new StreamOutput() β€” PHP 8.1 new in initializers.
  • enum ExitCode: int and enum Verbosity (from contracts) for typed exit codes / verbosity levels.
  • Typed constants via Waffle\Commons\Contracts\Console\Constant.

🧭 Architectural boundary (mago guard)

An active dependency perimeter is enforced on every CI run by vendor/bin/mago guard (bundled into composer mago; zero baselines). The rules live in mago.toml under [guard.perimeter] β€” a forbidden use statement fails the build, not a reviewer.

Production code under Waffle\Commons\Console may depend only on:

  • Waffle\Commons\Console\** β€” itself
  • Waffle\Commons\Contracts\** β€” the shared contracts package, the only Waffle dependency permitted
  • Psr\** β€” PSR interfaces
  • @global + Psl\** β€” PHP core and the PHP Standard Library

Test code under WaffleTests\Commons\Console is unrestricted (@all). Structural rules are guarded too: interfaces must be named *Interface, Exception\** classes must end in *Exception, and any Enum\** namespace may hold only enum declarations.

Contract-first, component-agnostic by construction: components compose through waffle-commons/contracts, never directly through one another.

πŸ§ͺ Testing

docker exec -w /waffle-commons/console waffle-dev composer tests

πŸ“š Documentation

Central framework docs (DiΓ‘taxis) for this component:

πŸ“„ License

MIT β€” see LICENSE.md.