Application
Table of Contents
Interfaces
- HasOffset
- Implemented by any exception that can report a byte/character
offset into the source it failed on (lexer/parser errors) -- lets
{@see DXNError::wrap()} preserve that offset without needing to
know about every concrete exception class in the text/binary
pipelines.
- DXNSchemaMaterializingProvider
- A {@see DXNSchemaProvider} that additionally supplies a materializer
-- how to turn an already-schema-validated field map into whatever
decoded shape the provider's own class prefers. Without this, a
provider's schema decodes to the default plain field array.
- DXNSchemaProvider
- Lets a class's *own* library ship a DXN schema for it -- field
names, types, required/optional, closed/forbidden, refinements --
without that library ever taking a runtime dependency on this one.
- DXNValueInterface
- Implemented by every `DXN*` wrapper class -- the PHP analog of the
`dxnType` string tag used for kind dispatch in the JS port, and of
`Dextrin.Value`'s closed type union in the Elixir original. Native
PHP values (`null`, `bool`, `int`, `float`, `string`, and `array`
for `map`/`array`) never implement this; {@see Equality::kindOf()}
handles those directly.
Classes
- BinaryError
- Error type for malformed `.dxnb` input -- the binary-side analog of `Text\ParseError`.
- Decoder
- `.dxnb` decoder -- the mirror image of {@see Encoder}. Envelope
(`magic` `version` `cbor_item`) checked once at the top; everything
below that is a single recursive {@see self::decodeItem()}, keyed
on CBOR major type first, then on tag -- matching `dxn/DXN.md`
§2.2's table row order.
- Encoder
- `.dxnb` encoder -- a recursive walk over CBOR's major types plus a
fixed tag dispatch table, matching `dxn/DXN.md` §2.2's table row
order.
- Tags
- Pure constants -- CBOR tag numbers and bit-layout tables, mirroring
`dxn/DXN.md` §2.3 verbatim. No behavior beyond the flag<->byte
conversions, which exist here (not in the encoder/decoder) as one
shared source of truth for the bit layout, used by both directions.
- Writer
- Low-level CBOR item builders shared by {@see Encoder} -- raw
major-type/head construction, kept separate from the DXN-type
-dispatch logic there.
- Dextrin
- Public API entry point: `.dxn` text and `.dxnb` binary decode/
encode, plus `.dxns` schema-backed struct decoding and encode-time
validation via an optional {@see Registry}.
- DXNError
- One exception type for every `dextrin` failure mode -- text-parse,
printing, and (in later phases) binary/schema failures alike -- so
a caller never has to special-case which pipeline produced it.
- MapBuilder
- Builds a DXN `map` as a bare PHP array -- shared by the text
{@see \JOetjen\Dextrin\Text\Parser} and the binary
{@see \JOetjen\Dextrin\Binary\Decoder}, since both need identical
logic for the exact same reason: `map` is the one collection type
this port decodes to bare native `array` rather than a wrapper
class (see the project's own collections decision), and every
PHP-array-key coercion hazard that follows from that applies
equally regardless of which pipeline produced the raw key/value
pairs.
- Registry
- Extension point for both `struct` and `custom-tag` decoding/encoding.
- Coercion
- Schema-driven type coercion on encode -- a capability with no Elixir
`dextrin` equivalent, ported from `node-dextrin` because the value
model's own `DXN*::fromX()`/`::from*()` conversion helpers already
make the coercion table trivial to build: every class's documented
native-type converter doubles as an encode-time coercion, with no
separate registration.
- Compiled
- The result of compiling one `%schema{}` entry from a `.dxns`
document ({@see Compiler}) -- enough to both validate a decoded
value and convert it both directions between `.dxnb`'s
always-positional wire shape and a named field map.
- Compiler
- Compiles a parsed `.dxns` document (an ordinary decoded DXN value --
a `.dxns` file is valid `.dxn`, no new grammar; what makes it a
*schema* document is purely the shape of the value it parses to: a
`map` from name to type expression -- a bare PHP array in this port)
into {@see Compiled} entries.
- FetchedSchema
- The result of a successful {@see Registry::fetchStructSchema()} --
the compiled schema itself, plus the (possibly-updated) registry a
lazy resolver hit should be threaded forward through, so a
resolved-on-demand schema is only ever resolved once per registry
lineage.
- Field
- One compiled field spec inside a {@see Compiled} schema. `required`
comes from the `?`-suffixed key convention (a field key ending in
`?` is optional; no separate flag exists in `.dxns` itself);
`default`/`description` only ever come from the `%field{...}` escape
hatch, since optionality is already fully covered by the key suffix.
- FileResolver
- An optional convenience resolver ({@see Registry::putResolver()})
resolving `Namespace/Name` references to `.dxns` files on disk.
- Provider
- Compiles and registers one {@see DXNSchemaProvider} into a
{@see Registry}, in order:
- SchemaCompileError
- Thrown by {@see Compiler} for a malformed `.dxns` document.
- SchemaValidationError
- Thrown by {@see Validator}/{@see \JOetjen\Dextrin\Schema} for a value that violates its schema.
- Std
- This library's standard library of named types
(`priv/schema/std.dxns`, ported verbatim from `dextrin`'s own) --
common refinements like `PositiveInteger`/`NonEmptyString`, so a
schema author doesn't redefine them by hand. Built the same way any
consumer's own named types would be -- nothing about them is
special-cased in {@see Compiler}. Deliberately excludes anything
domain-specific (email, phone number, URL-shaped string): what
counts as a valid one is an application decision this library
shouldn't guess at.
- TypeExpr
- Internal, compiled representation of a `.dxns` type expression -- the
12-form vocabulary (`any`, `primitive`, `reference`, `list-of`,
`set-of`, `tuple-of`, `map-of`, `enum`, `one-of`, `all-of`,
`nilable`, `refine`) {@see Compiler} turns a parsed `.dxns` value
into, and what {@see self::matches()} checks a decoded/to-be-encoded
value against. (The 13th `dxn/DXN.md` §4 form, `struct`, isn't a
distinct compiled shape here either -- a `%schema{}` entry compiles
straight to {@see Compiled}, never to a `TypeExpr` a *field* could
hold; `reference` is what a field uses to point at one.)
- Validated
- Internal only -- never appears in a value handed back to
`Dextrin::decode()`/`decodeBinary()`'s caller.
- Validator
- 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.
- Schema
- Public entry point for `.dxns` schema compilation and validation.
- Escapes
- Shared escape decoding/encoding for DXN's `string`/`char`/quoted
-`keyword` bodies (`dxn/DXN.md` §1.1's `escape` production) -- one
implementation so no caller duplicates it.
- Formatter
- Multi-line, indented `.dxn` rendering -- the `pretty: true` option
on `Dextrin::encode()`, built on top of {@see Printer}. Every
non-empty collection gets one entry per line, indented one level
deeper than its container; every empty collection and every scalar
falls through to the single-line printer unchanged (there's nothing
multi-line about `[]` or `42`).
- Lexer
- Hand-written `.dxn` tokenizer, directly off `dxn/DXN.md` §1.1's
lexical grammar. No parser-generator dependency -- the grammar is
small and fully normative.
- LexError
- ParseError
- Parser
- Hand-written recursive-descent `.dxn` parser -- the reverse of
{@see Printer}, built directly on {@see Lexer}'s token stream.
- Printer
- `.dxn` printer -- the reverse of {@see Parser}. Single-line,
minimal-whitespace output: a printer, not a formatter (no
line-wrapping/indentation policy is specified anywhere in
`dxn/DXN.md`) -- {@see Formatter} owns multi-line, human-readable
rendering, built on top of this class.
- Temporal
- ISO 8601 parsing/formatting shared by the `.dxn` text parser and
printer for `date`/`time`/`timestamp`/`datetime`/`duration` -- one
implementation so encode/decode never drift apart. Builds instants
via `DateTimeImmutable` (constructing the wall-clock value directly
in the relevant `DateTimeZone`, then reading back the UTC epoch)
rather than hand-rolling epoch arithmetic -- PHP's own offset
handling is already correct, unlike JS, which needed a hand-written
floor-division helper to get pre-epoch instants right.
- Token
- One lexed token. `start`/`end` are **codepoint indices**, not byte
offsets -- {@see Lexer} operates on the source split into an array
of single-codepoint strings (`preg_split('//u', ...)`) rather than
raw byte-indexed PHP strings, which sidesteps multi-byte UTF-8
sequences entirely (a JS port indexes UTF-16 code units natively;
PHP strings are raw bytes, so this port picks codepoints as its own
natural, consistently-defined unit instead).
- DXNBytes
- DXN `bytes` (`@bytes "..."`, base64 in text) -- raw binary data.
- DXNChar
- DXN `char` (`?a`) -- a single Unicode codepoint, distinct from a
1-codepoint `string`.
- DXNCustomTag
- DXN `custom-tag` (`@tag value`) -- the open extension point, no
decoder registered for `name`.
- DXNDate
- DXN `date` (`~D[YYYY-MM-DD]`) -- a bare calendar date, ISO 8601.
- DXNDateTime
- DXN `datetime` (`@datetime "..."`) -- an offset-aware instant,
non-UTC. Mirrors `.dxnb`'s wire shape directly: an instant plus an
informational offset. Same exact (not lossy) microsecond-precision
conversion as {@see DXNTimestamp}.
- DXNDecimal
- DXN `decimal` (`19.99M`) -- an exact fixed-point number, `sign *
unscaledValue * 10^exponent`. `unscaledValue` is always a numeric
string (bcmath-formatted), independent of the top-level integer
hybrid rule ({@see DXNInteger}) -- exact decimal arithmetic needs
bcmath regardless of magnitude, so there's no native-`int` fast
path to preserve here the way there is for a bare `integer`.
- DXNDuration
- DXN `duration` (`@duration "P1Y2M"`) -- 7 independent, individually
optional fields, not reducible to one scalar. No conversion helper:
PHP's `DateInterval` exists, but normalizes `P1W` into `days = 7`
internally, losing the weeks/days distinction DXN keeps
independent -- not a clean conversion target.
- DXNInteger
- An arbitrary-precision integer, backed by `bcmath` (no `ext-gmp`
assumed available) -- only ever constructed for a magnitude outside
`PHP_INT_MIN..PHP_INT_MAX`. {@see DXNInteger::represent()} is the
canonical entry point embodying the project's hybrid integer rule:
a value that fits stays a native `int` everywhere (ordinary `+`,
`-`, comparisons just work); only genuine overflow reaches this
class, whose own arithmetic is method calls, not operators -- PHP
has no operator overloading for arithmetic.
- DXNKeyword
- DXN `keyword` (`:foo` / `foo:` in key position) -- always wraps in
this port, with no `trusted:`-driven alternate representation the
way the Elixir (real atom) and JS (`Symbol.for`) ports have. PHP
has nothing analogous to a BEAM atom table or `Symbol.for`'s global
registry: every `DXNKeyword` is an ordinary heap-allocated, GC'd
object, so there's no exhaustible interning table decoding
untrusted input could ever threaten in the first place -- this is
a deliberate omission, not a missing feature.
- DXNList
- DXN `list` (`[1 2 3]`) -- wrapped so it stays distinct from `array`
(`@array[...]`), which claims the bare sequential-PHP-array shape
instead (see the project's collections decision: PHP's `array`
only offers one structural fork, sequential-vs-associative, so it
can't safely host two DXN types).
- DXNOrderedMap
- DXN `ordered-map` (`@ordered %{...}`) -- order is part of value
identity. Wrapped rather than a bare associative PHP array (which
already claims `map`, the no-ordering-guarantee sibling) so the two
stay distinguishable on encode; also because a DXN map/ordered-map
key can be any DXN value, not just an `int|string` the way a native
PHP array key must be.
- DXNRational
- DXN `rational` (`22/7`) -- an exact ratio, never auto-reduced (`4/2`
and `2/1` are structurally distinct values, same as the Elixir and
JS ports). `numerator`/`denominator` are numeric strings, same
"always bcmath, regardless of magnitude" rule as {@see DXNDecimal}.
- DXNRegex
- DXN `regex` (`~r/pattern/flags`) -- PCRE-style semantics (matching
Erlang `:re`/Elixir `Regex`, per `dxn/DXN.md` §2.3), which happens
to make PHP's own PCRE-backed `preg_*` a *better* native fit than
JS's `RegExp` had: DXN's `x` (extended) and `r` (ungreedy) flags
both have real PHP modifier equivalents (`r` maps to PHP's
uppercase `U`, distinct from lowercase `u`/unicode) where JS had
neither. Only `f` (firstline) has no PHP preg modifier to reach
for, so {@see self::toPcrePattern()} is guarded on that alone, not
on `x`/`f`/`r` wholesale.
- DXNSet
- DXN `set` (`@{...}`) -- deduplicated at construction, structural
equality, order-independent.
- DXNSortedSet
- DXN `sorted-set` (`@sorted-set @{...}`) -- sorted and deduplicated
at every construction site, same invariant enforced in the Elixir
and JS ports. Cross-type ordering is implementation-defined per
DXN.md; this port's fixed rank order is {@see Equality}'s own.
- DXNStruct
- DXN `struct` (`%Name{...}` / `%Name[...]`), opaque without a
schema. Keeps the keyed/positional shapes distinct rather than
normalizing to one, same as the Elixir and JS ports -- `.dxnb` is
always positional and can't recover field names without a schema.
- DXNSymbol
- DXN `symbol` (a bare identifier, e.g. `foo`) -- an unevaluated
reference, e.g. a type name. Always wraps: PHP has no "evaluated
later" bare-identifier native type, and no interning table whose
exhaustion decoding untrusted input would need to guard against
either way.
- DXNTime
- DXN `time` (`~T[HH:MM:SS(.ffffff)?]`) -- a bare time of day, ISO
8601. No conversion helper: PHP has no native "time of day" type to
convert to/from without a synthetic, misleading date component.
- DXNTimestamp
- DXN `timestamp` (`~U[YYYY-MM-DD HH:MM:SSZ]`) -- a UTC instant.
- DXNTuple
- DXN `tuple` (`{1 2 3}`) -- list-backed, arbitrary length.
- DXNUri
- DXN `uri` (`@uri "..."`) -- a raw string, not parsed, same
round-trip-safety rationale as the Elixir and JS ports.
- DXNUuid
- DXN `uuid` (`@uuid "..."`) -- 16 raw bytes, RFC 4122. PHP strings
are binary-safe natively, so the raw-bytes field itself needs no
`Uint8Array`-style wrapper the way JS needed one.
- Equality
- Shared structural equality/ordering kernel backing every DXN value,
native and wrapped alike -- the PHP analog of the JS port's
`src/values/equality.cjs`. `kindOf()` dispatches on
{@see DXNValueInterface::dxnType()} for wrapped classes, PHP's own
native types for scalars, `array_is_list()` to split `array`/`map`,
and "any other object" (a plain object with public properties,
`stdClass` or otherwise) as `struct` -- the encode-side convenience
described in the project's own value-model mapping.
Enums
- NoDefault
- A payload-free enum used purely as a unique sentinel value ({@see
Field::NO_DEFAULT}), comparable via `!==` -- PHP has no `undefined`/
`Symbol()` to reach for the way JS's own `Field.NO_DEFAULT` does,
and a plain `null` would be ambiguous with a field whose declared
default genuinely *is* `null` (DXN `nil`).
- NotCoercible
- A payload-free enum used as {@see Coercion::tryCoerce()}'s "no
applicable coercion" sentinel, comparable via `!==`/`instanceof` --
a coerced value can legitimately be `null` (a value coerced toward
a `nilable` field, say), so `null` itself can't double as "coercion
didn't apply" the way JS's own `tryCoerce` uses `undefined`.