rmb32 / menu
A generic, JSON-driven CLI menu engine — drives any menu-shaped flow
Requires
- php: ^8.5
- ext-ds: ^2.0
- php-ds/php-ds: ^2.0
- symfony/console: ^7.0
Requires (Dev)
- deptrac/deptrac: ^4.7
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^12.0
- squizlabs/php_codesniffer: ^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-07 18:34:40 UTC
README
Part of BarnSuite.
A generic, JSON-driven CLI menu engine. Describe a multi-step flow — a wizard, a setup script, anything shaped like "ask something, do something, decide what's next" — as a JSON file of named menus, and Menu turns it into a running interaction: prompting, validating, branching, looping, and dispatching writes, all driven by data rather than hand-written control flow.
Requirements
- PHP 8.5+
Installation
composer require rmb32/menu
The idea
A menu graph is a JSON object keyed by menu id; each entry is one menu, discriminated by a "type" field:
text— a free-typed prompt, stored (or dispatched) verbatim.select— a choice between dynamically-fetched options, plus optional fixed "create new" and "back" branches.repeat— a user-driven "add another?" loop.forEach— a bounded, data-driven loop over a supplied item list.
Three things a consumer implements to plug their own domain in:
OptionSource— supplies aselect's dynamic choices, or aforEach's items.DispatchAction— the one write escape hatch; runs against a menu's own resolved data, e.g. to persist something once an answer is confirmed.TextValidator— rejects a typed answer and forces a re-ask.
Everything a menu needs is declared explicitly via needs/optionalNeeds — there's no implicit access to the whole running AppState, only the keys a menu says it depends on. See docs/menu-schema.md for the full field-by-field reference.
Quick example
use Rmb32\Menu\Api\Factory\MenuEngineFactory;
use Rmb32\Menu\Api\Factory\StateMachineFactory;
use Rmb32\Menu\Api\ValueObject\AppState;
use Rmb32\Menu\Api\Factory\AskerFactory;
$appState = AppState::new();
$asker = new AskerFactory()->create($input, $output);
$transitions = new MenuEngineFactory()->create()->build(
'path/to/menu-graph.json',
$appState,
$asker,
optionSources: ['myChoices' => new MyOptionSource()],
);
$result = new StateMachineFactory()->create()->run($transitions, 'start', $appState);
$result->finished; // true if the flow reached a `{"type": "finish"}` outcome
Related packages
- Barnspec CLI — uses Menu to drive the
draft/review/refinewizards. - Barnspec — the application those wizards belong to.
- Filesystem — the other shared plumbing package.
More docs
Menu schema reference · Internals · History · Known issues
License
Proprietary. See LICENSE. Copyright (c) Roger Barnfather.