php-dextrin

Compiler
in package

FinalYes

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.

%schema}/%field} are recognized here directly, as DXNStruct instances named "schema"/"field" straight out of Dextrin::decode() -- never looked up in a registry, the same way built-in tags are recognized by name in the text/binary codecs rather than registered. This sidesteps an obvious bootstrapping question (doesn't parsing a .dxns file need a schema for its own schema/field structs?) the same way JSON Schema's own vocabulary is fixed tooling knowledge rather than itself validated against a schema.

An entry whose value isn't a %schema} is a named type instead: a reusable name for some combination of the fixed type_expr vocabulary, e.g. PositiveInt: {:refine :integer {min: 1}} -- purely data, no PHP code, no registry callback, so any conformant reader in any language resolves it the same way.

A struct's field names, and a document's own top-level entry names, are always plain PHP strings by the time they reach this class -- Parser already reduces a %schema}'s own keys -- its field names -- to strings on the way in (its own structFieldName()), and the document's top-level map keys were already coerced to PHP array keys by MapBuilder -- unlike the JS port's own entryName()/normalizeKeywords() helpers, neither is needed here.

Table of Contents

Constants

ANNOTATION_KEYS  : mixed = ['title', 'description', 'examples', 'read-only...
BOUNDS  : mixed = ['min', 'max', 'exclusive-min', 'exclusive-max']
CONTENT_READERS  : mixed = ['application/dxn', 'application/dxnb']
What `content` reads a string as, by its media type: none, or one of these.
COUNTS  : mixed = ['min-length', 'max-length', 'min-bytes', 'max-...
ENCODINGS  : mixed = ['base64', 'base64url', 'base32', 'base16']
FLAGS  : mixed = ['finite', 'float32', 'unique', 'before-now', '...
PRIMITIVES_ANY  : mixed = 'any'
REFINE_KEYS  : mixed = ['min', 'max', 'exclusive-min', 'exclusive-max'...
SIZED  : mixed = ['int32' => [-2147483648, 2147483647], 'int64'...
The sized integers' ranges; a bound past PHP's int is its digits.

Properties

$names  : array<string, string>
The names of the document being compiled (see {@see self::compile()}): set for one compile, which PHP runs one at a time.

Methods

compile()  : Registry
Compiles every entry of a decoded `.dxns` document into `$baseRegistry` -- a struct schema for each `%schema(N){}` entry, a named type_expr for every other entry. A refusal is a {@see SchemaCompileError} (category `schema/compile`), or a {@see \JOetjen\Dextrin\DXNError} of category `schema/duplicate-version` for a version the registry has loaded already (DXNS.md §8).
__construct()  : mixed
allOf()  : bool
Whether `$arg` is a list every item of which `$test` takes. A list, `[...]`, as the reference asks: a native PHP list is DXN's `@array[...]` here, which it refuses.
annotation()  : string|null
What the annotation `$key` takes when `$value` is not one, `null` when it is; a key that is none, or an enum value's annotations that are wrong, throw.
annotations()  : array<string, mixed>
A doc's annotations by name, each of its kind (DXNS.md §1b), and each enum value's under `values` keyed by name as well. `$type` is what they annotate -- `null` for a whole schema or one enum value -- so `values` can be checked against an enum's literals.
applies()  : void
Whether `$name` is a generic taking `$count` arguments.
argument()  : string|null
What `$key` takes when `$arg` is not one, `null` when it is.
arguments()  : void
Every argument the kind its key takes, in key order.
caseFields()  : array<int, string>
casesNameFields()  : void
Every `{:case field ...}` written in `$pairs`' field types names one of `$fields` -- told from the source, so a named type the case reaches through is not looked into.
compileAliases()  : Registry
Every named type in a document sees every other (design/ shapes-and-case.md): each is compiled with its siblings standing as `named`, and those are put in place once all are read -- so the order of a map's entries, which DXN does not give, never matters.
compileDocument()  : Registry
compileEntry()  : Compiled
One `%schema(N){field: type, ...}` entry: the body is the fields, in the order they are written; the header is the version, 1 when there is none.
compileField()  : Field
compileNamed()  : TypeExpr
A named type's definition: a generic's parameters and body, a shape carrying its own name, or a type.
compileTypeExpr()  : TypeExpr
constraintNames()  : array<string, mixed>
The constraints keyed by their names; a key that is no name is an error, not a crash.
contentReadable()  : void
`content` reads a DXN document: text, or `.dxnb` under application/dxnb, which a string holds only encoded.
describe()  : string
expected()  : string
fullName()  : string
The name a written symbol stands for: itself where it is in scope already (a generic's parameter, a name known so), otherwise the full name this document's names give it.
getField()  : mixed
guarded()  : void
Every cycle among `$compiled`'s named types passes a guard: a field of a shape, or a collection's elements, so that each step of a check reads a smaller part of the value. A cycle with none -- `A: {:nilable A}` -- describes no value and would never finish.
hasField()  : bool
inspect()  : string
`$value` as DXN text, for a message; its PHP type when it has none.
isForm()  : bool
Whether `$value` is a tuple headed by the keyword `$form`.
isInteger()  : bool
An integer, a big one too, and not below zero when `$nonNegative`.
isMap()  : bool
Whether `$value` is a map: a {@see DXNMap}, or a native array not shaped like a list (a native list is an `@array`); the empty one is both, and taken.
isOffset()  : bool
`Z`, or `±HH:MM`.
knownKeys()  : void
literalName()  : string|null
The name a keyword, symbol or string gives, or `null`.
namedArg()  : array<string, mixed>
`$key`'s argument as a name, one of `$allowed` when they are given.
notFormat10()  : void
Refuses a schema written in Dextrin 0.1's syntax -- `fields:
pairsOf()  : array<int, array{0: mixed, 1: mixed}>
A map's pairs, each key as DXN has it.
particle()  : Particle
particles()  : array<int, Particle>
A `{:list-items}`'s or a group's items: {@see Particle::one()} for a type, {@see Particle::repeat()} for a repeat, its max `null` when there is no upper bound.
refineConstraints()  : array<string, mixed>
A refine's constraints by name, every key one DXNS.md §2 knows and every argument one it takes: a misspelt key would otherwise refuse every value, silently, and a bad argument fail at check time. The types ({@see TypeExpr::TYPE_KEYS}) are compiled here; `unique-by`, `format`, `resolution` and `encoding` become names.
resolved()  : TypeExpr
`$type` with every `named` replaced by that named type; a reference back to a type being resolved closes a cycle, which {@see self::guarded()} has passed, and stays a reference by name.
takesArguments()  : string
undoc()  : mixed
An entry's value without the `{:doc ...}` around it.
unguardedCycle()  : array<int, string>|null
unguardedNames()  : array<int, string>
The named types `$type` refers to without a guard on the way.
valueAnnotations()  : null
An enum's `values:`: each key one of its literals, each value a map of that value's own annotations, which hold no `values` of their own.

Constants

ANNOTATION_KEYS

private mixed ANNOTATION_KEYS = ['title', 'description', 'examples', 'read-only', 'write-only', 'deprecated', 'meta', 'values']

BOUNDS

private mixed BOUNDS = ['min', 'max', 'exclusive-min', 'exclusive-max']

CONTENT_READERS

What `content` reads a string as, by its media type: none, or one of these.

private mixed CONTENT_READERS = ['application/dxn', 'application/dxnb']

COUNTS

private mixed COUNTS = ['min-length', 'max-length', 'min-bytes', 'max-bytes', 'min-count', 'max-count', 'min-contains', 'max-contains', 'min-precision', 'max-precision']

ENCODINGS

private mixed ENCODINGS = ['base64', 'base64url', 'base32', 'base16']

FLAGS

private mixed FLAGS = ['finite', 'float32', 'unique', 'before-now', 'after-now', 'prefix']

PRIMITIVES_ANY

private mixed PRIMITIVES_ANY = 'any'

REFINE_KEYS

private mixed REFINE_KEYS = ['min', 'max', 'exclusive-min', 'exclusive-max', 'multiple-of', 'min-length', 'max-length', 'min-bytes', 'max-bytes', 'pattern', 'min-count', 'max-count', 'finite', 'float32', 'offsets', 'version', 'prefix', 'contains', 'min-contains', 'max-contains', 'unique', 'unique-by', 'min-precision', 'max-precision', 'min-exponent', 'max-exponent', 'bits', 'before-now', 'after-now', 'within', 'resolution', 'format', 'encoding', 'media-type', 'content', 'encodes', 'encodes-seq']

SIZED

The sized integers' ranges; a bound past PHP's int is its digits.

private mixed SIZED = ['int32' => [-2147483648, 2147483647], 'int64' => [PHP_INT_MIN, PHP_INT_MAX], 'uint32' => [0, 4294967295], 'uint64' => [0, '18446744073709551615']]

Properties

$names

The names of the document being compiled (see {@see self::compile()}): set for one compile, which PHP runs one at a time.

private static array<string, string> $names = []

Methods

compile()

Compiles every entry of a decoded `.dxns` document into `$baseRegistry` -- a struct schema for each `%schema(N){}` entry, a named type_expr for every other entry. A refusal is a {@see SchemaCompileError} (category `schema/compile`), or a {@see \JOetjen\Dextrin\DXNError} of category `schema/duplicate-version` for a version the registry has loaded already (DXNS.md §8).

public static compile(mixed $schemaDoc, Registry $baseRegistry[, array<string, string> $names = [] ]) : Registry

Every named type in a document sees every other, in any order, and may reach itself through a guard -- a field, or a collection's elements (DXNS.md §5, Jan, 2026-10-20). $names maps an entry's key to the name it is registered under -- a tree's full names (DXNS.md §6, Layout); a bare symbol naming a sibling resolves to it.

Parameters
$schemaDoc : mixed
$baseRegistry : Registry
$names : array<string, string> = []
Return values
Registry

__construct()

private __construct() : mixed

allOf()

Whether `$arg` is a list every item of which `$test` takes. A list, `[...]`, as the reference asks: a native PHP list is DXN's `@array[...]` here, which it refuses.

private static allOf(mixed $arg, callable(mixed): bool $test) : bool
Parameters
$arg : mixed
$test : callable(mixed): bool
Return values
bool

annotation()

What the annotation `$key` takes when `$value` is not one, `null` when it is; a key that is none, or an enum value's annotations that are wrong, throw.

private static annotation(string $key, mixed $value, TypeExpr|null $type) : string|null
Parameters
$key : string
$value : mixed
$type : TypeExpr|null
Return values
string|null

annotations()

A doc's annotations by name, each of its kind (DXNS.md §1b), and each enum value's under `values` keyed by name as well. `$type` is what they annotate -- `null` for a whole schema or one enum value -- so `values` can be checked against an enum's literals.

private static annotations(mixed $raw, TypeExpr|null $type) : array<string, mixed>
Parameters
$raw : mixed
$type : TypeExpr|null
Return values
array<string, mixed>

applies()

Whether `$name` is a generic taking `$count` arguments.

private static applies(string $name, int $count, array<string, TypeExpr> $known) : void
Parameters
$name : string
$count : int
$known : array<string, TypeExpr>

argument()

What `$key` takes when `$arg` is not one, `null` when it is.

private static argument(string $key, mixed $arg) : string|null
Parameters
$key : string
$arg : mixed
Return values
string|null

arguments()

Every argument the kind its key takes, in key order.

private static arguments(array<string, mixed> $named) : void
Parameters
$named : array<string, mixed>

caseFields()

private static caseFields(mixed $value) : array<int, string>
Parameters
$value : mixed
Return values
array<int, string>

casesNameFields()

Every `{:case field ...}` written in `$pairs`' field types names one of `$fields` -- told from the source, so a named type the case reaches through is not looked into.

private static casesNameFields(iterable<string|int, array{0: string, 1: mixed}> $pairs, array<int, Field> $fields) : void
Parameters
$pairs : iterable<string|int, array{0: string, 1: mixed}>
$fields : array<int, Field>

compileAliases()

Every named type in a document sees every other (design/ shapes-and-case.md): each is compiled with its siblings standing as `named`, and those are put in place once all are read -- so the order of a map's entries, which DXN does not give, never matters.

private static compileAliases(array<int, array{0: string, 1: mixed}> $aliasEntries, Registry $registry) : Registry
Parameters
$aliasEntries : array<int, array{0: string, 1: mixed}>
$registry : Registry
Return values
Registry

compileDocument()

private static compileDocument(array<int|string, mixed> $schemaDoc, Registry $baseRegistry, array<string, string> $names) : Registry
Parameters
$schemaDoc : array<int|string, mixed>
$baseRegistry : Registry
$names : array<string, string>
Return values
Registry

compileEntry()

One `%schema(N){field: type, ...}` entry: the body is the fields, in the order they are written; the header is the version, 1 when there is none.

private static compileEntry(mixed $entry, array<string, TypeExpr> $aliases) : Compiled
Parameters
$entry : mixed
$aliases : array<string, TypeExpr>
Return values
Compiled

compileField()

private static compileField(string $key, mixed $value, array<string, TypeExpr> $aliases) : Field
Parameters
$key : string

a field name as a struct's keyed pairs hold it.

$value : mixed
$aliases : array<string, TypeExpr>
Return values
Field

compileNamed()

A named type's definition: a generic's parameters and body, a shape carrying its own name, or a type.

private static compileNamed(string $name, mixed $raw, array<string, TypeExpr> $scope) : TypeExpr
Parameters
$name : string
$raw : mixed
$scope : array<string, TypeExpr>
Return values
TypeExpr

constraintNames()

The constraints keyed by their names; a key that is no name is an error, not a crash.

private static constraintNames(array<int|string, mixed>|DXNMap $constraints) : array<string, mixed>
Parameters
$constraints : array<int|string, mixed>|DXNMap
Return values
array<string, mixed>

contentReadable()

`content` reads a DXN document: text, or `.dxnb` under application/dxnb, which a string holds only encoded.

private static contentReadable(array<string, mixed> $named) : void
Parameters
$named : array<string, mixed>

describe()

private static describe(mixed $value) : string
Parameters
$value : mixed
Return values
string

expected()

private static expected(array<int, string>|null $allowed) : string
Parameters
$allowed : array<int, string>|null
Return values
string

fullName()

The name a written symbol stands for: itself where it is in scope already (a generic's parameter, a name known so), otherwise the full name this document's names give it.

private static fullName(string $written, array<string, TypeExpr> $aliases) : string
Parameters
$written : string
$aliases : array<string, TypeExpr>
Return values
string

getField()

private static getField(array<int, array{0: string, 1: mixed}> $pairs, string $key[, mixed $fallback = null ]) : mixed
Parameters
$pairs : array<int, array{0: string, 1: mixed}>
$key : string
$fallback : mixed = null

guarded()

Every cycle among `$compiled`'s named types passes a guard: a field of a shape, or a collection's elements, so that each step of a check reads a smaller part of the value. A cycle with none -- `A: {:nilable A}` -- describes no value and would never finish.

private static guarded(array<string, TypeExpr> $compiled) : void
Parameters
$compiled : array<string, TypeExpr>

hasField()

private static hasField(array<int, array{0: string, 1: mixed}> $pairs, string $key) : bool
Parameters
$pairs : array<int, array{0: string, 1: mixed}>
$key : string
Return values
bool

inspect()

`$value` as DXN text, for a message; its PHP type when it has none.

private static inspect(mixed $value) : string
Parameters
$value : mixed
Return values
string

isForm()

Whether `$value` is a tuple headed by the keyword `$form`.

private static isForm(mixed $value, string $form) : bool
Parameters
$value : mixed
$form : string
Return values
bool

isInteger()

An integer, a big one too, and not below zero when `$nonNegative`.

private static isInteger(mixed $arg, bool $nonNegative) : bool
Parameters
$arg : mixed
$nonNegative : bool
Return values
bool

isMap()

Whether `$value` is a map: a {@see DXNMap}, or a native array not shaped like a list (a native list is an `@array`); the empty one is both, and taken.

private static isMap(mixed $value) : bool
Parameters
$value : mixed
Tags
phpstan-assert-if-true

array<int|string, mixed>|DXNMap $value

Return values
bool

isOffset()

`Z`, or `±HH:MM`.

private static isOffset(mixed $text) : bool
Parameters
$text : mixed
Return values
bool

knownKeys()

private static knownKeys(array<string, mixed> $named) : void
Parameters
$named : array<string, mixed>

literalName()

The name a keyword, symbol or string gives, or `null`.

private static literalName(mixed $value) : string|null
Parameters
$value : mixed
Return values
string|null

namedArg()

`$key`'s argument as a name, one of `$allowed` when they are given.

private static namedArg(array<string, mixed> $named, string $key, array<int, string>|null $allowed) : array<string, mixed>
Parameters
$named : array<string, mixed>
$key : string
$allowed : array<int, string>|null
Return values
array<string, mixed>

notFormat10()

Refuses a schema written in Dextrin 0.1's syntax -- `fields:

private static notFormat10(iterable<string|int, array{0: string, 1: mixed}> $pairs) : void
Parameters
$pairs : iterable<string|int, array{0: string, 1: mixed}>
Tags
ordered

%{...}, closed: true, forbidden: [...]-- saying so, and what a schema is written as now (DXNS.md §4), rather than that a field has no known type. Only those shapes: nowfields, closedandforbidden` are ordinary field names, whose values are type expressions. The format stays 1.0 until 1.0.0 (Jan, 2026-10-20), so the old syntax is named by the release that had it.

pairsOf()

A map's pairs, each key as DXN has it.

private static pairsOf(array<int|string, mixed>|DXNMap $map) : array<int, array{0: mixed, 1: mixed}>
Parameters
$map : array<int|string, mixed>|DXNMap
Return values
array<int, array{0: mixed, 1: mixed}>

particles()

A `{:list-items}`'s or a group's items: {@see Particle::one()} for a type, {@see Particle::repeat()} for a repeat, its max `null` when there is no upper bound.

private static particles(array<int, mixed> $values, array<string, TypeExpr> $aliases) : array<int, Particle>
Parameters
$values : array<int, mixed>
$aliases : array<string, TypeExpr>
Return values
array<int, Particle>

refineConstraints()

A refine's constraints by name, every key one DXNS.md §2 knows and every argument one it takes: a misspelt key would otherwise refuse every value, silently, and a bad argument fail at check time. The types ({@see TypeExpr::TYPE_KEYS}) are compiled here; `unique-by`, `format`, `resolution` and `encoding` become names.

private static refineConstraints(array<int|string, mixed>|DXNMap $constraints, array<string, TypeExpr> $aliases) : array<string, mixed>
Parameters
$constraints : array<int|string, mixed>|DXNMap
$aliases : array<string, TypeExpr>
Return values
array<string, mixed>

resolved()

`$type` with every `named` replaced by that named type; a reference back to a type being resolved closes a cycle, which {@see self::guarded()} has passed, and stays a reference by name.

private static resolved(TypeExpr $type, array<string, TypeExpr> $known, array<int, string> $path) : TypeExpr
Parameters
$type : TypeExpr
$known : array<string, TypeExpr>
$path : array<int, string>
Return values
TypeExpr

takesArguments()

private static takesArguments(string $name, TypeExpr $generic) : string
Parameters
$name : string
$generic : TypeExpr
Return values
string

undoc()

An entry's value without the `{:doc ...}` around it.

private static undoc(mixed $value) : mixed
Parameters
$value : mixed

unguardedCycle()

private static unguardedCycle(string $name, array<string, array<int, string>> $edges, array<int, string> $path) : array<int, string>|null
Parameters
$name : string
$edges : array<string, array<int, string>>
$path : array<int, string>
Return values
array<int, string>|null

unguardedNames()

The named types `$type` refers to without a guard on the way.

private static unguardedNames(TypeExpr $type) : array<int, string>
Parameters
$type : TypeExpr
Return values
array<int, string>

valueAnnotations()

An enum's `values:`: each key one of its literals, each value a map of that value's own annotations, which hold no `values` of their own.

private static valueAnnotations(array<int|string, mixed>|DXNMap $values, array<int, mixed> $literals) : null

Always null; a wrong one throws.

Parameters
$values : array<int|string, mixed>|DXNMap
$literals : array<int, mixed>
Return values
null
On this page

Search results