Beyond the scalar and nullable types of Strict Types and Null and Nullables, a union int|string accepts either type, and an intersection Countable&ArrayAccess demands an object implementing every listed interface; a DNF type such as (A&B)|null combines the two. never means the function always throws or exits, so analyzers (Debugging and Analysis) treat code after the call as dead.
| Type form | Example | Since |
|---|---|---|
| Union, mixed, static | int|string, mixed | 8.0 |
| Intersection, never | A&B, : never | 8.1 |
| DNF; null, false, true alone | (A&B)|null, : false | 8.2 |
<?php
declare(strict_types=1);
function formatId(int|string $id): string { return "ID-$id"; }
function size(Countable&ArrayAccess $c): int { return count($c); }
function fail(string $msg): never { throw new RuntimeException($msg); }
echo formatId(42), ' ', formatId('A100'), ' ', size(new ArrayObject([1, 2])), "\n";
foreach ([fn() => formatId(1.5), fn() => size(new SplMinHeap()), fn() => fail('disk full')]
as $call) {
try {
$call();
} catch (TypeError | RuntimeException $e) {
echo strstr($e->getMessage(), ', called', true) ?: $e->getMessage(), "\n";
}
}Output
ID-42 ID-A100 2 formatId(): Argument #1 ($id) must be of type string|int, float given size(): Argument #1 ($c) must be of type Countable&ArrayAccess, SplMinHeap given disk full
SplMinHeap is Countable but not ArrayAccess. The union prints as string|int: PHP normalizes types rather than echoing them as written.