Application
Table of Contents
Interfaces
- HasCategory
- Implemented by any exception that knows its DXN.md §5 category --
`syntax`, `binary`, `encode/unsupported`, ... -- so
{@see DXNError::wrap()} can carry it across without knowing every
concrete exception class, the way {@see HasOffset} carries an offset.
- 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 the schema's
materializer -- called for every decoded struct of the schema,
whatever its version, with the schema's name, the version the fields
were read with and the checked fields; it answers the provider's own
class at the latest version. Without this, a provider's schema decodes
to the generic {@see \JOetjen\Dextrin\Value\DXNStruct}.
- DXNSchemaProvider
- Lets a class's *own* library ship a DXN schema for it -- field
names, types, required/optional, 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.
- FloatWidth
- CBOR's half- and single-precision floats (RFC 8949 §3.3, Appendix D),
which `.dxnb` writes when they hold a value exactly -- the smallest
representation (DXN.md §2.2, Jan, 2026-10-20). Named for what it
chooses, as `Float` is a name PHP keeps for itself.
- 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.
- Canonical
- The canonical form of a value (DXN.md §1.5): one `.dxn` rendering,
fixed down to the byte, so values from different implementations can
be compared as text.
- Conformance
- This implementation's answers to the Dextrin Conformance Suite.
- 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}.
- DocumentError
- Why a {@see \JOetjen\Dextrin\Document} edit was refused: the path led
nowhere (`document/not-found`), to no collection
(`document/not-a-collection`), to a key already there
(`document/exists`), or an update could not tell which edit was meant
without risking a comment (`document/ambiguous`).
- Formatter
- A document laid out the way the pretty printer lays out a value --
every non-empty collection over lines, one entry per line, indented --
with every comment kept beside the entry it belongs to, mirroring
`Dextrin.Document.Formatter`.
- Document
- A `.dxn` document with everything it holds: its value, and every
comment, blank line and comma around it (`design/comment-retention.md`
in `dextrin`), mirroring `Dextrin.Document`.
- 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.
- Format
- The three forms' names on the wire and on disk, and a cheap test of
whether a body is worth decoding as one -- DXN.md §6, mirroring
`Dextrin.Format`, so every implementation answers the same.
- KeyNotAllowed
- A map key that DXN.md §1.6 does not allow, with its `key/not-allowed`
category.
- MapBuilder
- Builds a decoded DXN `map` -- shared by the text
{@see \JOetjen\Dextrin\Text\Parser} and the binary
{@see \JOetjen\Dextrin\Binary\Decoder}, which need identical logic.
- MapKey
- Which values may be map keys (DXN.md §1.6): a string, keyword, integer,
uuid, objectid, date, bytes, symbol or char. Anything else is refused as
`key/not-allowed` -- in text, in binary and when encoding.
- Registry
- Extension point for both `struct` and `custom-tag` decoding/encoding.
- Absent
- What {@see Validator} answers for an optional field that was not given
and has no default: absent, which DXN tells apart from a field written
`nil`. One instance, compared by class.
- Compat
- The version discipline check (DXNS.md §8), mirroring
`Dextrin.Schema.Compat`: compares the schemas of a previous release
with the current ones.
- Compiled
- The result of compiling one `%schema(N){}` 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.
- Context
- What a check knows besides the type and the value: the registry
(`null` when there is none -- strict is then off), and the fields of
the nearest enclosing struct or shape by name, which a `{:case}` reads
its sibling from (`null` outside any).
- Docs
- HTML documentation for a directory of `.dxns` schema documents -- the
port of `Dextrin.Schema.Docs`, writing the same site byte for byte (Jan,
2026-10-20): a sidebar tree of every namespace and definition, the index
and every namespace's page in one design, the pages a directory tree with
an `index.html` per namespace, every reference a link, every version
shown. It reads the documents' source, not their compiled schemas.
- 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
- A resolver ({@see Registry::putResolver()}) finding a struct's schema
in `.dxns` files on disk, by the convention DXNS.md §6 states for
every implementation -- so one set of schema files serves them all.
- Layout
- The names a `.dxns` file's entries have, from where the file lies under
its root (DXNS.md §6) -- one rule for every reader of a tree: the docs
generator, {@see Tree} and the file resolver; the port of
`Dextrin.Schema.Layout`.
- Particle
- One of a `{:list-items}`'s items (DXNS.md §1a): a type for exactly one
item, or a repeat of a group of them, `$max` `null` when there is no
upper bound.
- Provider
- Compiles and registers one {@see DXNSchemaProvider} into a
{@see Registry}, in order:
- Reader
- Which schema a decoded struct is read with, given the version it was
written with and the versions loaded (DXNS.md §8). Shared by the text
parser and the binary decoder, so both answer every case the same way
-- mirrors `Dextrin.Schema.Reader`:
- SchemaCompileError
- Thrown by {@see Compiler} for a malformed `.dxns` document, with the `schema/compile` category.
- SchemaValidationError
- Thrown by {@see Validator}/{@see \JOetjen\Dextrin\Schema} for a value
that violates its schema, with the violation's DXN.md §5 category
(`schema/missing-field`, ...), which every channel passes on
unchanged. Null only where a caller asked for something that is not a
DXN error at all, such as a schema that was never registered.
- 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.
- Tree
- A tree of `.dxns` files read as one (Jan, 2026-10-20), the port of
`Dextrin.Schema.Tree`: every entry named from where its file lies
(DXNS.md §6, {@see Layout}), the files compiled in the order their
references need -- so a reference to another file's named type
resolves whatever order the files sort in -- and every problem reported
with its file and line:
- TypeExpr
- Internal, compiled representation of a `.dxns` type expression -- the
vocabulary of DXNS.md §1 (`any`, `never`, `primitive`, `reference`,
`list-of`, `list-items`, `set-of`, `tuple-of`, `tuple-of-all`, `map-of`, `array-of`, `tagged`, `doc`, `if`,
`ordered-map-of`, `sorted-set-of`, `enum`, `one-of`, `any-of`,
`all-of`, `not`, `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 fields and their types) -- 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 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.
- SyntaxNode
- One node of a document's syntax tree, as {@see Parser::parseTree()}
records it: its kind, its first and last token (indices into the
tokens), the nodes inside it, and which of its tokens open it, key it,
or name it. The trivia between two tokens is the text between them.
- Temporal
- DXN's dates and times as text (DXN.md §1.2, §1.5; Jan, 2026-10-20):
the ISO 8601 strings under `@date`, `@time`, `@datetime`,
`@local-datetime` and `@duration`, read and written in one place so
the parser, the printer and the canonical form never drift apart.
- 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).
- UnsupportedValue
- A value with no DXN meaning -- a closure, a resource -- refused by the
printer and the binary encoder alike, with DXN.md §5's
`encode/unsupported` category.
- DXNBits
- DXN `bits` (DXN.md §1.4): a run of bits that is not whole bytes --
`@bits {"oA==" 3}` in text, `T_BITS` around `[bytes, count]` in
`.dxnb`. PHP has no bitstring, so this holds the bytes, the unused low
bits of the last one zero, and how many bits are the value.
- 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` (`@date "YYYY-MM-DD"`) -- a bare calendar date, ISO 8601.
- DXNDateTime
- DXN `datetime` (`@datetime "2026-10-20T14:30:00Z"`) -- an instant with
its offset, `Z` for UTC (DXN.md §1.3). DXN 0.2's `timestamp`, a UTC
instant, is one of these at offset zero (Jan, 2026-10-20). Mirrors
`.dxnb`'s wire shape directly: the instant as nanoseconds since the
epoch, and the offset it was written with, in minutes.
- 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. Seconds and their
fraction are one field, `nanoseconds` (Jan, 2026-10-20); a field's
sign is its own, `P-1DT2H`. 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.
- DXNIp
- DXN `ip` (`@ip "..."`, Jan, 2026-10-20): an IPv4 or IPv6 address, or a
prefix -- an address and a length. `$address` holds the raw bytes, 4
or 16; `$prefix` is the length, or null for an address. A prefix's
host bits are zero (RFC 9164). In `.dxnb` it is CBOR's tag 52 or 54,
raw bytes.
- 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).
- DXNLocalDateTime
- DXN `local-datetime` (`@local-datetime "2026-10-20T14:30:00"`) -- a
date and a time on the wall clock, with no offset, so no instant
(DXN.md §1.3; Jan, 2026-10-20). Held as `.dxnb` holds it: nanoseconds
since 1970-01-01T00:00:00 on the wall clock.
- DXNMap
- A DXN map that a native PHP array cannot hold.
- DXNObjectId
- DXN `objectid` (`@objectid "..."`, Jan, 2026-10-20): BSON's 12-byte
ObjectId, built in like `uuid` -- readable in text, its raw bytes in
`.dxnb` (tag 221). The value holds the bytes; the 24 hex digits exist
only at the text boundary.
- 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` (`@regex "pattern"`, or `@regex(flags) "pattern"`) -- its
flags in a header, PCRE's inline modifiers part of the pattern, so
`@regex "(?i)a"` is a different value from `@regex(i) "a"` (DXN.md
§1.2). 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` (`@time "HH:MM:SS(.f{1,9})?"`) -- a bare time of day, ISO
8601, to the nanosecond (DXN.md §1.3; Jan, 2026-10-20). No conversion
helper: PHP has no native "time of day" type to convert to/from
without a synthetic, misleading date component.
- 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
- Form
- One of DXN's three forms: text (`.dxn`), binary (`.dxnb`) and schemas
(`.dxns`). See {@see Format}.
- 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`).
- NoMatch
- What {@see TypeExpr::conform()} answers when a value does not match:
a value of its own, since `null` and `false` are values a type takes.