Template
A fixed shape with named slots to fill in. The shape's fixed text renders as context and only the slots are typed into, so a value that has to be formatted a particular way does not put that burden on the reader. It collects a string - the assembled shape - and its parts are available alongside it.
$p->template('crate', 'Crate label')
->pattern('{{orchard}}-{{fruit}}-{{grade}}') // The shape to fill in.
->default('valley-pear-a'); // Initial value, assembled.
// Label a slot, and validate it apart from the others:
$p->template('crate', 'Crate label')
->pattern('{{orchard}}-{{fruit}}-{{grade}}')
->slot('orchard', 'Orchard')
->slot('fruit', 'Fruit')
->slot('grade', 'Grade', fn(string $value): ?string => preg_match('/^[a-c]$/', $value) === 1 ? NULL : 'use a single letter a-c');
Runnable script: playground/02-fields-template.php.
Options
| Name | Description | Required | Default |
|---|---|---|---|
pattern() | The shape to fill in, carrying {{name}} slots. | Yes | None |
default() | Initial value, as the assembled string. | No | '' (empty) |
slot() | Label and validator of one slot: slot(string $name, string $label = '', ?Closure $validate = NULL). | No | None |
A slot name is a word ({{grade}}, {{part_1}}), and inner whitespace is allowed ({{ grade }}). Every other character in the pattern is fixed text.
Only the slot name is required: an omitted label falls back to the slot name, and an omitted validator leaves the slot checked by the field's own validator alone - so ->slot('grade') is enough to name a slot without constraining it.
Five rules are enforced when the form is built, each raising a FormException: a template field declares a pattern at all, that pattern declares at least one slot, no slot name is repeated, two slots are separated by some fixed text, and ->slot() names a slot the pattern actually declares. The middle two answer to the same reason - every slot is filled in separately and the answer is read back off the assembled string - but they are separate rules, and a pattern can break either one on its own: {{a}}-{{a}} has a boundary and still has no one place to put a value, while {{a}}{{b}} names two slots and still has no boundary to read back at.
Reading the answer
The answer is the whole assembled string; the parts are read back off it, so the two can never disagree.
$answers = (new Tui($form))->run();
$answers->value('crate'); // 'valley-pear-a'
$answers->parts('crate'); // ['orchard' => 'valley', 'fruit' => 'pear', 'grade' => 'a']
parts() returns an empty array for any question that is not a template, and for a template answer that does not have the shape.
Keyboard
| Key | Action |
|---|---|
| printable keys | Insert into the slot holding the caret |
| Tab / ↓ | Move to the next slot |
| ↑ | Move to the previous slot |
| ← / → | Move the caret inside the slot |
| Backspace | Delete the character before the caret |
| Enter | Accept |
| Esc | Cancel |
Moving off a slot validates it. A rejected slot shows its error but does not hold the caret, so slots can be filled in any order; accepting validates every slot and puts the caret back on the first one that fails.
Because a slot ends where the next fixed chunk begins, a slot's value cannot contain that chunk: valley-west in the first slot of {{orchard}}-{{fruit}} would move the boundary, and the answer would read back as orchard: valley, fruit: west. Accepting rejects it. The last slot runs to the end of the string, so it has no such limit.
Headless behavior
An unattended run asks for the assembled value directly - there are no slots to tab between - and holds it to the same rules the editor does: it must have the shape, and every slot must pass its own validator.
APP_CRATE=valley-pear-a php collect.php
An empty value is an unfilled template, left to the required() check rather than rejected as a mismatch. In the agent schema the field is a string carrying a pattern - the expression the assembled answer must match - so a generated answer can be checked before it is sent.
Display modes
In all four display modes - Unicode or ASCII, color on or off:
| ANSI | No ANSI | |
| Unicode | ||
| ASCII |