PHP 8.0 — Union types (int|string|null)
Concept
Union types (PHP 8.0) allow a parameter, property, or return type to accept more than one type. The syntax is TypeA|TypeB. They replace the common pattern of using mixed or PHPDoc @param int|string $value with a machine-enforced type declaration.
Syntax: int|string, int|float|null, Foo|Bar|null. Union with null can also be written as ?Foo (shorthand for Foo|null). void cannot be used in a union. never (PHP 8.1) can only be a standalone return type.
int|false and int|null: Common unions for functions that return a value on success or a sentinel on failure. PHP's own standard library uses int|false for functions like strpos (returns position or false).
Type widening rules: A child class method can widen the return type (return more specific types = narrowing) and widen the parameter types (accept more = widening). Union types enable this: a child can override a method to accept int|string where the parent accepted only int.
never return type (PHP 8.1): For functions that never return normally (always throw or call exit()). Useful for abort(), redirect(), or helper functions that always throw.
Code Example
<?php
declare(strict_types=1);
// Function accepting int or string ID
function findUser(int|string $id): array|null
{
if (is_int($id)) {
return ['id' => $id, 'found_by' => 'integer'];
}
return ['id' => $id, 'found_by' => 'string uuid'];
}
$user1 = findUser(42); // int
$user2 = findUser('a1b2-c3d4-...'); // string UUID
// Union with false — mirroring standard library pattern
function parseInteger(string $value): int|false
{
if (!ctype_digit($value)) return false;
return (int) $value;
}
$result = parseInteger('42');
if ($result === false) {
echo "Not a valid integer\n";
} else {
echo "Parsed: $result\n";
}
// Property union types
class Config
{
public int|string $timeout = 30; // can be seconds (int) or '30s' (string)
public array|null $tags = null; // nullable array
public int|float $rate = 1.0; // int or float rate
}
// Return type with union
function divide(float $a, float $b): float|false
{
if ($b == 0.0) return false;
return $a / $b;
}
// DNF types (PHP 8.2) — intersection within union: (A&B)|null
interface Countable {}
interface Iterable {}
// function process((Countable&Iterable)|null $collection): void {}
// never return type — function that always throws
function abort(int $statusCode, string $message): never
{
throw new \RuntimeException("HTTP $statusCode: $message");
}
function findOrAbort(int $id): array
{
$user = findUserById($id);
if ($user === null) {
abort(404, "User $id not found"); // never returns
}
return $user; // type system knows $user is not null here
}