FieldSpec
in package
CRUDUI canonical FieldSpec model. SINGLE SOURCE OF TRUTH (SPEC §3).
This class mechanizes the canonical CRUDUI model.
What is mechanized here:
- TOP_LEVEL: the closed, ordered set of field keys, each tagged with its role.
- SLOTS: role slots validate/design/behavior/options. Every slot value is polymorphic: false (suppress synthesized inheritance) | } (a sub-key map) | true (bare enable).
- DEPENDENCY_BUCKETS: dependency isolation. type-dependent keys live under the
optionsslot; multiple/lang/items-dependent keys live UNDER that structural key. A dependent key is never hoisted to the field root. - CLOSED_BUCKET_KEYS: design, behavior, multiple, lang and validate (and each design node's class/style) reject unknown keys; options and the items dynamic source stay open.
- DESIGN_NODE_MAP: which DOM node a design appearance entry targets (R8).
- CONDITION_MAP: a declaration-ordered map; its default key is the literal
trueexpression (always truthy). R4 forbids convention sigils such as_. - COMPOSITION: $ref (base inheritance) then $patch (mutation).
- FORBIDDEN_META_KEYS: condition-only and retired meta keys that MUST NOT appear anywhere in a CRUDUI field — not at root, not one level below any slot/bucket.
Recognition. Only canonical CRUDUI names are recognition keys (R2 forbids dual
spellings; R4 forbids magic tokens like * or :). Other spellings such as
multiple_max, sortable*, lang:append or langs are not recognized.
Comment keys (x{key}). The meta-schema is expected to x-strip every x-prefixed
key BEFORE validating the canonical spec; this model therefore REJECTS an
x-prefixed key in a canonical document (it is a comment, not a canonical
key) while recording why via isCommentKey(). x-strip happens upstream.
Round-trip. parse() reads a CRUDUI JSON document into an ordered associative array (PHP associative arrays preserve insertion order natively, so condition-map and properties declaration order survive) and serialize() emits byte-identical canonical JSON. Both directions exist — no Marshal-only half.
Table of Contents
Constants
- BEHAVIOR_SUB_KEYS : array<int, string> = ['onchange', 'onclick', 'onload']
- Sub-keys inside the `behavior` slot. Opaque client JS, passed through WITHOUT the expression engine (§4). `behavior: false` voids synthesized inheritance. An action label sits adjacent at behavior.{action}.label.
- CLOSED_BUCKET_KEYS : array<string, array<int, string>> = ['design' => self::DESIGN_SUB_KEYS, 'behavior'...
- Closed buckets and the only keys each map form accepts; any other key is an unknown-key violation. Mirrors the JSON schema: design, behavior, multiple, lang and validate close their map form, and every design node (DESIGN_NODE_KEYS) closes to class/style. `options` and the `items` dynamic source stay open (see OPTIONS_IS_OPEN) and are checked only for forbidden and comment keys.
- COMPOSITION_DIRECTIVES : array<int, string> = ['$ref', '$patch']
- Composition directives, in resolution order: $ref (base inheritance) is expanded first; an unresolved $ref cannot be loaded. $patch then mutates.
- CONDITION_MAP_DEFAULT_KEY : mixed = 'true'
- The condition-map default key: the literal `true` expression (always truthy). R4 forbids convention sigils such as `_`. A condition map is an ordered map {expr: value, …}; keys are evaluated in declaration order and the first truthy expression's value wins; otherwise the `true` key's value (if present), else null. A single `?:` expression is the one-entry shorthand with identical semantics. Each key is an expressions.md §2 expression; the condition map is a thin wrapper over the same engine, not a separate parser. Evaluated value type — boolean | string | number | null — is decided by the call site.
-
DEPENDENCY_BUCKETS
: array<string, array{location: string, keys: list
}> = ['type' => ['location' => 'options', 'keys' =>... - Dependency-bucket targets and where each one's dependent keys live. Keys here are CANONICAL CRUDUI names only — no alternative spellings, no magic tokens.
- DESIGN_NODE_KEYS : array<int, string> = ['class', 'style']
- DESIGN_NODE_MAP : array<int, string> = ['show', 'class', 'style', 'attributes', 'label...
- Design appearance entries and the DOM node each targets (R8): show is the display condition (not a node); class/style hit the primary node; the named nodes carry .class/.style. Same surface as SPEC §design_node_map.
- DESIGN_NODES : array<int, string> = ['label', 'wrapper', 'group', 'prepend']
- The design nodes (label/wrapper/group/prepend) and the closed key set of each node's map form.
- DESIGN_SUB_KEYS : array<int, string> = ['show', 'class', 'style', 'attributes', 'layou...
- Sub-keys inside the `design` slot. show = display condition (NOT a node); class/style = the primary (input) node; attributes = the declared attributes of the control; layout = the layout a group gives its fields (not a node); label|wrapper|group|prepend carry .class/.style for that named node, and the wrapper also .attributes. Which node a class/style targets is revealed by the key (R8).
- FORBIDDEN_META_KEYS : array<int, string> = ['display_switch', 'display_target', 'if', 'whe...
- Condition-only and retired meta keys that MUST NOT appear ANYWHERE in a CRUDUI field — at root, and also one level below any slot or bucket body. Open buckets stay open via propertyNames:{not:{enum:FORBIDDEN}}, so extension is allowed but every one of these is globally blocked. `x{key}` comment keys are not listed here; they are handled separately by the x-strip premise (see isCommentKey()).
- OPTIONS_IS_OPEN : mixed = true
- The `options` slot is open (a type may introduce its own settings). The schema keeps it open with propertyNames:{not:{enum:FORBIDDEN}} rather than additionalProperties:true, so a type extends freely yet a forbidden meta key is still blocked one level below `options`.
- OPTIONS_SUB_KEYS : array<int, string> = ['keyword_min_length', 'max_tags', 'checkbox_la...
- Sub-keys inside the `options` slot — the type-dependent slot (the type defines and validates them; the core stays uninvolved). Container chrome and type-dependent scripts/callbacks live here too. Removing the type also removes these options. A new widget does not change the core.
- ROLE_CONTENT : mixed = 'content'
- Content: human-facing text. A plain string, a per-language LangMap {ko,en} (G3 content translation), or null/omitted for empty content — null is identical to absent and has no render/validate effect (SPEC §2 G3).
- ROLE_FORM : mixed = 'form'
- Form declaration: buttons, action and dataLimits, honored on the form root only.
- ROLE_IDENTITY : mixed = 'identity'
- Identity: field type. Defines and owns the `options` slot.
- ROLE_MESSAGES : mixed = 'messages'
- Messages: error message overrides keyed by registered rule name.
- ROLE_SLOT : mixed = 'role_slot'
- Role slot: validate/design/behavior/options (§3 role distribution).
- ROLE_STRUCTURE : mixed = 'structure'
- Structure: $ref/$patch composition, child maps, dependency buckets.
- ROLE_STRUCTURE_IDENTITY : mixed = 'structure_identity'
- Structure/identity: serialization name (explicit, semantic; R4).
- SLOT_SUB_KEYS : array<string, array<int, string>> = ['validate' => self::VALIDATE_SUB_KEYS, 'design...
- Slot name => its recognized sub-keys.
- SLOT_VALUE_FORMS : array<int, string> = ['false', 'map', 'true']
- The polymorphic slot value forms. A slot value MUST be one of these.
- SLOTS : array<int, string> = ['validate', 'design', 'behavior', 'options']
- Role slot names (§3). Every slot is polymorphic: false | {} | true.
- TOP_LEVEL : array<string, string> = ['type' => self::ROLE_IDENTITY, 'name' => self:...
- The closed set of top-level field keys, in declaration order, each with its role. A CRUDUI field accepts these keys and nothing else; the schema enforces this with additionalProperties:false plus the global propertyNames forbidden-key check.
- VALIDATE_SUB_KEYS : array<int, string> = ['required', 'email', 'minlength', 'maxlength',...
- Sub-keys recognized inside the `validate` slot: the registered rule names, in the order of the validator's default messages. Each value may itself be an expression / condition map, so conditional validation needs no separate key (e.g. required: '.subscribe', email: true). Polymorphic slot (R: G1).
- WRAPPER_NODE_KEYS : array<int, string> = ['class', 'style', 'attributes']
- The closed key set of the wrapper node: its appearance and the declared attributes of the node root.
Methods
- isCommentKey() : bool
- Whether a key is an `x`-prefixed comment key. The meta-schema x-strips these before validating the canonical spec, so a canonical document must not carry one. Rejected by validation; recorded here for the reason.
- isDependencyTarget() : bool
- Whether a key is a structural dependency-bucket target (type/multiple/lang/items).
- isDynamicItemsSource() : bool
- Whether a decoded `items` value is a dynamic source descriptor: a non-empty string-keyed map whose keys are ALL dynamic-source keys (model/method/table/relations/api_server/items, the placeholder included).
- isForbiddenMetaKey() : bool
- Whether a key is forbidden in a CRUDUI field at ANY depth.
- isSlot() : bool
- Whether a key is a role slot (validate/design/behavior/options).
- isTopLevelKey() : bool
- Whether a key is a recognized top-level field key.
- isValidContent() : bool
- Whether a content value is a valid CRUDUI content shape (SPEC §2 G3): a plain string, a per-language LangMap (string-keyed array of string|null), or null (empty content — identical to absent, no render/validate effect). A LangMap slot may itself be null (empty label for that language).
- isValidSlotValue() : bool
- Whether a slot value is a valid polymorphic form (false | array map | true).
- isValueLabelItemsMap() : bool
- Whether an `items` value is a static value→label content map (SPEC §2 G3): a non-empty string-keyed array whose entry values are labels — a string, a LangMap array {ko,en}, or null (empty label). The option VALUE is the KEY (the membership target); the label is display-only and never a membership value, so a LangMap label and a null label have no membership effect.
- itemsMembershipValues() : array<int, mixed>
- The membership values of an `items` source: for a static value→label map the KEYS (the option values — labels are display-only, SPEC §2 G3); for a static list the list itself; otherwise an empty list (a dynamic source has no static membership set). Mirrors the `in`-rule flatten across the four engines.
- parse() : array<string, mixed>
- Parse a CRUDUI JSON document into an ordered associative array for the analysis API (validate/predicates). PHP associative arrays preserve insertion order, so condition-map and properties declaration order survives. Throws on malformed JSON.
- parseTree() : mixed
- Parse a CRUDUI JSON document into an object tree (stdClass for objects, list for arrays). This keeps insertion order AND distinguishes the empty-map `{}` polymorphic form from an empty array `[]`. Throws on malformed JSON.
- roleOf() : string|null
- Role of a top-level key, or null when the key is not top-level.
- roundTrips() : bool
- Round-trip a canonical JSON document and report whether it is byte-stable.
- serialize() : string
- Serialize a decoded CRUDUI field (associative-array form) back to canonical JSON, preserving key order. Empty associative arrays emit as `[]`; when the `{}` polymorphic form matters, use the *Tree round-trip path instead.
- serializeTree() : string
- Serialize a CRUDUI object tree back to canonical JSON, preserving key order and the `{}` form. Round-trip invariant: serializeTree(parseTree(canonical)) === canonical for any already-canonical document.
- slotValueForm() : string|null
- The polymorphic form name of a slot value, or null when invalid.
- subKeysOf() : array<int, string>|null
- Recognized sub-keys for a slot, or null when the name is not a slot.
- validate() : array<int, string>
- Deep-validate a decoded CRUDUI field. Returns the list of violations (empty = valid). Enforces: closed top-level key set, polymorphic slot value forms, dependency isolation (a bucket key only under its location), closed bucket key sets (CLOSED_BUCKET_KEYS: design, behavior, multiple, lang, validate and each design node's class/style), `messages` keys limited to the registered rule names, and the global forbidden-key check (root + one level below every slot and bucket; open options/items too). An `x`-prefixed comment key is a violation (x-strip is an upstream step).
- guardInside() : void
- Append a violation for every forbidden, comment or (in a closed bucket) unknown key found one level below the given container. Open buckets ($allowed null) accept extension but never a forbidden or comment key.
Constants
BEHAVIOR_SUB_KEYS
Sub-keys inside the `behavior` slot. Opaque client JS, passed through WITHOUT the expression engine (§4). `behavior: false` voids synthesized inheritance. An action label sits adjacent at behavior.{action}.label.
public
array<int, string>
BEHAVIOR_SUB_KEYS
= ['onchange', 'onclick', 'onload']
CLOSED_BUCKET_KEYS
Closed buckets and the only keys each map form accepts; any other key is an unknown-key violation. Mirrors the JSON schema: design, behavior, multiple, lang and validate close their map form, and every design node (DESIGN_NODE_KEYS) closes to class/style. `options` and the `items` dynamic source stay open (see OPTIONS_IS_OPEN) and are checked only for forbidden and comment keys.
public
array<string, array<int, string>>
CLOSED_BUCKET_KEYS
= ['design' => self::DESIGN_SUB_KEYS, 'behavior' => self::BEHAVIOR_SUB_KEYS, 'multiple' => ['only', 'min', 'max', 'copy', 'sortable', 'title', 'controls', 'header', 'onclick'], 'lang' => ['mode', 'only', 'name', 'key', 'frame', 'title', 'group_class'], 'validate' => self::VALIDATE_SUB_KEYS]
COMPOSITION_DIRECTIVES
Composition directives, in resolution order: $ref (base inheritance) is expanded first; an unresolved $ref cannot be loaded. $patch then mutates.
public
array<int, string>
COMPOSITION_DIRECTIVES
= ['$ref', '$patch']
$after/$before/$merge/$remove are not directives and are forbidden as literal keys.
CONDITION_MAP_DEFAULT_KEY
The condition-map default key: the literal `true` expression (always truthy). R4 forbids convention sigils such as `_`. A condition map is an ordered map {expr: value, …}; keys are evaluated in declaration order and the first truthy expression's value wins; otherwise the `true` key's value (if present), else null. A single `?:` expression is the one-entry shorthand with identical semantics. Each key is an expressions.md §2 expression; the condition map is a thin wrapper over the same engine, not a separate parser. Evaluated value type — boolean | string | number | null — is decided by the call site.
public
mixed
CONDITION_MAP_DEFAULT_KEY
= 'true'
DEPENDENCY_BUCKETS
Dependency-bucket targets and where each one's dependent keys live. Keys here are CANONICAL CRUDUI names only — no alternative spellings, no magic tokens.
public
array<string, array{location: string, keys: list}>
DEPENDENCY_BUCKETS
= ['type' => ['location' => 'options', 'keys' => self::OPTIONS_SUB_KEYS], 'multiple' => ['location' => 'multiple', 'keys' => ['only', 'min', 'max', 'copy', 'sortable', 'title', 'controls', 'header', 'onclick']], 'lang' => ['location' => 'lang', 'keys' => ['mode', 'only', 'name', 'key', 'frame', 'title', 'group_class']], 'items' => ['location' => 'items', 'keys' => ['model', 'method', 'table', 'relations', 'api_server', 'items']]]
- type -> the
optionsslot (scalar target's dedicated slot). - multiple -> directly under the
multiplestructural key. - lang -> directly under the
langstructural key. - items -> directly under
items(polymorphic option source).
The items dynamic-source keys are STRUCTURE ONLY (the type fixes the
declared shape; the runtime loads options). The supported shape
(type: search) is model (a name string OR a nested
{table, relations, keys} relational query) + a sibling api_server (a
runtime HTTP-endpoint fn reference) + a placeholder static items — all
three coexist under items and are preserved verbatim; none is resolved
here. Runtime resolution is out of scope (SPEC §6 R1).
DESIGN_NODE_KEYS
public
array<int, string>
DESIGN_NODE_KEYS
= ['class', 'style']
DESIGN_NODE_MAP
Design appearance entries and the DOM node each targets (R8): show is the display condition (not a node); class/style hit the primary node; the named nodes carry .class/.style. Same surface as SPEC §design_node_map.
public
array<int, string>
DESIGN_NODE_MAP
= ['show', 'class', 'style', 'attributes', 'label.class', 'label.style', 'wrapper.class', 'wrapper.style', 'wrapper.attributes', 'group.class', 'group.style', 'prepend.class', 'prepend.style']
DESIGN_NODES
The design nodes (label/wrapper/group/prepend) and the closed key set of each node's map form.
public
array<int, string>
DESIGN_NODES
= ['label', 'wrapper', 'group', 'prepend']
DESIGN_SUB_KEYS
Sub-keys inside the `design` slot. show = display condition (NOT a node); class/style = the primary (input) node; attributes = the declared attributes of the control; layout = the layout a group gives its fields (not a node); label|wrapper|group|prepend carry .class/.style for that named node, and the wrapper also .attributes. Which node a class/style targets is revealed by the key (R8).
public
array<int, string>
DESIGN_SUB_KEYS
= ['show', 'class', 'style', 'attributes', 'layout', 'label', 'wrapper', 'group', 'prepend']
FORBIDDEN_META_KEYS
Condition-only and retired meta keys that MUST NOT appear ANYWHERE in a CRUDUI field — at root, and also one level below any slot or bucket body. Open buckets stay open via propertyNames:{not:{enum:FORBIDDEN}}, so extension is allowed but every one of these is globally blocked. `x{key}` comment keys are not listed here; they are handled separately by the x-strip premise (see isCommentKey()).
public
array<int, string>
FORBIDDEN_META_KEYS
= ['display_switch', 'display_target', 'if', 'when', 'show_if', '_', 'seqtokey', '__13hex__', '$after', '$before', '$merge', '$remove', 'xclass', 'xstyle', 'x{key}']
OPTIONS_IS_OPEN
The `options` slot is open (a type may introduce its own settings). The schema keeps it open with propertyNames:{not:{enum:FORBIDDEN}} rather than additionalProperties:true, so a type extends freely yet a forbidden meta key is still blocked one level below `options`.
public
mixed
OPTIONS_IS_OPEN
= true
OPTIONS_SUB_KEYS
Sub-keys inside the `options` slot — the type-dependent slot (the type defines and validates them; the core stays uninvolved). Container chrome and type-dependent scripts/callbacks live here too. Removing the type also removes these options. A new widget does not change the core.
public
array<int, string>
OPTIONS_SUB_KEYS
= ['keyword_min_length', 'max_tags', 'checkbox_label', 'on_label', 'collapse', 'expend', 'view_total', 'stepper', 'blank_message', 'callback', 'event']
Mirrors SPEC §3 slots.options.sub_keys exactly (the wider type-dependent surface — marker_draggable/zoom/geometry_type — rides through the open options bucket checked only by global forbidden-key validation).
ROLE_CONTENT
Content: human-facing text. A plain string, a per-language LangMap {ko,en} (G3 content translation), or null/omitted for empty content — null is identical to absent and has no render/validate effect (SPEC §2 G3).
public
mixed
ROLE_CONTENT
= 'content'
A LangMap slot may itself be null (empty label for that language).
ROLE_FORM
Form declaration: buttons, action and dataLimits, honored on the form root only.
public
mixed
ROLE_FORM
= 'form'
ROLE_IDENTITY
Identity: field type. Defines and owns the `options` slot.
public
mixed
ROLE_IDENTITY
= 'identity'
ROLE_MESSAGES
Messages: error message overrides keyed by registered rule name.
public
mixed
ROLE_MESSAGES
= 'messages'
ROLE_SLOT
Role slot: validate/design/behavior/options (§3 role distribution).
public
mixed
ROLE_SLOT
= 'role_slot'
ROLE_STRUCTURE
Structure: $ref/$patch composition, child maps, dependency buckets.
public
mixed
ROLE_STRUCTURE
= 'structure'
ROLE_STRUCTURE_IDENTITY
Structure/identity: serialization name (explicit, semantic; R4).
public
mixed
ROLE_STRUCTURE_IDENTITY
= 'structure_identity'
SLOT_SUB_KEYS
Slot name => its recognized sub-keys.
public
array<string, array<int, string>>
SLOT_SUB_KEYS
= ['validate' => self::VALIDATE_SUB_KEYS, 'design' => self::DESIGN_SUB_KEYS, 'behavior' => self::BEHAVIOR_SUB_KEYS, 'options' => self::OPTIONS_SUB_KEYS]
SLOT_VALUE_FORMS
The polymorphic slot value forms. A slot value MUST be one of these.
public
array<int, string>
SLOT_VALUE_FORMS
= ['false', 'map', 'true']
SLOTS
Role slot names (§3). Every slot is polymorphic: false | {} | true.
public
array<int, string>
SLOTS
= ['validate', 'design', 'behavior', 'options']
false suppresses synthesized inheritance; } carries sub-keys; true is the bare-enable form.
TOP_LEVEL
The closed set of top-level field keys, in declaration order, each with its role. A CRUDUI field accepts these keys and nothing else; the schema enforces this with additionalProperties:false plus the global propertyNames forbidden-key check.
public
array<string, string>
TOP_LEVEL
= ['type' => self::ROLE_IDENTITY, 'name' => self::ROLE_STRUCTURE_IDENTITY, 'default' => self::ROLE_STRUCTURE, 'properties' => self::ROLE_STRUCTURE, 'items' => self::ROLE_STRUCTURE, 'multiple' => self::ROLE_STRUCTURE, 'lang' => self::ROLE_STRUCTURE, 'label' => self::ROLE_CONTENT, 'description' => self::ROLE_CONTENT, 'placeholder' => self::ROLE_CONTENT, 'prepend' => self::ROLE_CONTENT, 'append' => self::ROLE_CONTENT, 'help' => self::ROLE_CONTENT, 'content' => self::ROLE_CONTENT, 'validate' => self::ROLE_SLOT, 'messages' => self::ROLE_MESSAGES, 'design' => self::ROLE_SLOT, 'behavior' => self::ROLE_SLOT, 'options' => self::ROLE_SLOT, 'buttons' => self::ROLE_FORM, 'action' => self::ROLE_FORM, 'dataLimits' => self::ROLE_FORM]
key => role constant
VALIDATE_SUB_KEYS
Sub-keys recognized inside the `validate` slot: the registered rule names, in the order of the validator's default messages. Each value may itself be an expression / condition map, so conditional validation needs no separate key (e.g. required: '.subscribe', email: true). Polymorphic slot (R: G1).
public
array<int, string>
VALIDATE_SUB_KEYS
= ['required', 'email', 'minlength', 'maxlength', 'min', 'max', 'match', 'pattern', 'unique', 'in', 'range', 'rangelength', 'number', 'digits', 'equalTo', 'containedIn', 'typeFrom', 'minFrom', 'maxFrom', 'notEqual', 'date', 'dateISO', 'enddate', 'url', 'accept', 'mincount', 'maxcount', 'step', 'conditionTree', 'reference']
WRAPPER_NODE_KEYS
The closed key set of the wrapper node: its appearance and the declared attributes of the node root.
public
array<int, string>
WRAPPER_NODE_KEYS
= ['class', 'style', 'attributes']
Methods
isCommentKey()
Whether a key is an `x`-prefixed comment key. The meta-schema x-strips these before validating the canonical spec, so a canonical document must not carry one. Rejected by validation; recorded here for the reason.
public
static isCommentKey(string $key) : bool
Parameters
- $key : string
Return values
boolisDependencyTarget()
Whether a key is a structural dependency-bucket target (type/multiple/lang/items).
public
static isDependencyTarget(string $key) : bool
Parameters
- $key : string
Return values
boolisDynamicItemsSource()
Whether a decoded `items` value is a dynamic source descriptor: a non-empty string-keyed map whose keys are ALL dynamic-source keys (model/method/table/relations/api_server/items, the placeholder included).
public
static isDynamicItemsSource(mixed $items) : bool
A value→label map's keys are option values, never the source key set, so
the two never collide in practice. STRUCTURE ONLY — the runtime runs the
model query and calls api_server; this predicate only classifies the
shape (it never resolves the source). Mirrors the JS / Go / Rust
"all keys ∈ source set" discriminator exactly.
Parameters
- $items : mixed
Return values
boolisForbiddenMetaKey()
Whether a key is forbidden in a CRUDUI field at ANY depth.
public
static isForbiddenMetaKey(string $key) : bool
Parameters
- $key : string
Return values
boolisSlot()
Whether a key is a role slot (validate/design/behavior/options).
public
static isSlot(string $key) : bool
Parameters
- $key : string
Return values
boolisTopLevelKey()
Whether a key is a recognized top-level field key.
public
static isTopLevelKey(string $key) : bool
Parameters
- $key : string
Return values
boolisValidContent()
Whether a content value is a valid CRUDUI content shape (SPEC §2 G3): a plain string, a per-language LangMap (string-keyed array of string|null), or null (empty content — identical to absent, no render/validate effect). A LangMap slot may itself be null (empty label for that language).
public
static isValidContent(mixed $value) : bool
Parameters
- $value : mixed
Return values
boolisValidSlotValue()
Whether a slot value is a valid polymorphic form (false | array map | true).
public
static isValidSlotValue(mixed $value) : bool
Any other type (string, int, null) is rejected.
Parameters
- $value : mixed
Return values
boolisValueLabelItemsMap()
Whether an `items` value is a static value→label content map (SPEC §2 G3): a non-empty string-keyed array whose entry values are labels — a string, a LangMap array {ko,en}, or null (empty label). The option VALUE is the KEY (the membership target); the label is display-only and never a membership value, so a LangMap label and a null label have no membership effect.
public
static isValueLabelItemsMap(mixed $items) : bool
Lists (static arrays) and dynamic sources (a model/method/table/
relations/api_server/items source descriptor — where model may be a
name string OR a nested {table, relations, keys} query, api_server is a
runtime HTTP-endpoint fn reference, and items is the placeholder static
choices that coexist) are NOT value→label maps.
Parameters
- $items : mixed
Return values
boolitemsMembershipValues()
The membership values of an `items` source: for a static value→label map the KEYS (the option values — labels are display-only, SPEC §2 G3); for a static list the list itself; otherwise an empty list (a dynamic source has no static membership set). Mirrors the `in`-rule flatten across the four engines.
public
static itemsMembershipValues(mixed $items) : array<int, mixed>
Parameters
- $items : mixed
Return values
array<int, mixed>parse()
Parse a CRUDUI JSON document into an ordered associative array for the analysis API (validate/predicates). PHP associative arrays preserve insertion order, so condition-map and properties declaration order survives. Throws on malformed JSON.
public
static parse(string $json) : array<string, mixed>
Caveat: assoc decoding cannot tell an empty object } from an empty array
[]; for byte-stable round-trip use parseTree()/serializeTree() (or
roundTrips()), which decode to stdClass and keep the } polymorphic form.
Parameters
- $json : string
Return values
array<string, mixed>parseTree()
Parse a CRUDUI JSON document into an object tree (stdClass for objects, list for arrays). This keeps insertion order AND distinguishes the empty-map `{}` polymorphic form from an empty array `[]`. Throws on malformed JSON.
public
static parseTree(string $json) : mixed
Parameters
- $json : string
roleOf()
Role of a top-level key, or null when the key is not top-level.
public
static roleOf(string $key) : string|null
Parameters
- $key : string
Return values
string|nullroundTrips()
Round-trip a canonical JSON document and report whether it is byte-stable.
public
static roundTrips(string $canonicalJson) : bool
Decodes to an object tree so the polymorphic slot forms (false | } | true) and declaration-ordered maps survive intact.
Parameters
- $canonicalJson : string
Return values
boolserialize()
Serialize a decoded CRUDUI field (associative-array form) back to canonical JSON, preserving key order. Empty associative arrays emit as `[]`; when the `{}` polymorphic form matters, use the *Tree round-trip path instead.
public
static serialize(array<string, mixed> $field) : string
Parameters
- $field : array<string, mixed>
Return values
stringserializeTree()
Serialize a CRUDUI object tree back to canonical JSON, preserving key order and the `{}` form. Round-trip invariant: serializeTree(parseTree(canonical)) === canonical for any already-canonical document.
public
static serializeTree(mixed $tree) : string
Parameters
- $tree : mixed
Return values
stringslotValueForm()
The polymorphic form name of a slot value, or null when invalid.
public
static slotValueForm(mixed $value) : string|null
Parameters
- $value : mixed
Return values
string|nullsubKeysOf()
Recognized sub-keys for a slot, or null when the name is not a slot.
public
static subKeysOf(string $slot) : array<int, string>|null
Parameters
- $slot : string
Return values
array<int, string>|nullvalidate()
Deep-validate a decoded CRUDUI field. Returns the list of violations (empty = valid). Enforces: closed top-level key set, polymorphic slot value forms, dependency isolation (a bucket key only under its location), closed bucket key sets (CLOSED_BUCKET_KEYS: design, behavior, multiple, lang, validate and each design node's class/style), `messages` keys limited to the registered rule names, and the global forbidden-key check (root + one level below every slot and bucket; open options/items too). An `x`-prefixed comment key is a violation (x-strip is an upstream step).
public
static validate(array<string, mixed> $field) : array<int, string>
Parameters
- $field : array<string, mixed>
Return values
array<int, string>guardInside()
Append a violation for every forbidden, comment or (in a closed bucket) unknown key found one level below the given container. Open buckets ($allowed null) accept extension but never a forbidden or comment key.
private
static guardInside(string $owner, array<string|int, mixed> $container, array<int, string>|null $allowed, array<int, string> &$violations) : void
Parameters
- $owner : string
- $container : array<string|int, mixed>
- $allowed : array<int, string>|null
- $violations : array<int, string>