match expression — no type coercion, exhaustive checks
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
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,
};