MapBuilder
in package
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.
Every key is one DXN.md ยง1.6 allows -- a string, keyword, integer,
uuid, objectid, date, bytes, symbol or char -- or the map is refused with
KeyNotAllowed (key/not-allowed).
A native PHP array when it can hold the map exactly, a
DXNMap when it cannot. A native array holds a map whose keys
are all integers, keywords or strings, where no two of them coerce to
one array slot, no string key is one PHP would turn into an integer
("5"), and the result is not shaped like a list (an empty map, or
one keyed 0..n-1, which would read back as a DXN array). Anything
else is a DXNMap, which keys by canonical form and loses nothing.
Within a native array a DXNKeyword reduces to its bare name string
(matching map_entry's shorthand form, and keeping the common case --
%{x: 1} -- looking up as the ergonomic $map['x']), while a
genuinely string-typed key with the same text is marked with a single
leading self::STRING_KEY_MARKER byte, so :x and "x" stay two
keys. self::toDxnMapKey() is the reverse, used by every encoder
to recover which one a given PHP key was.
Table of Contents
Constants
- STRING_KEY_MARKER : mixed = "\x00"
- Marks a genuinely string-typed map key, distinguishing it from a `DXNKeyword` key with the same text -- see this class's own doc for why. A single NUL byte: it can't be the first character of any *other* PHP-array-key-representable DXN value (int/bool/float/nil all coerce to a plain `int` key or the bare empty string, never a leading-NUL string), and prepending exactly one byte is trivially, unambiguously reversible by stripping exactly one byte back -- no escaping needed even for a string that itself already starts with a NUL byte.
Methods
- build() : array<int|string, mixed>|DXNMap
- markStringKey() : string
- Marks `$s` as a genuinely `string`-typed map key, the same rule `mapKeyToPhpKey()` applies to a decoded `string` key -- exposed for callers that build a map-shaped PHP array directly from a known-string source without going through `build()`'s DXN-value dispatch. The one caller today: {@see \JOetjen\Dextrin\Schema\Validator}'s default (no custom materializer registered) struct decode, which resolves a `DXNStruct`'s field *names* into a bare associative array -- exactly the same "materialize a struct with no registered module/materializer into a generic map" shape Elixir's own default (`Atom.to_string/1` on each field name) and node-dextrin's default both produce, keyed by the field name as a genuine string, not a keyword. Left unmarked, those keys would silently re-encode as `keyword` instead on the way back out -- diverging from what a schema-free reader of the same document in either of those other two languages would produce.
- toDxnMapKey() : DXNKeyword|string|int
- The reverse of {@see self::mapKeyToPhpKey()}'s `DXNKeyword`/ `string` handling, for `Text\Printer`/`Text\Formatter`/ `Binary\Encoder` to recover which one a bare `map`'s PHP array key actually started out as. An `int` key needs no translation: {@see self::build()} only ever leaves one there for a DXN integer, since a string key PHP would coerce to an integer makes a {@see DXNMap} instead.
- __construct() : mixed
- isCanonicalIntegerString() : bool
- Whether PHP would auto-coerce `$s` to an `int` array key on its own (e.g. `"1"`, `"0"`, `"-5"` -- but not `"01"`, `"1.0"`, `""`, or a string past `int` range) -- left un-marked so it keeps colliding with a bare `integer` key of the same value exactly as before, rather than this class's own keyword/string marking accidentally hiding that collision instead of reporting it.
- mapKeyToPhpKey() : int|string
Constants
STRING_KEY_MARKER
Marks a genuinely string-typed map key, distinguishing it from a `DXNKeyword` key with the same text -- see this class's own doc for why. A single NUL byte: it can't be the first character of any *other* PHP-array-key-representable DXN value (int/bool/float/nil all coerce to a plain `int` key or the bare empty string, never a leading-NUL string), and prepending exactly one byte is trivially, unambiguously reversible by stripping exactly one byte back -- no escaping needed even for a string that itself already starts with a NUL byte.
private
mixed
STRING_KEY_MARKER
= "\x00"
The one residual ambiguity this can't resolve: a keyword
literally named starting with a NUL byte (only reachable via an
explicit \x{0} escape inside a quoted keyword literal) is
indistinguishable from a marked string key. Accepted as a
documented, exceedingly rare edge case, same spirit as the
int-vs-numeric-string collision this class already detects
rather than silently resolving.
Methods
build()
public
static build(array<int, array{0: mixed, 1: mixed}> $pairs, callable(string): Throwable $errorFactory) : array<int|string, mixed>|DXNMap
Parameters
- $pairs : array<int, array{0: mixed, 1: mixed}>
- $errorFactory : callable(string): Throwable
-
builds the caller's own exception type (
Text\ParseError/Binary\BinaryError) from a message.
Return values
array<int|string, mixed>|DXNMapmarkStringKey()
Marks `$s` as a genuinely `string`-typed map key, the same rule `mapKeyToPhpKey()` applies to a decoded `string` key -- exposed for callers that build a map-shaped PHP array directly from a known-string source without going through `build()`'s DXN-value dispatch. The one caller today: {@see \JOetjen\Dextrin\Schema\Validator}'s default (no custom materializer registered) struct decode, which resolves a `DXNStruct`'s field *names* into a bare associative array -- exactly the same "materialize a struct with no registered module/materializer into a generic map" shape Elixir's own default (`Atom.to_string/1` on each field name) and node-dextrin's default both produce, keyed by the field name as a genuine string, not a keyword. Left unmarked, those keys would silently re-encode as `keyword` instead on the way back out -- diverging from what a schema-free reader of the same document in either of those other two languages would produce.
public
static markStringKey(string $s) : string
Parameters
- $s : string
Return values
stringtoDxnMapKey()
The reverse of {@see self::mapKeyToPhpKey()}'s `DXNKeyword`/ `string` handling, for `Text\Printer`/`Text\Formatter`/ `Binary\Encoder` to recover which one a bare `map`'s PHP array key actually started out as. An `int` key needs no translation: {@see self::build()} only ever leaves one there for a DXN integer, since a string key PHP would coerce to an integer makes a {@see DXNMap} instead.
public
static toDxnMapKey(int|string $phpKey) : DXNKeyword|string|int
Parameters
- $phpKey : int|string
Return values
DXNKeyword|string|int__construct()
private
__construct() : mixed
isCanonicalIntegerString()
Whether PHP would auto-coerce `$s` to an `int` array key on its own (e.g. `"1"`, `"0"`, `"-5"` -- but not `"01"`, `"1.0"`, `""`, or a string past `int` range) -- left un-marked so it keeps colliding with a bare `integer` key of the same value exactly as before, rather than this class's own keyword/string marking accidentally hiding that collision instead of reporting it.
private
static isCanonicalIntegerString(string $s) : bool
Parameters
- $s : string
Return values
boolmapKeyToPhpKey()
private
static mapKeyToPhpKey(mixed $dxnKey, callable(string): Throwable $errorFactory) : int|string
Parameters
- $dxnKey : mixed
- $errorFactory : callable(string): Throwable