php-dextrin

Validator
in package

FinalYes

Checks a value's fields against a {@see Compiled} schema (required/closed/forbidden/refine) -- shared by decode ({@see self::materialize()}, which also produces a materialized result) and encode-time validation ({@see self::validateForEncode()}/{@see self::validateTreeForEncode()}). Both directions reuse the exact same `resolveFields()`/`TypeExpr::matches()` field-checking; encode's own values are wrapped in the same {@see Validated} marker decode already uses, via `wrapAndCheck()`, so there's one type-checking implementation, not two that could drift.

One genuine difference from a decode-only validator: encode here can transform a value, not just answer ok/error -- resolveFields's $coerce option (see Coercion) may replace a field's value with its coerced form (a plain float becoming a DXNRational for a :rational field, say), and that coerced value has to actually reach the printer/binary encoder, not just pass a check and be discarded. wrapAndCheck() therefore rebuilds every container it walks from its (possibly-coerced) children rather than returning the value unchanged.

Enforcement is decode-time and fail-fast, with no lenient escape hatch to get the materialized value anyway -- a violation is always a thrown SchemaValidationError.

Table of Contents

Methods

materialize()  : Validated
Validates and materializes `$struct`'s fields against `$compiled`, using `$materializer` if given. On success, the result is wrapped in a {@see Validated} (name: `$compiled->name`) -- internal bookkeeping so an *outer* schema's `{form: 'reference', name}` field can verify this value's origin even after materialization; callers that aren't another schema check must call {@see Validated::strip()} on the final result.
validateForEncode()  : Validated
Checks `$value` -- ordinary PHP data about to be encoded -- against `$compiled`. Returns the checked (and, if `$coerce`, possibly coerced) value, {@see Validated}-wrapped.
validateTreeForEncode()  : mixed
Walks `$value` looking for every {@see DXNStruct} or registered application class instance anywhere in the tree and checks each one against its own schema, if the registry has one -- regardless of what any enclosing field's declared type is, recursing fully into whatever it finds. Returns the (possibly-coerced) tree.
__construct()  : mixed
checkClosed()  : void
checkCompiled()  : Validated
checkForbidden()  : void
checkType()  : mixed
markFieldNameKeys()  : array<int|string, mixed>
The default (no custom materializer registered) decode result is a bare associative array keyed by field *name* -- this port's own representation of DXN `map`, same as Elixir's own default (`Atom.to_string/1` on each field name) and node-dextrin's. Each key is marked via {@see MapBuilder::markStringKey()} so it round-trips as a genuine `string` key on re-encode, not a `keyword` -- matching what a schema-free reader of the same document in either of those other two languages would decode a struct with no registered materializer into. Only this no-materializer path needs it: a *registered* materializer closure (like the one above) receives `$resolved` directly and expects ordinary, unmarked `$resolved['name']` access.
rebuildChecked()  : mixed
Rebuilds `$value` in its *original* shape (a `DXNStruct`, or a real application class instance) with `$resolved`'s checked -- and, if coercion applied, coerced -- field values written back in. Without this, `checkCompiled()` would hand the printer/binary encoder a bare field map instead of something it actually knows how to encode (a plain array has no name of its own to recover `%Name{...}` wire syntax from) -- the encode-time analog of why `materialize()`'s decode-side default already produces a real, named result rather than discarding shape information.
resolveField()  : mixed
resolveFields()  : array<string, mixed>
runRefineFn()  : void
schemaNameFor()  : Compiled}|null
toNameMap()  : array<string, mixed>
toNameMapForEncode()  : array<string, mixed>
wrapAndCheck()  : mixed
`wrapAndCheck()` is the recursive core: for any value that's a named struct with a registered schema, check it (`checkCompiled()`) and wrap the result in `Validated`; otherwise recurse into whatever container it is, reconstructing it from the (possibly-coerced, possibly-wrapped) results so a wrapped/coerced grandchild survives all the way up to whichever ancestor's field type actually needs to see it.
wrapChildren()  : mixed
wrapGiven()  : array<string, mixed>

Methods

materialize()

Validates and materializes `$struct`'s fields against `$compiled`, using `$materializer` if given. On success, the result is wrapped in a {@see Validated} (name: `$compiled->name`) -- internal bookkeeping so an *outer* schema's `{form: 'reference', name}` field can verify this value's origin even after materialization; callers that aren't another schema check must call {@see Validated::strip()} on the final result.

public static materialize(Compiled $compiled, DXNStruct $struct, callable(array<string, mixed>): mixed|null $materializer, Registry $registry) : Validated
Parameters
$compiled : Compiled
$struct : DXNStruct
$materializer : callable(array<string, mixed>): mixed|null
$registry : Registry
Return values
Validated

validateForEncode()

Checks `$value` -- ordinary PHP data about to be encoded -- against `$compiled`. Returns the checked (and, if `$coerce`, possibly coerced) value, {@see Validated}-wrapped.

public static validateForEncode(Compiled $compiled, mixed $value, Registry $registry[, bool $coerce = true ]) : Validated
Parameters
$compiled : Compiled
$value : mixed
$registry : Registry
$coerce : bool = true
Return values
Validated

