php-dextrin

MapBuilder
in package

FinalYes

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>|DXNMap

markStringKey()

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
string

toDxnMapKey()

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

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
bool

mapKeyToPhpKey()

private static mapKeyToPhpKey(mixed $dxnKey, callable(string): Throwable $errorFactory) : int|string
Parameters
$dxnKey : mixed
$errorFactory : callable(string): Throwable
Return values
int|string
On this page

Search results