Arrays & Shapes
TypePHP provides runtime enforcement for sequential lists, key-value generic maps, typed class arrays, positional tuples, sealed and unsealed array shapes, and object shapes.
Sequential Lists (list<T> & non-empty-list<T>)
A list<T> represents a sequential, 0-indexed integer key array without gaps. TypePHP validates lists at runtime using PHP's native array_is_list() function:
<?php
declare(strict_types=1);
/**
* @param list<non-empty-string> $tags
* @param non-empty-list<positive-int> $scores
*/
function processList(array $tags, array $scores): void
{
// ...
}
// Valid Call
processList(['php', 'pest', 'typephp'], [10, 20, 30]);
// Invalid Call (Associative array passed where list was expected)
processList(['tag1' => 'php'], [10, 20]);
// Throws: TypeError: processList(): Argument $tags must be a list
// Invalid Call (Empty array passed where non-empty-list was expected)
processList(['php'], []);
// Throws: TypeError: processList(): Argument $scores must be a non-empty listKey-Value Generic Arrays (array<K, V> & T[])
TypePHP enforces specific key and value types on associative or indexed arrays:
Generic Key-Value Arrays (array<K, V>)
/**
* @param array<string, positive-int> $userScores
*/
function recordScores(array $userScores): void
{
// ...
}
// Valid Call
recordScores(['alice' => 100, 'bob' => 95]);
// Invalid Call (Key '0' is integer instead of string)
recordScores([0 => 100]);
// Throws: TypeError: recordScores(): Argument $userScores key must be of type string
// Invalid Call (Value -5 violates positive-int)
recordScores(['alice' => -5]);
// Throws: TypeError: recordScores(): Argument $userScores['alice'] must be of type positive-intTyped Scalar, Refinement, & Callable Arrays (positive-int[], non-empty-string[], callable[])
In addition to typed class arrays (User[]), TypePHP validates arrays of primitives, scalar refinements, callables, or shapes using T[] syntax:
/**
* @param positive-int[] $ids
* @param non-empty-string[] $tags
* @param callable[] $callbacks
* @param array{id: positive-int}[] $userShapes
*/
function processTypedArrays(array $ids, array $tags, array $callbacks, array $userShapes): void
{
// ...
}
// Valid Call
processTypedArrays(
ids: [10, 20, 30],
tags: ['php', 'pest'],
callbacks: [fn () => null, 'strlen'],
userShapes: [['id' => 1], ['id' => 2]]
);
// Invalid Call (-50 violates positive-int[])
processTypedArrays(
ids: [10, -50, 30],
tags: ['php', 'pest'],
callbacks: [fn () => null],
userShapes: [['id' => 1]]
);
// Throws: TypeError: processTypedArrays(): Argument $ids[1] must be of type positive-int, negative int (-50) givenPerformance Optimization: When validating arrays of objects (such as
User[]), TypePHP memoizes previously checked object instances in\WeakMap. If the same object instance appears multiple times in a collection, its type is checked once and retrieved in O(1) time on subsequent accesses.
Deeply Nested Arrays & Lists (array<K, list<V>>)
TypePHP recursively validates deeply nested array structures down to any depth:
/**
* @param array<string, list<positive-int>> $matrix
*/
function processMatrix(array $matrix): void
{
// ...
}
// Valid Call
processMatrix([
'math' => [100, 95],
'science' => [88, 92],
]);
// Invalid Call (Nested list item -50 violates positive-int)
processMatrix([
'math' => [100, -50],
]);
// Throws: TypeError: processMatrix(): Argument $matrix['math'][1] must be of type positive-intGenerics inside Typed Arrays & Shapes (list<Producer<T>>)
TypePHP validates generic container objects nested inside arrays or array shapes:
use App\Generics\Producer;
use App\Models\Dog;
use App\Models\Car;
/**
* @param list<Producer<Dog>> $producers
* @param array{items: list<Producer<covariant Animal>>, count: positive-int} $payload
*/
function processGenericList(array $producers, array $payload): void
{
// ...
}
// Valid Call
processGenericList(
[new Producer(new Dog()), new Producer(new Dog())],
['items' => [new Producer(new Dog())], 'count' => 1]
);
// Invalid Call (Producer holds Car instead of Dog)
processGenericList(
[new Producer(new Dog()), new Producer(new Car())],
['items' => [new Producer(new Dog())], 'count' => 1]
);
// Throws: TypeError: processGenericList(): Argument $producers[1] must be an instance of Producer<Dog>Array Shapes (array{key: type})
Array shapes define exact key-value contracts for associative arrays.
Required vs. Optional Keys
Mark optional keys with a question mark (key?: type):
/**
* @param array{id: positive-int, username: non-empty-string, role?: 'admin'|'user'} $payload
*/
function saveUserPayload(array $payload): void
{
// ...
}
// Valid Call (Optional 'role' key omitted)
saveUserPayload(['id' => 10, 'username' => 'Alice']);
// Valid Call (Optional 'role' key provided)
saveUserPayload(['id' => 10, 'username' => 'Alice', 'role' => 'admin']);
// Invalid Call (Missing required 'username' key)
saveUserPayload(['id' => 10]);
// Throws: TypeError: saveUserPayload(): Argument $payload is missing required key 'username'Sealed vs. Unsealed Shapes
By default, array shapes are sealed. Any unexpected extra keys in the array will trigger a TypeError.
To allow additional dynamic keys, define an unsealed shape using ...<K, V> syntax:
/**
* Unsealed Shape: Requires 'id', but permits additional string-string pairs
*
* @param array{id: positive-int, ...<string, string>} $options
*/
function processUnsealedOptions(array $options): void
{
// ...
}
// Valid Call (Includes extra string key 'category')
processUnsealedOptions(['id' => 10, 'category' => 'admin']);
// Invalid Call (Extra key 'code' has integer value 999 instead of string)
processUnsealedOptions(['id' => 10, 'code' => 999]);
// Throws: TypeError: processUnsealedOptions(): Argument $options['code'] must be of type stringPositional Tuple Shapes (array{0: T1, 1: T2})
Define fixed-length, positional array tuples:
/**
* @param array{0: positive-int, 1: non-empty-string} $tuple
*/
function processTuple(array $tuple): void
{
// ...
}
// Valid Call
processTuple([100, 'success']);
// Invalid Call (Index 0 is negative integer)
processTuple([-5, 'success']);
// Throws: TypeError: processTuple(): Argument $tuple['0'] must be of type positive-intObject Shapes (object{prop: type} & stdClass{prop: type})
Define property shape contracts for generic objects or strictly for stdClass instances:
Generic Object Shapes (object{prop: type})
Accepts any object or stdClass matching the property shape:
/**
* @param object{id: positive-int, name: non-empty-string} $user
*/
function processObjectShape(object $user): void
{
// ...
}
$std = new stdClass();
$std->id = 42;
$std->name = 'Alice';
processObjectShape($std); // ValidStrict stdClass Shapes (stdClass{prop: type})
Strictly requires a stdClass instance, rejecting custom class instances:
/**
* @param stdClass{id: positive-int, name: non-empty-string} $payload
*/
function processStrictStdClass(object $payload): void
{
// ...
}
// Rejects custom class instances even if they possess 'id' and 'name' properties!
class CustomUser { public int $id = 42; public string $name = 'Alice'; }
processStrictStdClass(new CustomUser());
// Throws: TypeError: processStrictStdClass(): Argument $payload must be an instance of stdClass