0

match expression — no type coercion, exhaustive checks

Intermediate5 min read·php-05-003
compare

Concept

The match expression (PHP 8.0) is a concise, safe dispatch mechanism that solves the three main problems with switch: loose type coercion, unintentional fall-through, and non-exhaustiveness.

Strict equality: match uses === internally. match(0) will NOT match "0" or false. This makes it safe for dispatching on values where type matters — HTTP status codes, enums, typed constants.

Expression form: match returns a value and can be used inline in any expression context — assigned to a variable, returned from a function, embedded in string interpolation, or passed as a function argument. This eliminates the need for a mutable $result variable that gets assigned inside a switch.

No fall-through: Each arm is isolated. Multiple conditions share an arm via comma separation (1, 2 => 'low'), which is explicit intent rather than silent fall-through.

Exhaustiveness enforcement: Without a default arm, match throws \UnhandledMatchError for unmatched values. This is a feature — it forces you to handle every case or acknowledge the catch-all. In switch, a missing case silently continues execution after the switch block.

With enums (PHP 8.1+): match becomes the idiomatic way to dispatch on enum cases, especially when static analysis can verify exhaustiveness.

Code Example

php
<?php
declare(strict_types=1);

// Inline use — match as expression
function httpStatusText(int $code): string
{
    return match($code) {
        200 => 'OK',
        201 => 'Created',
        204 => 'No Content',
        301 => 'Moved Permanently',
        302 => 'Found',
        400 => 'Bad Request',
        401 => 'Unauthorized',
        403 => 'Forbidden',
        404 => 'Not Found',
        500 => 'Internal Server Error',
        default => "HTTP $code",
    };
}
echo httpStatusText(404); // "Not Found"

// Multiple conditions per arm
function classify(int $score): string
{
    return match(true) {           // match(true) pattern for range checks
        $score >= 90 => 'A',
        $score >= 80 => 'B',
        $score >= 70 => 'C',
        $score >= 60 => 'D',
        default      => 'F',
    };
}
echo classify(85); // "B"

// With enums
enum Status: int {
    case Active   = 1;
    case Inactive = 0;
    case Banned   = -1;
}

function statusLabel(Status $s): string
{
    return match($s) {
        Status::Active   => 'Active user',
        Status::Inactive => 'Inactive account',
        Status::Banned   => 'Account suspended',
        // No default — static analysis can verify all cases are covered
    };
}

// Nested match
$userType = 'admin';
$action   = 'delete';
$allowed = match($userType) {
    'admin'  => true,
    'editor' => match($action) {
        'read', 'write' => true,
        'delete'        => false,
        default         => false,
    },
    default  => false,
};