Validator
in package
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
ValidatedvalidateForEncode()
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
Return values
ValidatedvalidateTreeForEncode()
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
__construct()
private
__construct() : mixed
checkClosed()
private
static checkClosed(Compiled $compiled, array<string, mixed> $given) : void
Parameters
- $compiled : Compiled
- $given : array<string, mixed>
checkCompiled()
private
static checkCompiled(Compiled $compiled, mixed $value, Registry $registry, bool $coerce) : Validated
Parameters
Return values
ValidatedcheckForbidden()
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
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
resolveFields()
private
static resolveFields(Compiled $compiled, array<string, mixed> $given, Registry $registry, bool $coerce) : array<string, mixed>
Parameters
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}|nulltoNameMap()
private
static toNameMap(Compiled $compiled, DXNStruct $struct) : array<string, mixed>
Parameters
Return values
array<string, mixed>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