---
title: "Readers"
description: "Optional and required typed reads, null handling, integer keys, nested readers and sparse collections"
type: "docs"
category: "doc"
tags: []
authors: [Anonymous]
date: "2026-10-26"
last_update: "2026-10-26"
time_minutes: 6
draft: false
unlisted: false
url: "https://www.derafu.dev/docs/data/array/readers"
---

# ArrayReader

```php
use Derafu\Array\ArrayReader;
use Derafu\Value\ValueType;

$reader = ArrayReader::fromArray(['name' => 'Alice', 'enabled' => false]);
$reader->requireString('name'); // 'Alice'
$reader->getBool('enabled', true); // false
```

## Creating a Reader

| Factory | Input | Behavior |
| --- | --- | --- |
| `fromArray(array $data, ?string $source = null)` | An array. | Creates a reader at the root. |
| `fromValue(mixed $data, ?string $source = null, string $path = '')` | Untrusted input. | Validates that it is an array, without converting it. |
| `new ArrayReader($data, $path = '', $source = null)` | An array. | Creates a reader with an explicit path and source. |

`toArray()` returns the stored array. Changing the original array after creating a reader does not change the reader's array values.

## Optional and Required Values

A missing key or a value of `null` uses the optional getter's default. A present value of another type throws. Falsy values such as `false`, `0`, `''` and `[]` are preserved when they match the requested type.

