Simplify Conditional Logic in PHP with the match Expression
Why I Switched to match
For years I reached for switch or long if/elseif chains when a value could map to several outcomes. The syntax was verbose, the fall–through bugs were real, and the type juggling PHP does behind the scenes sometimes produced surprising results.
The Old Way: A Payment Gateway Example
Imagine a service that normalises responses from three different payment providers. Each provider returns a status string — 'approved', 'declined', 'pending' — but they also use their own codes like 'AUTH_OK' or 'ERR_INSUFFICIENT'. The legacy code looked like this:
function normalizeStatus(string $raw): string {
switch ($raw) {
case 'approved':
case 'AUTH_OK':
case 'SUCCESS':
return 'approved';
case 'declined':
case 'ERR_INSUFFICIENT':
case 'FAILED':
return 'declined';
case 'pending':
case 'AWAITING':
return 'pending';
default:
throw new InvalidArgumentException("Unknown status: $raw");
}
}
It works, yet the repeated case lines make it hard to see the mapping at a glance, and the switch uses loose comparison, so '0' would match integer 0.
Enter the match Expression
PHP 8 introduced match, an expression that returns a value, uses strict === comparison, and does not fall through. The same logic becomes:
function normalizeStatus(string $raw): string {
return match ($raw) {
'approved', 'AUTH_OK', 'SUCCESS' => 'approved',
'declined', 'ERR_INSUFFICIENT', 'FAILED' => 'declined',
'pending', 'AWAITING' => 'pending',
default => throw new InvalidArgumentException("Unknown status: $raw"),
};
}
Notice the comma–separated list on the left side — each value is compared strictly, and the right side is a single expression. The function now reads like a lookup table.
Why It Matters in Production
- Strict typing –
matchuses===, so'0'never equals0. This eliminates a whole class of bugs that only appear under odd input. - Expression, not statement – You can assign the result directly, return it, or embed it inside a larger expression without extra variables.
- Exhaustiveness checking – When combined with a
defaultarm that throws, static analysers like Psalm or PHPStan can warn you if a new enum value is added but not handled. - Readability – The mapping is declarative; future maintainers see the intent instantly.
Tip: If you model the external codes as a PHPenum, you can drop thedefaultarm entirely and let the type system guarantee coverage.
Real–World Refactor
In a recent project we replaced a 150–line switch that translated HTTP response codes into domain exceptions. The refactor cut the method to 30 lines, and the test suite caught a hidden bug where the string '404' was being treated as integer 404 because of loose comparison. After the change the bug disappeared and the CI pipeline started flagging any missing case automatically.
Gotchas to Keep in Mind
matchdoes not support complex conditions like$x > 5. For those you still needif.- Each arm must be a single expression. If you need multiple statements, wrap them in a closure or extract a helper function.
- The
defaultarm is optional, but omitting it makes the expression non–exhaustive; the engine will throw aUnhandledMatchErrorat runtime if no arm matches.
Wrapping Up
Adopting match has become a default habit for me whenever I see a value–to–value mapping. It reduces boilerplate, enforces strict comparison, and plays nicely with static analysis tools. Next time you reach for a switch, ask yourself whether a match would express the same intent more cleanly — chances are it will.