validateTreeForEncode()

Walks `$value` looking for every {@see DXNStruct} or registered application class instance anywhere in the tree and checks each one against its own schema, if the registry has one -- regardless of what any enclosing field's declared type is, recursing fully into whatever it finds. Returns the (possibly-coerced) tree.

public static validateTreeForEncode(mixed $value, Registry $registry[, bool $coerce = true ]) : mixed
Parameters
$value : mixed
$registry : Registry
$coerce : bool = true

checkClosed()

private static checkClosed(Compiled $compiled, array<string, mixed> $given) : void
Parameters
$compiled : Compiled
$given : array<string, mixed>

checkForbidden()

private static checkForbidden(Compiled $compiled, array<string, mixed> $given) : void
Parameters
$compiled : Compiled
$given : array<string, mixed>

checkType()

private static checkType(string $name, mixed $value, TypeExpr $type, Registry $registry, bool $coerce) : mixed
Parameters
$name : string
$value : mixed
$type : TypeExpr
$registry : Registry
$coerce : bool

markFieldNameKeys()

The default (no custom materializer registered) decode result is a bare associative array keyed by field *name* -- this port's own representation of DXN `map`, same as Elixir's own default (`Atom.to_string/1` on each field name) and node-dextrin's. Each key is marked via {@see MapBuilder::markStringKey()} so it round-trips as a genuine `string` key on re-encode, not a `keyword` -- matching what a schema-free reader of the same document in either of those other two languages would decode a struct with no registered materializer into. Only this no-materializer path needs it: a *registered* materializer closure (like the one above) receives `$resolved` directly and expects ordinary, unmarked `$resolved['name']` access.

private static markFieldNameKeys(array<string, mixed> $resolved) : array<int|string, mixed>
Parameters
$resolved : array<string, mixed>
Return values
array<int|string, mixed>

rebuildChecked()

Rebuilds `$value` in its *original* shape (a `DXNStruct`, or a real application class instance) with `$resolved`'s checked -- and, if coercion applied, coerced -- field values written back in. Without this, `checkCompiled()` would hand the printer/binary encoder a bare field map instead of something it actually knows how to encode (a plain array has no name of its own to recover `%Name{...}` wire syntax from) -- the encode-time analog of why `materialize()`'s decode-side default already produces a real, named result rather than discarding shape information.

private static rebuildChecked(mixed $value, Compiled $compiled, array<string, mixed> $resolved) : mixed

A value that was neither of those (a bare PHP array, this port's own map) is promoted to a keyed DXNStruct under $compiled's own name: having just checked it against that specific schema, the natural encoded form is %Name{...}, not a bare, nameless %{...} map with no record that a check ever happened.

PHP-specific limitation: rebuilding a real application class instance writes coerced field values back onto a clone via direct property assignment, which throws if that property is readonly (PHP has no generic "reconstruct with these fields changed" for readonly properties the way JS's always-mutable objects allow) -- a genuinely PHP-specific constraint the JS port doesn't share, not something this port works around.

Parameters
$value : mixed
$compiled : Compiled
$resolved : array<string, mixed>

resolveField()

private static resolveField(Field $field, array<string, mixed> $given, Registry $registry, bool $coerce) : mixed
Parameters
$field : Field
$given : array<string, mixed>
$registry : Registry
$coerce : bool

resolveFields()

private static resolveFields(Compiled $compiled, array<string, mixed> $given, Registry $registry, bool $coerce) : array<string, mixed>
Parameters
$compiled : Compiled
$given : array<string, mixed>
$registry : Registry
$coerce : bool
Return values
array<string, mixed>

runRefineFn()

private static runRefineFn(Compiled $compiled, array<string, mixed> $resolved) : void
Parameters
$compiled : Compiled
$resolved : array<string, mixed>

schemaNameFor()

private static schemaNameFor(mixed $value, Registry $registry) : Compiled}|null
Parameters
$value : mixed
$registry : Registry
Return values
Compiled}|null

toNameMapForEncode()

private static toNameMapForEncode(Compiled $compiled, mixed $value) : array<string, mixed>
Parameters
$compiled : Compiled
$value : mixed
Return values
array<string, mixed>

wrapAndCheck()

`wrapAndCheck()` is the recursive core: for any value that's a named struct with a registered schema, check it (`checkCompiled()`) and wrap the result in `Validated`; otherwise recurse into whatever container it is, reconstructing it from the (possibly-coerced, possibly-wrapped) results so a wrapped/coerced grandchild survives all the way up to whichever ancestor's field type actually needs to see it.

private static wrapAndCheck(mixed $value, Registry $registry, bool $coerce) : mixed
Parameters
$value : mixed
$registry : Registry
$coerce : bool

wrapChildren()

private static wrapChildren(mixed $value, Registry $registry, bool $coerce) : mixed
Parameters
$value : mixed
$registry : Registry
$coerce : bool

wrapGiven()

private static wrapGiven(array<string, mixed> $given, Registry $registry, bool $coerce) : array<string, mixed>
Parameters
$given : array<string, mixed>
$registry : Registry
$coerce : bool
Return values
array<string, mixed>
On this page

Search results