---
title: "Typed Getters"
description: "Typed Getters"
type: "docs"
category: "doc"
tags: []
authors: [Anonymous]
date: "2026-10-20"
last_update: "2026-10-20"
time_minutes: 5
draft: false
unlisted: false
url: "https://www.derafu.dev/docs/data/container/typed-getters"
---

# Typed Getters

`get()` returns `mixed`, because a container can hold anything. That is a
problem when you know what a key holds: the type checker does not, and every use
of the value needs a cast or a check.

The typed getters read a value and check its type. If the value has another
type they throw, so what they return can be trusted, by you and by static
analysis (PHPStan, Psalm).

```php
$host = $options->requireString('transport.host'); // string
$port = $options->requireInt('transport.port');    // int
$ssl  = $options->getBool('transport.ssl', true);  // bool
```

## `get*()` and `require*()`

Every type has two methods:

- `getX($key, $default = null)`: the value may not be there. It returns the
  value, or the default when the key does not exist or its value is `null`.
- `requireX($key)`: the value has to be there. It throws when the key does not
  exist or its value is `null`.

| Situation | `getX()` | `requireX()` |
|---|---|---|
| The key does not exist, or its value is `null` | Returns the default (`null` if none). | Throws `MissingValueException`. |
| The value has another type | Throws `UnexpectedTypeException`. | Throws `UnexpectedTypeException`. |
| The value has the type | Returns it. | Returns it. |

A value with the wrong type is never replaced by the default. If `port` is the
string `"abc"`, `getInt('port', 993)` throws instead of answering `993`: a
mistake in the data must not look like a value that is not defined.

`0`, `''`, `false` and `[]` are values: `getInt('retries', 3)` returns `0` when
`retries` is `0`.

## The Methods

| Methods | Type returned | Notes |
|---|---|---|
| `getString()`, `requireString()` | `string` | |
| `getInt()`, `requireInt()` | `int` | |
| `getFloat()`, `requireFloat()` | `float` | An `int` is accepted and returned as `float`. |
| `getBool()`, `requireBool()` | `bool` | |
| `getArray()`, `requireArray()` | `array` | |
| `getList()`, `requireList()` | `list` | An array with sequential integer keys. |
| `getEnum()`, `requireEnum()` | A `BackedEnum` | Accepts a case or a backing value. |
| `getObject()`, `requireObject()` | An object | Checks the class with `instanceof`. |
| `getContainer()`, `requireContainer()` | `static` | See [Nested Containers](#nested-containers). |

They all take dot notation keys, like `get()`.

## No Conversion

The getters do not convert: `"993"` is not an `int` and `"true"` is not a
`bool`. Converting in every getter would make each one decide what is valid,
and would hide the mistakes that the checks are for.

Convert once, when the data enters the container: a `Vault` can normalize a
value with the `normalizer` of its schema (see [Validation](validation)).

```php
use Derafu\Container\Vault;

$options = new Vault(
    ['port' => '993'], // For example, from an environment variable.
    [
        'port' => [
            'types' => ['int', 'string'],
            'normalizer' => fn ($options, $value) => (int) $value,
        ],
    ]
);

$options->requireInt('port'); // 993
```

## Arrays and Lists with Typed Elements

`getArray()` and `getList()` take `of:`, the type of the elements: `string`,
`int`, `float`, `bool`, `array` or the name of a class or interface. Every
element is checked, and the error names the offending one:

```php
$bag = new Bag(['formats' => ['xml', 'json'], 'ids' => [1, 2, 'x']]);

$bag->requireList('formats', of: 'string'); // list<string>: ['xml', 'json']
$bag->getList('ids', of: 'int');
// UnexpectedTypeException: The value of ids.2 must be of type int; string given.
```

For `of: 'float'` the integers are returned as floats. For a class, the elements
have to be instances of it:

```php
$handlers = $bag->requireList('handlers', of: HandlerInterface::class); // list<HandlerInterface>
```

`getList()` also rejects an array that is not a list (`['a' => 1]`), where
`getArray()` accepts it.

## Enums and Objects

```php
enum Encryption: string
{
    case Ssl = 'ssl';
    case Tls = 'tls';
}

$bag = new Bag(['encryption' => 'ssl', 'mailer' => new SmtpMailer()]);

$bag->requireEnum('encryption', Encryption::class); // Encryption::Ssl
$bag->requireObject('mailer', MailerInterface::class); // MailerInterface
```

`getEnum()` accepts a case of the enum or one of its backing values, and throws
`UnexpectedTypeException` for any other value (`'none'` in the example).

## Nested Containers

`get('transport')` returns an array for a `Bag`, and a container for the
containers that wrap arrays when they are read (like `Derafu\Config\Options`).
`getArray()` and `getContainer()` do not depend on that:

- `getArray('transport')` returns the array.
- `getContainer('transport')` returns a container of the same class, with the
  data of that key.

```php
$transport = $options->requireContainer('transport');

$host = $transport->requireString('host');
$port = $transport->requireInt('port');
```

The nested container is a copy: changing it does not change the one it came
from. A `Vault` does not give the nested container the part of its schema that
applies to that key.

If a container has a constructor that does not take the data as its first
argument, it overrides `createChild(array $data, string $key): static`, the
protected method that builds the nested container.

## `ArrayAccess` Is Not Typed

`$container['key']` always returns `mixed`: it is the contract of `ArrayAccess`
in PHP. Use the typed getters when the type matters.

## Static Analysis

The getters declare their types in PHPDoc, with conditional types for `of:`, so
PHPStan knows what they return:

```php
$bag->getString('a');                  // string|null
$bag->requireInt('a');                 // int
$bag->requireList('a', of: 'string');  // list<string>
$bag->getList('a', of: Foo::class);    // list<Foo>|null
$bag->requireEnum('a', Encryption::class); // Encryption
$bag->requireContainer('a');           // static
```

## Exceptions

| Exception | When |
|---|---|
| `Derafu\Container\Exception\MissingValueException` | A `require*()` method finds no value. |
| `Derafu\Container\Exception\UnexpectedTypeException` | The value, or one of its elements, has another type. |

Their messages are translatable (see [Translations](translations)):

```text
The value of port must be of type int; string given.
The value of host is required but it is not defined.
```

A type name that is not a scalar type, `array`, a class or an interface, given
as `of:`, throws `TranslatableInvalidArgumentException`.



---
Last updated on 20/10/2026

