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 for switch to map a value to another value, match is 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 break would cause silent fall‑through — a maintenance nightmare.
  • You cannot write return match ($status) { … }; because switch is 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:

  1. Strict === comparison — no accidental string matches.
  2. Each arm is an expression; the whole match returns the created exception.
  3. No break needed, 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 default arm or let the UnhandledMatchError surface during testing.
  • Type juggling: Because match uses strict comparison, 0 and '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.