CRUDUI PHP API

Validator
in package

FinalYes

CRUDUI form validator — SPEC §2 G5→§3→§2 G1. Port of validator-ts/src/validate/validator.ts (Validator), function-for-function.

This is the THIRD pass of the CRUDUI pipeline. It consumes a CRUDUI field model (the validate/design/behavior/options role slots) AFTER the compose pass has expanded $ref/$patch into a single spec (Validate::run runs compose first). It does NOT re-implement the rule semantics or the expression engine — it CALLS the rule instances (Polyspec\Crudui\Validator\Rules) and the CRUDUI expression engine (Polyspec\Crudui\Validator\Expr). The logic here is: (a) reading the validate slot, (b) evaluating a rule value that is an expression OR a condition map (G1 — the condition is the value's expression, never a separate if/when key), (c) skipping every field hidden by design.show, with everything it contains.

The rule instances return a bool and skip nothing on empty values, so this engine applies the empty-value skip of the validation rules centrally and builds the error message itself (DEFAULT_MESSAGES + per-field messages override). Declared rule parameters, including every literal a condition can select, are checked when the validator is constructed (DeclarationCheck); a value a ternary takes from the data is checked when it is selected, before the empty-value skip. Both raise ComposeLoadError at the field's declaration path.

Table of Contents

Constants

DEFAULT_MESSAGES  : mixed = ['required' => 'This field is required.', 'emai...
Default error messages per rule (identical in every runtime). Every registered rule has one, so the keys are the registered rule names. The `pattern` and `match` aliases share one message. {0}/{1} are replaced by the parameters of the rules that have them.
ARRAY_LEVEL_RULES  : mixed = ['required', 'unique', 'mincount', 'maxcount']
Rules that apply to the whole array of a `multiple` field (the rest apply to each element). VALIDATION-RULES §array-level.
SINGLE_CHOICE_TYPES  : mixed = ['select', 'dropdown', 'selectbox', 'choice', '...
Field types whose control holds one value (validation-rules.md, "Evaluation").

Properties

$duplicatesByField  : array<string, array<string, bool>>
For each collection field of the validation in progress (container path, field name and filter), the row keys whose `unique` value an earlier row already holds.
$idsByPath  : array<string, Membership>
For each declaration path that `reference` names, the member set of its ids in the data of the validation in progress (docs/spec/validation-rules.md, "References").
$properties  : array<string, mixed>
$rules  : array<string, RuleInterface>

Methods

__construct()  : mixed
validate()  : ValidationResult
Validate `data` against the composed CRUDUI spec.
applyRules()  : bool
Run the rules in order until one fails, record that error under the path of the value and return whether one failed.
areAllUnique()  : bool
Whether no two canonical keys are identical.
assertSingleChoiceData()  : void
Reject an array or object as the value of a single-choice field, or of a row of a repeated one (rows in sorted key order).
buildMessage()  : string
Build the display message: per-field override → DEFAULT_MESSAGES → fallback, with every {0}/{1} of a rule that has those parameters replaced by their canonical texts.
canonicalKey()  : string
The canonical key of a value for `unique` (docs/spec/validation-rules.md): equal keys mean the same JSON value. Every value carries its type, strings are JSON-escaped, a number of either PHP type is written by its exact value as ECMAScript writes it, lists keep their order, and object members, of a stdClass or an associative array, are sorted by name at every depth.
childProperties()  : array<string, mixed>|null
The child field map of a group (composed, plain object), or null.
collectHidden()  : void
Append the data paths of the fields whose design.show resolves to false, in declaration order, each field of a group row under the row's key and the fields inside a hidden field included (validation-rules.md, "Evaluation"). Data of another shape is read as missing.
fieldMessages()  : array<string, string>|null
Read the per-field custom messages (the `messages` map).
fieldsOf()  : Generator<string, array<string, mixed>>
The field declarations of a properties map in declaration order, each as an array under its name; a member that is not an object is not a field.
getFieldName()  : string
getValueByPath()  : mixed
Read the value at a concrete path (JS getValueByPath). Missing → null.
isConditionExpression()  : bool
Whether a string looks like a condition expression (JS isConditionExpression): starts with `.`, `identifier.`, contains a comparison/logical/in operator, or contains a ternary `?...:`.
isSingleChoice()  : bool
Whether a field is a single-choice field: a single-choice type without `lang`.
itemPassesCondition()  : bool
Evaluate a filter condition for an item, as if validating the same field on that item (JS itemPassesCondition).
makeError()  : array<string, mixed>
Assemble an error record matching the shared fixture shape.
normalizeValidateSlot()  : array<string, mixed>|null
Normalize a polymorphic `validate` slot to a rule map. false/true/null → no rules; a list/empty array → no rules; an assoc array → the rule map itself.
pathKey()  : string
A per-field key so the first error per field wins (later errors on the same path are ignored). The dot-path is unique per field instance.
pathToString()  : string
registerRules()  : void
Register the rule instances; `pattern` and `match` share one implementation.
resolveRuleValue()  : mixed
Resolve a rule value to the effective param (G1).
rulesAt()  : array<string, mixed>
The rules that judge the collection of a `multiple` field as a whole (`$arrayLevel`), or the rules that judge each element, in declaration order.
runGroupSource()  : bool
A field whose `items` is a group source runs an implicit `in` check against the choices that the source gives for the whole form data, after the implicit `number` check and before the declared rules (validation-rules.md, "Values"). Returns true when it pushed an error (caller stops the field).
runImplicitNumber()  : bool
`type:number` runs an implicit `number` rule before everything else when no explicit `number` rule is declared (VALIDATION-RULES §2 — reported as rule `number`). Returns true when it pushed an error (caller stops the field).
runRule()  : string|null
Run one rule: evaluate its (possibly conditional) value to an effective param, check a selected param, apply the central empty-skip, then call the rule instance and build the message. Returns the error message or null.
stringify()  : string
Placeholder text: the canonical text of a scalar; false, null, arrays and objects are empty.
validateFieldRules()  : void
All rules for a single (scalar or group-as-whole) field, or, with `$elementLevel`, the element-level rules of one element of a `multiple` field (no array-level rule).
validateMultipleFieldRules()  : void
Collection rules and per-row rules for a repeated scalar field.
validateProperties()  : void
validateUnique()  : bool
Unique rule (two modes). A string param that is a condition expression acts as a per-item filter (only items passing the condition participate); a non-condition string param is a field name within array items.

Constants

DEFAULT_MESSAGES

Default error messages per rule (identical in every runtime). Every registered rule has one, so the keys are the registered rule names. The `pattern` and `match` aliases share one message. {0}/{1} are replaced by the parameters of the rules that have them.

public mixed DEFAULT_MESSAGES = ['required' => 'This field is required.', 'email' => 'Please enter a valid email address.', 'minlength' => 'Please enter at least {0} characters.', 'maxlength' => 'Please enter no more than {0} characters.', 'min' => 'Please enter a value greater than or equal to {0}.', 'max' => 'Please enter a value less than or equal to {0}.', 'match' => 'Please enter a valid format.', 'pattern' => 'Please enter a valid format.', 'unique' => 'Values must be unique.', 'in' => 'Please select a valid option.', 'range' => 'Please enter a value between {0} and {1}.', 'rangelength' => 'Please enter a value between {0} and {1} characters.', 'number' => 'Please enter a valid number.', 'digits' => 'Please enter only digits.', 'equalTo' => 'Please enter the same value again.', 'containedIn' => 'Please enter a value that the referenced field contains.', 'typeFrom' => 'Please enter a value of the declared type.', 'minFrom' => 'Please enter a value at or above the declared minimum.', 'maxFrom' => 'Please enter a value at or below the declared maximum.', 'notEqual' => 'Please enter a different value.', 'date' => 'Please enter a valid date.', 'dateISO' => 'Please enter a valid date in ISO format (YYYY-MM-DD).', 'enddate' => 'End date must be after the start date.', 'url' => 'Please enter a valid URL.', 'accept' => 'Please upload a file with a valid format.', 'mincount' => 'Please select at least {0} items.', 'maxcount' => 'Please select no more than {0} items.', 'step' => 'Please enter a value that is a multiple of {0}.', 'conditionTree' => 'Please enter a valid condition.', 'reference' => 'Please select an existing value.']

ARRAY_LEVEL_RULES

Rules that apply to the whole array of a `multiple` field (the rest apply to each element). VALIDATION-RULES §array-level.

private mixed ARRAY_LEVEL_RULES = ['required', 'unique', 'mincount', 'maxcount']

SINGLE_CHOICE_TYPES

Field types whose control holds one value (validation-rules.md, "Evaluation").

private mixed SINGLE_CHOICE_TYPES = ['select', 'dropdown', 'selectbox', 'choice', 'radio']

Properties

$duplicatesByField

For each collection field of the validation in progress (container path, field name and filter), the row keys whose `unique` value an earlier row already holds.

private array<string, array<string, bool>> $duplicatesByField = []

$idsByPath

For each declaration path that `reference` names, the member set of its ids in the data of the validation in progress (docs/spec/validation-rules.md, "References").

private array<string, Membership> $idsByPath = []

$properties read-only

private array<string, mixed> $properties

the composed root field map (composition-free)

Methods

__construct()

public __construct(array<string, mixed> $composedSpec) : mixed
Parameters
$composedSpec : array<string, mixed>

a composed CRUDUI root spec — a group with properties (already free of $ref/$patch and forbidden keys).

Tags
throws
ComposeLoadError

when a declared rule name or parameter is invalid

applyRules()

Run the rules in order until one fails, record that error under the path of the value and return whether one failed.

private applyRules(array<string, mixed> $rules, mixed $value, array<int, string> $path, array<string|int, mixed> $rowKeys, array<int, string> $declarationPath, array<string, string>|null $messages, array<string, mixed> $allData, array<int, array<string, mixed>> &$errors) : bool
Parameters
$rules : array<string, mixed>

the rules to run, in declaration order, each with its declared value

$value : mixed
$path : array<int, string>

the path of the value that the rules check and that keys the error

$rowKeys : array<string|int, mixed>
$declarationPath : array<int, string>

the path of the declaration in the specification, for the load errors

$messages : array<string, string>|null

the custom messages of the field

$allData : array<string, mixed>

the whole form data, which the conditions of a rule read

$errors : array<int, array<string, mixed>>

the errors found so far, keyed by path; receives the first error

Return values
bool

areAllUnique()

Whether no two canonical keys are identical.

private areAllUnique(array<int, mixed> $values) : bool
Parameters
$values : array<int, mixed>
Return values
bool

assertSingleChoiceData()

Reject an array or object as the value of a single-choice field, or of a row of a repeated one (rows in sorted key order).

private static assertSingleChoiceData(mixed $value, array<int, string> $path, bool $repeated) : void
Parameters
$value : mixed
$path : array<int, string>
$repeated : bool
Tags
throws
FormInputError

for the first value that is not a single value

buildMessage()

Build the display message: per-field override → DEFAULT_MESSAGES → fallback, with every {0}/{1} of a rule that has those parameters replaced by their canonical texts.

private buildMessage(string $ruleName, mixed $param, array<string, mixed>|null $messages) : string
Parameters
$ruleName : string
$param : mixed
$messages : array<string, mixed>|null
Return values
string

canonicalKey()

The canonical key of a value for `unique` (docs/spec/validation-rules.md): equal keys mean the same JSON value. Every value carries its type, strings are JSON-escaped, a number of either PHP type is written by its exact value as ECMAScript writes it, lists keep their order, and object members, of a stdClass or an associative array, are sorted by name at every depth.

private canonicalKey(mixed $value) : string
Parameters
$value : mixed
Return values
string

childProperties()

The child field map of a group (composed, plain object), or null.

private childProperties(array<string|int, mixed> $field) : array<string, mixed>|null
Parameters
$field : array<string|int, mixed>
Return values
array<string, mixed>|null

collectHidden()

Append the data paths of the fields whose design.show resolves to false, in declaration order, each field of a group row under the row's key and the fields inside a hidden field included (validation-rules.md, "Evaluation"). Data of another shape is read as missing.

private collectHidden(array<string, mixed> $properties, array<string|int, mixed> $data, array<int, string> $currentPath, array<int, int> $rowKeys, array<string, mixed> $allData, array<int, string> &$hidden) : void
Parameters
$properties : array<string, mixed>
$data : array<string|int, mixed>
$currentPath : array<int, string>
$rowKeys : array<int, int>

positions of the row keys in $currentPath

$allData : array<string, mixed>
$hidden : array<int, string>

fieldMessages()

Read the per-field custom messages (the `messages` map).

private fieldMessages(array<string|int, mixed> $field) : array<string, string>|null
Parameters
$field : array<string|int, mixed>
Return values
array<string, string>|null

fieldsOf()

The field declarations of a properties map in declaration order, each as an array under its name; a member that is not an object is not a field.

private fieldsOf(array<string|int, mixed> $properties) : Generator<string, array<string, mixed>>
Parameters
$properties : array<string|int, mixed>
Return values
Generator<string, array<string, mixed>>

getFieldName()

private getFieldName(array<int, string> $path) : string
Parameters
$path : array<int, string>
Return values
string

getValueByPath()

Read the value at a concrete path (JS getValueByPath). Missing → null.

private getValueByPath(array<string, mixed> $data, array<int, string> $path) : mixed
Parameters
$data : array<string, mixed>
$path : array<int, string>

isConditionExpression()

Whether a string looks like a condition expression (JS isConditionExpression): starts with `.`, `identifier.`, contains a comparison/logical/in operator, or contains a ternary `?...:`.

private isConditionExpression(string $value) : bool
Parameters
$value : string
Return values
bool

isSingleChoice()

Whether a field is a single-choice field: a single-choice type without `lang`.

private static isSingleChoice(array<string|int, mixed> $field) : bool
Parameters
$field : array<string|int, mixed>
Return values
bool

itemPassesCondition()

Evaluate a filter condition for an item, as if validating the same field on that item (JS itemPassesCondition).

private itemPassesCondition(string $condition, array<int, string> $itemFieldPath, array<string|int, mixed> $rowKeys, array<string, mixed> $allData) : bool
Parameters
$condition : string
$itemFieldPath : array<int, string>
$rowKeys : array<string|int, mixed>
$allData : array<string, mixed>
Return values
bool

makeError()

Assemble an error record matching the shared fixture shape.

private makeError(array<int, string> $path, string $rule, string $message, mixed $value) : array<string, mixed>
Parameters
$path : array<int, string>
$rule : string
$message : string
$value : mixed
Return values
array<string, mixed>

normalizeValidateSlot()

Normalize a polymorphic `validate` slot to a rule map. false/true/null → no rules; a list/empty array → no rules; an assoc array → the rule map itself.

private normalizeValidateSlot(mixed $slot) : array<string, mixed>|null
Parameters
$slot : mixed
Return values
array<string, mixed>|null

pathKey()

A per-field key so the first error per field wins (later errors on the same path are ignored). The dot-path is unique per field instance.

private pathKey(array<int, string> $path) : string
Parameters
$path : array<int, string>
Return values
string

pathToString()

private pathToString(array<int, string> $path) : string
Parameters
$path : array<int, string>
Return values
string

registerRules()

Register the rule instances; `pattern` and `match` share one implementation.

private registerRules() : void

resolveRuleValue()

Resolve a rule value to the effective param (G1).

private resolveRuleValue(string $ruleName, mixed $ruleValue, array<int, string> $path, array<string|int, mixed> $rowKeys, array<string, mixed> $allData) : mixed

Path-reference, literal-param, regex and membership rules keep their parameter verbatim; any other rule value resolves as a conditional parameter (ConditionalValue).

Parameters
$ruleName : string
$ruleValue : mixed
$path : array<int, string>
$rowKeys : array<string|int, mixed>
$allData : array<string, mixed>

rulesAt()

The rules that judge the collection of a `multiple` field as a whole (`$arrayLevel`), or the rules that judge each element, in declaration order.

private static rulesAt(array<string, mixed> $rules, bool $arrayLevel) : array<string, mixed>
Parameters
$rules : array<string, mixed>
$arrayLevel : bool
Return values
array<string, mixed>

runGroupSource()

A field whose `items` is a group source runs an implicit `in` check against the choices that the source gives for the whole form data, after the implicit `number` check and before the declared rules (validation-rules.md, "Values"). Returns true when it pushed an error (caller stops the field).

private runGroupSource(array<string, mixed> $field, mixed $value, array<int, string> $path, array<string|int, mixed> $rowKeys, array<int, string> $declarationPath, array<string, string>|null $messages, array<string, mixed> $allData, array<int, array<string, mixed>> &$errors) : bool
Parameters
$field : array<string, mixed>

the field declaration

$value : mixed
$path : array<int, string>

the path of the value that is checked

$rowKeys : array<string|int, mixed>
$declarationPath : array<int, string>

the path of the declaration of the field in the specification

$messages : array<string, string>|null

the custom messages of the field

$allData : array<string, mixed>

the whole form data, which the choices are read from

$errors : array<int, array<string, mixed>>

the errors found so far, keyed by path; receives the new one

Return values
bool

runImplicitNumber()

`type:number` runs an implicit `number` rule before everything else when no explicit `number` rule is declared (VALIDATION-RULES §2 — reported as rule `number`). Returns true when it pushed an error (caller stops the field).

private runImplicitNumber(array<string|int, mixed> $field, array<string, mixed>|null $rules, mixed $value, array<int, string> $path, array<string|int, mixed> $rowKeys, array<int, string> $declarationPath, array<string, string>|null $messages, array<string, mixed> $allData, array<int, array<string, mixed>> &$errors) : bool
Parameters
$field : array<string|int, mixed>
$rules : array<string, mixed>|null

the validate slot of the field, which says whether number is declared

$value : mixed
$path : array<int, string>

the path of the value that is checked

$rowKeys : array<string|int, mixed>
$declarationPath : array<int, string>

the path of the declaration of the field in the specification

$messages : array<string, string>|null

the custom messages of the field

$allData : array<string, mixed>

the whole form data, which the conditions of the rule read

$errors : array<int, array<string, mixed>>

the errors found so far, keyed by path; receives the new one

Return values
bool

runRule()

Run one rule: evaluate its (possibly conditional) value to an effective param, check a selected param, apply the central empty-skip, then call the rule instance and build the message. Returns the error message or null.

private runRule(string $ruleName, mixed $ruleValue, mixed $value, array<int, string> $path, array<string|int, mixed> $rowKeys, array<int, string> $declarationPath, array<string, string>|null $messages, array<string, mixed> $allData) : string|null
Parameters
$ruleName : string
$ruleValue : mixed
$value : mixed
$path : array<int, string>
$rowKeys : array<string|int, mixed>
$declarationPath : array<int, string>
$messages : array<string, string>|null
$allData : array<string, mixed>
Tags
throws
ComposeLoadError

when a selected param is invalid

Return values
string|null

stringify()

Placeholder text: the canonical text of a scalar; false, null, arrays and objects are empty.

private stringify(mixed $value) : string
Parameters
$value : mixed
Return values
string

validateFieldRules()

All rules for a single (scalar or group-as-whole) field, or, with `$elementLevel`, the element-level rules of one element of a `multiple` field (no array-level rule).

private validateFieldRules(array<string|int, mixed> $field, mixed $value, array<int, string> $fieldPath, array<string|int, mixed> $rowKeys, array<int, string> $declarationPath, array<string, mixed> $allData, array<int, array<string, mixed>> &$errors[, bool $elementLevel = false ]) : void
Parameters
$field : array<string|int, mixed>
$value : mixed
$fieldPath : array<int, string>
$rowKeys : array<string|int, mixed>
$declarationPath : array<int, string>
$allData : array<string, mixed>
$errors : array<int, array<string, mixed>>
$elementLevel : bool = false

validateMultipleFieldRules()

Collection rules and per-row rules for a repeated scalar field.

private validateMultipleFieldRules(array<string|int, mixed> $field, array<string, mixed>|stdClass $values, array<int, string> $fieldPath, array<string|int, mixed> $rowKeys, array<int, string> $declarationPath, array<string, mixed> $allData, array<int, array<string, mixed>> &$errors) : void
Parameters
$field : array<string|int, mixed>
$values : array<string, mixed>|stdClass

keyed rows

$fieldPath : array<int, string>
$rowKeys : array<string|int, mixed>
$declarationPath : array<int, string>
$allData : array<string, mixed>
$errors : array<int, array<string, mixed>>

validateProperties()

private validateProperties(array<string, mixed> $properties, array<string, mixed> $data, array<int, string> $currentPath, array<string|int, mixed> $rowKeys, array<int, string> $declarationPath, array<string, mixed> $allData, array<int, array<string, mixed>> &$errors[, bool $hidden = false ]) : void
Parameters
$properties : array<string, mixed>
$data : array<string, mixed>
$currentPath : array<int, string>
$rowKeys : array<string|int, mixed>
$declarationPath : array<int, string>

$currentPath without row keys

$allData : array<string, mixed>
$errors : array<int, array<string, mixed>>
$hidden : bool = false

whether an enclosing field is hidden: its descendants' data shape is still checked, their rules are not

validateUnique()

Unique rule (two modes). A string param that is a condition expression acts as a per-item filter (only items passing the condition participate); a non-condition string param is a field name within array items.

private validateUnique(mixed $value, mixed $ruleParam, array<int, string> $pathSegments, array<string|int, mixed> $rowKeys, array<string, mixed> $allData) : bool
Parameters
$value : mixed
$ruleParam : mixed
$pathSegments : array<int, string>
$rowKeys : array<string|int, mixed>
$allData : array<string, mixed>
Return values
bool
On this page

Search results