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).

$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.

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).

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:

$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:

$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

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.
$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:

$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):

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.

On this page

Last updated on 20/10/2026 by Anonymous