0

PHP 8.0 — Union types (int|string|null)

Intermediate5 min read·php-09-002

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
<?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
}