CRUDUI PHP API

FieldSpec
in package

FinalYes

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 options slot; 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 true expression (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 options slot (scalar target's dedicated slot).
  • multiple -> directly under the multiple structural key.
  • lang -> directly under the lang structural 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
bool

isDependencyTarget()

Whether a key is a structural dependency-bucket target (type/multiple/lang/items).

public static isDependencyTarget(string $key) : bool
Parameters
$key : string
Return values
bool

isDynamicItemsSource()

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
bool

isForbiddenMetaKey()

Whether a key is forbidden in a CRUDUI field at ANY depth.

public static isForbiddenMetaKey(string $key) : bool
Parameters
$key : string
Return values
bool

isSlot()

Whether a key is a role slot (validate/design/behavior/options).

public static isSlot(string $key) : bool
Parameters
$key : string
Return values
bool

isTopLevelKey()

Whether a key is a recognized top-level field key.

public static isTopLevelKey(string $key) : bool
Parameters
$key : string
Return values
bool

isValidContent()

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
bool

isValidSlotValue()

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
bool

isValueLabelItemsMap()

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
bool

itemsMembershipValues()

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|null

roundTrips()

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
bool

serialize()

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
string

serializeTree()

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
string

slotValueForm()

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|null

subKeysOf()

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>|null

validate()

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>
On this page

Search results