| Optional method | Required method | Value |
| --- | --- | --- |
| `getString($key, $default = null)` | `requireString($key)` | String. |
| `getNonEmptyString($key)` | `requireNonEmptyString($key)` | String containing at least one non-whitespace character; returned without trimming. |
| `getInt($key, $default = null)` | `requireInt($key)` | Integer. |
| `getFloat($key, $default = null)` | `requireFloat($key)` | Float; stored integers are rejected. |
| `getNumber($key, $default = null)` | `requireNumber($key)` | Integer or float. |
| `getBool($key, $default = null)` | `requireBool($key)` | Boolean. |
| `getDateTime($key)` | `requireDateTime($key)` | `DateTimeInterface`; see [Value Types](https://www.derafu.dev/docs/data/value/types#dates). |
| `getObject($key, $class)` | `requireObject($key, $class)` | Instance of the class or interface; the return type is that class. |
| `getArray($key)` | `requireArray($key)` | Any array. |
| `getMap($key)` | `requireMap($key)` | Array with string keys. |
| `getList($key)` | `requireList($key)` | Zero-based consecutive list. |
| `getStringList($key)` | `requireStringList($key)` | List of strings. |
| `getIntList($key)` | `requireIntList($key)` | List of integers. |
| `getScalarList($key)` | `requireScalarList($key)` | List of booleans, numbers or strings. |
| `getArrayOf($key, $type)` | `requireArrayOf($key, $type)` | Array whose values match a type; keys are preserved. |
| `getMapOf($key, $type)` | `requireMapOf($key, $type)` | String-keyed array whose values match a type. |
| `getListOf($key, $type)` | `requireListOf($key, $type)` | List whose values match a type. |

Optional methods return `null` when there is no value or default. The primitive getters with a non-null default have a non-null return type for PHPStan. Required methods reject missing keys and `null`, including when the requested `ValueType` is nullable.

The generic methods accept any [composable type](types):

```php
$reader = ArrayReader::fromArray(['mode' => 'fast']);
$reader->get('mode', ValueType::literal('fast', 'safe'), 'safe');
$reader->require('mode', ValueType::string());
$reader->get('tags', ValueType::listOf(ValueType::string()), []);
```

Defaults are returned as supplied, not validated or converted by the requested type.

## Explicit Conversions

Strict getters validate existing types. The getAsXXX()/requireAsXXX() methods explicitly convert through ValueType and ValueCaster from [Derafu Value](https://www.derafu.dev/docs/data/value).

| Optional method | Required method | Converted type |
| --- | --- | --- |
| `getAsInt($key, $default = null)` | `requireAsInt($key)` | Integer. |
| `getAsFloat($key, $default = null)` | `requireAsFloat($key)` | Float. |
| `getAsNumber($key, $default = null)` | `requireAsNumber($key)` | Integer when possible, otherwise float. |
| `getAsBool($key, $default = null)` | `requireAsBool($key)` | Boolean. |
| `getAsString($key, $default = null)` | `requireAsString($key)` | String. |

```php
use Derafu\Array\ArrayReader;

$reader = ArrayReader::fromArray(['port' => '5432', 'enabled' => 'off', 'label' => false]);
$reader->requireAsInt('port');       // 5432
$reader->requireAsBool('enabled');   // false
$reader->requireAsString('label');   // '0'
$reader->getAsNumber('missing', 0);  // 0
```

Missing keys and null retain the reader's existing default/required rules. Present values that cannot be converted throw; they do not use the default. See [ValueCaster's conversion contracts](https://www.derafu.dev/docs/data/value/casting).

## Presence and Allowed Keys

Every read accepts an integer or string key. An existing literal key takes precedence; otherwise, dot notation such as `'connection.port'` navigates nested arrays.

| Method | Meaning |
| --- | --- |
| `hasKey($key)` | The key exists, even when its value is `null`. |
| `hasValue($key)` | The key exists and its value is not `null`. |
| `assertOnlyKeys(...$keys)` | Throws for any key not allowed; returns the same reader. It does not require the allowed keys to exist. |

```php
$reader = ArrayReader::fromArray([0 => null, 'enabled' => false]);
$reader->hasKey(0); // true
$reader->hasValue(0); // false
$reader->hasValue('enabled'); // true
$reader->assertOnlyKeys(0, 'enabled');
```

Integer allowed keys and their equivalent string forms are accepted by `assertOnlyKeys()`. This method checks actual keys at the current level; allowed names are literal and are not expanded into paths. To check nested keys, obtain a nested reader and call `assertOnlyKeys()` on it.

## Nested Readers and Collections

`getReader()` and `requireReader()` read any nested array as another reader: maps, lists and sparse arrays all work. An optional second argument validates the selected array with a `ValueType` before creating its reader. Omitting it, or passing `null`, accepts any array as before.

```php
$reader = ArrayReader::fromArray(['authors' => [['Alice', 'alice']]]);
$author = $reader->requireReader('authors')->requireReader(0);
$author->requireString(0); // 'Alice'
$author->getString(1); // 'alice'
```

| Optional | Required | Outer collection |
| --- | --- | --- |
| `getReaders($key)` | `requireReaders($key)` | A list of arrays, returned as a list of readers. |
| `getArrayReaders($key)` | `requireArrayReaders($key)` | An array of arrays, preserving arbitrary keys. |

Each record can itself be any array. The collection methods also accept an optional second `ValueType` argument: it validates each record without changing the outer collection's requirements. `getReaders()` and `requireReaders()` still require a consecutive list; `getArrayReaders()` and `requireArrayReaders()` preserve arbitrary keys.

Optional methods return `null` for absent or null values without invoking the type. Required methods throw `MissingValueException` for those values before invoking it. Empty collections return `[]`.

```php
use Derafu\Array\ArrayReader;
use Derafu\Value\ValueType;

$reader = ArrayReader::fromArray([
    'innerJoin' => ['table' => 'stock', 'condition' => 'products.id = stock.product_id'],
    'questions' => [['text' => 'What is PHP?']],
]);

$join = $reader->requireReader('innerJoin', ValueType::map());
$table = $join->requireString('table'); // 'stock'.
$questions = $reader->requireReaders('questions', ValueType::map());
$text = $questions[0]->requireString('text'); // 'What is PHP?'.
```

The supplied type must produce an array. Examples include `ValueType::map()`, `ValueType::list()`, `ValueType::arrayOf()` and `ValueType::mapOf()`. Scalar-producing types are not valid arguments. A map requires string keys; a positional list is rejected when a map is requested.

Each selected array is parsed once during reader creation, and the parsed result becomes the child reader's data. Explicit conversion types can therefore prepare a reader's values without modifying the parent reader. Both validation errors and subsequent child reads retain the full path and source.

```php
use Derafu\Array\ArrayReader;
use Derafu\Value\ValueType;

$reader = ArrayReader::fromArray([
    'translations' => [
        'es' => ['save' => 'Guardar'],
        'en' => ['save' => 'Save'],
    ],
]);

$languages = $reader->requireArrayReaders(
    'translations',
    ValueType::mapOf(ValueType::string()),
);
$label = $languages['es']->requireString('save'); // 'Guardar'.
```

```php
$reader = ArrayReader::fromValue(
    ['rows' => [3 => ['name' => 'Alice'], 8 => ['name' => 123]]],
    source: 'account API',
    path: 'response',
);

$rows = $reader->requireArrayReaders('rows');
$rows[3]->requireString('name'); // 'Alice'
$rows[8]->requireString('name');
// Throws: The key "response.rows.8.name" of account API must be of type string, int given.
```

`requireReaders('rows')` would reject this outer collection because its keys are not a consecutive list. No reader reindexes an arbitrary collection.



---
Last updated on 26/10/2026

