Documentation

Typed arrays

Give PHP lists and maps precise key and value contracts while keeping their familiar runtime behavior.

PHP arrays are remarkably flexible. The same value can behave as a list, a map, or both, and that flexibility is useful at uncertain boundaries. Inside domain code, however, “array” often hides exactly the information a caller needs: what may be used as a key and what each element contains.

++PHP lets that information live in the native source type.

Choose the shape you mean

There are three array forms:

Type Meaning
array<T> a list whose values are T and whose positions use integer keys
array<K, V> a map whose keys are K and values are V
array PHP's broad array with mixed keys and values

A checkout service can make each role visible:

array<LineItem> $items = [];
array<string, Coupon> $couponsByCode = [];
array $unvalidatedPayload = json_decode($json, true);

The broad form is not forbidden. It marks the place where validation or narrowing still needs to happen.

Build a typed list

A list preserves one value type through literals, appends, indexing, parameters, and returns:

function availableProducts(Catalog $catalog): array<Product>
{
    array<Product> $products = [];

    foreach ($catalog->entries() as CatalogEntry $entry) {
        if ($entry->isAvailable()) {
            $products[] = $entry->product();
        }
    }

    return $products;
}

Appending a Product is valid. Appending a string is rejected at the write, close to the mistake. Returning the list from a function declared as array<Product> preserves the element type for every caller.

An empty literal is accepted because it introduces no conflicting element. The declared type, not the first append, determines what the list may hold.

Build a typed map

A map records both sides of the relationship:

function indexCustomers(array<Customer> $customers): array<string, Customer>
{
    array<string, Customer> $byEmail = [];

    foreach ($customers as Customer $customer) {
        $byEmail[$customer->email] = $customer;
    }

    return $byEmail;
}

The compiler checks the key expression against string and the assigned value against Customer. Reading a known map produces a Customer value rather than mixed.

Numeric-string keys follow PHP's normal normalization rules. Iteration order and all other array behavior remain unchanged.

Use array types everywhere a type belongs

Typed arrays can appear in local declarations, parameters, returns, properties, nullable types, and nested generic positions:

final class DeliveryPlan
{
    public function __construct(
        public array<string, array<Parcel>> $parcelsByRegion,
    ) {
    }
}

function planFor(?array<Order> $orders): array<string, DeliveryPlan>
{
    // ...
}

The same type relationship is checked across calls rather than being rediscovered inside each function.

Keep element types exact

array<Dog> is not automatically assignable to array<Animal>. If the receiving code could append a Cat, the original dog list would no longer satisfy its declaration.

When a function only needs every item to share one capability, give it a generic parameter constrained to that capability. When a collection genuinely accepts unrelated values, use an explicit broad type instead of weakening a precise list.

Readonly prevents structural writes

Readonly applies to the array variable as a whole:

readonly array<string, FeatureFlag> $flags = loadFlags();

$flags['checkout'] = new FeatureFlag(); // rejected
unset($flags['search']);                // rejected
ksort($flags);                          // rejected: mutates by reference
$flags = [];                            // rejected

It is not legal inside a type argument:

array<readonly string, int> $invalid = [];

Readonly array storage is not deep object immutability. A FeatureFlag object reached through the array is still governed by the class and property rules that define that object.

Generate useful PHPDoc

The generic arguments disappear from executable PHP syntax. The compiler keeps the native array declaration and emits compatible PHPDoc:

function group(array<Order> $orders): array<string, Order>
{
    // ...
}

becomes conceptually:

/**
 * @param list<Order> $orders
 * @return array<string, Order>
 */
function group(array $orders): array
{
    // ...
}

The generated code uses normal PHP arrays, functions, copy-on-write behavior, keys, and iteration. PHPStan, IDEs, and ordinary PHP callers can still understand the element relationship through list<T> and array<K, V> metadata.

Choose typed or broad deliberately

Use array<T> when ordering and list positions matter. Use array<K, V> for lookup tables, grouped values, and domain indexes. Keep bare array at data boundaries whose shape has not yet been validated.

For reusable relationships beyond arrays, continue with erased generics. To keep a typed collection variable from being replaced or structurally mutated, review typed locals.