Skip to main content

Field behaviour

Behaviour beyond a static value is declared on the field itself - a dynamic default, validation and a value transform as closures, right in the form:

use DrevOps\Tui\Handler\Context;

$p->text('name', 'Project name')
->default(fn(Context $c): string => basename($c->directory))
->validate(fn(mixed $v): ?string => is_string($v) && trim($v) !== '' ? NULL : 'A name is required.')
->transform(fn(mixed $v): mixed => is_string($v) ? trim($v) : $v);

Reusable validators and transformers live as public static methods on a consumer class. Reference one explicitly with a first-class callable - ->validate(Webroot::validate(...)) - or let the engine discover it: registering a namespace (new Tui($form, ['App\\Handler'])) resolves the class by field id (machine_name -> MachineName) and uses its static validate()/transform() whenever the field declares none. The field declaration always wins.

The TUI only collects: it presents answers and never applies them. Applying answers - writing files, renaming directories - is the consumer's job. A consumer that processes answers defines its own processor interface, keeping the form for collection and the processors for side effects - one class per field can carry both its process() and its reusable static behaviour (this is exactly what the Vortex CLI does).