0

PHP 8.0 — Throw as expression

Intermediate5 min read·php-09-009

Concept

PHP 8.0 made throw an expression — it can now be used anywhere an expression is valid: in ternary operators, arrow functions, null coalescing, and the right-hand side of &&/||. Previously, throw was a statement that could only appear on its own line.

Why this matters: It enables inline error handling without extracting helper functions or writing multi-line conditionals. Validation code becomes more compact and readable.

Common patterns:

  • $value ?? throw new InvalidArgumentException('Required') — throw in null coalescing: if $value is null, throw the exception.
  • $user ?: throw new UserNotFoundException() — throw in Elvis operator.
  • $valid || throw new ValidationException() — throw in short-circuit ||.
  • Arrow function: fn($x) => $x > 0 ? $x : throw new \InvalidArgumentException('Must be positive').
  • match arm: match($val) { 'ok' => 'fine', default => throw new \RuntimeException() }.

Idiomatic use: The most common practical use is the ?? throw pattern for required constructor arguments or for converting null returns from a repository into exceptions.

Code Example

php
<?php
declare(strict_types=1);

// throw in null coalescing — replace required field check
function createUser(array $data): array
{
    $name  = $data['name']  ?? throw new \InvalidArgumentException('name is required');
    $email = $data['email'] ?? throw new \InvalidArgumentException('email is required');
    return compact('name', 'email');
}
createUser(['name' => 'Alice', 'email' => 'alice@ex.com']); // ok
// createUser(['name' => 'Alice']); // throws: email is required

// throw in ternary
function getAge(array $user): int
{
    return is_int($user['age'] ?? null)
        ? $user['age']
        : throw new \InvalidArgumentException("Invalid age value");
}

// throw in arrow function
$parsePositive = fn(int $n) => $n > 0
    ? $n
    : throw new \InvalidArgumentException("Must be positive, got $n");

echo $parsePositive(5);  // 5
// $parsePositive(-1);   // throws

// throw in match
function getLabel(int $code): string
{
    return match($code) {
        200 => 'OK',
        404 => 'Not Found',
        500 => 'Server Error',
        default => throw new \RuntimeException("Unknown code: $code"),
    };
}

// throw with || (assert-style)
function assertPositive(int $n): int
{
    return ($n > 0) || throw new \RangeException("Expected positive, got $n");
    // Note: this returns the result of ||: either true or throws
    // For cleaner code, use: return $n > 0 ? $n : throw ...
}

// Repository pattern — throw on not found
class UserRepository
{
    private array $users = [1 => ['id' => 1, 'name' => 'Alice']];

    public function findOrFail(int $id): array
    {
        return $this->users[$id]
            ?? throw new \RuntimeException("User $id not found");
    }
}

$repo = new UserRepository();
echo $repo->findOrFail(1)['name']; // 'Alice'
// $repo->findOrFail(999);          // throws