waffle-commons / console
A minimalist, zero-magic CLI runtime for Waffle Commons: commands registered explicitly at boot, constructor-injected dependencies, plus the Waffle Maker code generators.
Requires
- php: ^8.5
- psr/clock: ^1.0
- psr/log: ^3.0
- waffle-commons/contracts: 0.1.0-beta7
- waffle-commons/utils: 0.1.0-beta7
Requires (Dev)
- carthage-software/mago: ^1.59
- cyclonedx/cyclonedx-php-composer: ^6.2
- igor-php/igor-php: ^0.10.1
- php-mock/php-mock-phpunit: ^2.16
- phpunit/phpunit: ^12.5
- vimeo/psalm: ^6.20
Suggests
- ext-pcntl: So that queue:work finishes the message in flight on SIGTERM or SIGINT before it exits. Without it, a signal stops the worker at once and the message is delivered again once the queue reclaims it.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-05 01:59:04 UTC
README
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
--backoffseconds, doubled at each further failure and capped at an hour. After--max-attemptsfailed 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--waitmilliseconds. A signal never interrupts a handler, so keep handlers shorter than the termination grace period. After--max-messagesmessages or at--memory-limitMB, 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.
0stopped or recycled Β·1the queue failed Β·64an invalid option Β·78no handler, or two handlers for one message class. - Tracing. Each message runs in a
Consumerspan,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():
- With no arguments, prints the available-commands listing and exits with
ExitCode::USAGE. - The built-in
listcommand name reprints the same listing withExitCode::SUCCESS. -v/-vv/-vvv/--verbose/--very-verbose/--debug/--quietflags adjust output verbosity viaOutputInterface::setVerbosity(Verbosity).- Dispatches to the resolved command's
execute(InputInterface, OutputInterface): int. ConsoleExceptionInterfaceand any otherThrowableare caught and returned asExitCode::FAILURE, with the message printed to stderr.
π PHP 8.5 features used
final class ConsoleApplication.- Constructor property promotion +
private readonlyon every dependency. - Default
OutputInterface $output = new StreamOutput()β PHP 8.1new in initializers. enum ExitCode: intandenum 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\**β itselfWaffle\Commons\Contracts\**β the shared contracts package, the only Waffle dependency permittedPsr\**β 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:
- Reference:
reference/console.md - Explanation:
explanation/aot-compilation.md - Full documentation tree: waffle-commons/documentation
π License
MIT β see LICENSE.md.