Skip to main content

Configuration

A form is a tree of panels, each holding fields, built fluently. Rules are named-argument spec objects, so the IDE completes them and a typo fails at declaration time:

use DrevOps\Tui\Builder\Form;
use DrevOps\Tui\Builder\PanelBuilder;
use DrevOps\Tui\Condition\Condition;
use DrevOps\Tui\Derive\Derive;

$form = Form::create('My form')
->panel('general', 'General', function (PanelBuilder $p): void {
// text | select | multiselect | suggest | confirm
// number | textarea | password | search | multisearch | pause
$p->text('name', 'Project name')->required();

// Compute one field from others.
$p->text('machine_name', 'Machine name')->derive(new Derive('{{name}}', transform: 'machine'));

$p->select('profile', 'Profile')
->default('standard')
->options(['standard' => 'Standard', 'custom' => 'Custom']);

// Shown only when the condition holds; compose with Condition::all()/any()/not().
$p->text('profile_custom', 'Custom profile')->when(new Condition('profile', eq: 'custom'));
});

Each field builder chains ->description(), ->default(), ->required(), ->weight(), ->options() / ->option() (with per-option descriptions and optional disabled state), ->heading() / ->separator() (non-selectable option-list structure), ->when(new Condition(...)), ->derive(new Derive(...)), ->discover(...), ->validate(...) and ->transform(...).

Form-level methods tune the interactive TUI: ->theme() names a theme, auto-detected from the terminal background when unset (see Themes), ->banner() sets a start banner, ->buttons() controls the submit/cancel buttons, ->clearOnExit() keeps or clears the final frame, and ->color() / ->unicode() force a display mode.

Derived values

A Derive computes a field from other answers: a template with {{field}} placeholders plus a transform. A transform is any str2name conversion (machine, kebab, pascal, ...) plus host, lower, upper and initials - an unknown name throws when the form is declared. Chains of derives settle to a fixpoint:

$p->text('machine_name', 'Machine name')->derive(new Derive('{{name}}', 'machine'));
$p->text('package', 'Composer package')->derive(new Derive('{{vendor}}/{{machine_name}}', 'lower'));
$p->text('namespace', 'PHP namespace')->derive(new Derive('{{name}}', 'pascal'));

Conditional fields

A ->when() rule shows or hides a field based on other answers, with operators eq / ne / in / contains, composable with Condition::all(), Condition::any() and Condition::not():

$p->text('docker_image', 'Docker base image')->default('php:8.4-cli')->when(new Condition('features', contains: 'docker'));
$p->confirm('docker_compose', 'Generate a docker-compose.yml?')->when(Condition::all(new Condition('features', contains: 'docker'), new Condition('type', eq: 'application')));

A form-level ->fixup(new Fixup(set: ..., to: ..., when: ...)) reconciles dependent answers on every settle pass, so an answer that no longer makes sense after another change is corrected instead of leaking through.