Simplify Conditional Logic in PHP with the match Expression
Why the match expression matters
When PHP 8.0 introduced match, it wasn't just syntactic sugar — it solved a class of bugs that switch silently encourages. The old construct uses loose comparison, falls through unless you remember break, and can't return a value directly. match uses strict === comparison, has no fall‑through, and is an expression, so you can assign its result or return it straight from a function.
Rule of thumb: If you reach forswitchto map a value to another value,matchis almost always the cleaner choice.
A typical switch that hurts
Imagine a service that translates HTTP status codes into domain‑specific exceptions. The classic implementation looks like this:
function exceptionForStatus(int $status): \Exception {
switch ($status) {
case 400:
return new BadRequestException();
case 401:
return new UnauthorizedException();
case 403:
return new ForbiddenException();
case 404:
return new NotFoundException();
case 500:
return new ServerException();
default:
return new GenericException($status);
}
}
Problems:
- Loose comparison means
'400'(string) would also match. - Missing
breakwould cause silent fall‑through — a maintenance nightmare. - You cannot write
return match ($status) { … };becauseswitchis a statement.
Refactoring with match
The same logic expressed with match becomes a single expression:
function exceptionForStatus(int $status): \Exception {
return match ($status) {
400 => new BadRequestException(),
401 => new UnauthorizedException(),
403 => new ForbiddenException(),
404 => new NotFoundException(),
500 => new ServerException(),
default => new GenericException($status),
};
}
Notice the differences:
- Strict
===comparison — no accidental string matches. - Each arm is an expression; the whole
matchreturns the created exception. - No
breakneeded, so the control flow is obvious.
Real‑world example: API error handling
In a recent project we consumed a third‑party payment gateway. Their JSON payload contains a result_code field that can be numeric or a short string like "INSUFFICIENT_FUNDS". We needed a unified PaymentError hierarchy for our domain layer.
final class PaymentErrorFactory {
public static function create(string|int $code, string $message): PaymentError {
return match (true) {
$code === 100 || $code === 'INSUFFICIENT_FUNDS' => new InsufficientFundsError($message),
$code === 200 || $code === 'CARD_EXPIRED' => new CardExpiredError($message),
$code === 300 || $code === 'FRAUD_DETECTED' => new FraudDetectedError($message),
default => new UnknownPaymentError($code, $message),
};
}
}
Using match (true) lets us evaluate arbitrary boolean conditions while still keeping the expression form. Each arm returns a concrete PaymentError instance, so the factory stays a pure function — easy to test and to reason about.
Gotchas and best practices
- Exhaustiveness: PHP does not enforce that all possible values are covered. Add a
defaultarm or let theUnhandledMatchErrorsurface during testing. - Type juggling: Because
matchuses strict comparison,0and'0'are distinct. Cast inputs early if you expect mixed types. - Complex arms: Keep each arm a single expression. If logic grows, extract a private method — readability wins over clever one‑liners.
Final thoughts
Switch statements served us well for two decades, but they carry legacy baggage. match gives you a modern, expression‑oriented tool that eliminates a whole category of subtle bugs and makes the intent of mapping logic obvious. Next time you catch yourself writing a switch that returns or assigns a value, pause and rewrite it with match. Your future self — and the next developer who reads the code — will thank you.