switch and match
When one value decides between many outcomes, PHP offers two constructs. The classic switch statement runs code for a matching case. The newer match expression (PHP 8.0) returns a value, compares strictly, and refuses to silently ignore unexpected values — it's usually the better choice.
The switch statement
<?php $day = 3; switch ($day) { case 1: echo "Monday\n"; break; case 2: echo "Tuesday\n"; break; case 3: echo "Wednesday\n"; break; default: echo "Another day\n"; }
Wednesday
switch compares the value with each case and starts running at the first match. default handles everything else.
Fall-through
Without break, execution falls through into the next case and keeps going. Sometimes that's useful — listing several cases that share code — but forgetting a break is a classic bug:
<?php function season(string $month): string { switch ($month) { case "Dec": case "Jan": case "Feb": return "winter"; case "Jun": case "Jul": case "Aug": return "summer"; default: return "spring or autumn"; } } echo season("Jan"), "\n"; $level = 1; switch ($level) { case 1: echo "Beginner\n"; // forgot break! case 2: echo "Intermediate\n"; break; }
winter Beginner Intermediate
Inside a function, return ends the case just as well as break.
switch compares loosely
switch uses ==, so type juggling applies — another source of surprises:
<?php $input = "1"; switch ($input) { case 1: echo "matched the integer 1\n"; break; case "1": echo "matched the string \"1\"\n"; break; }
matched the integer 1
The match expression
match compares a value against arms and returns the result of the first match. Each arm is conditions => result, separated by commas:
<?php foreach ([1, 6, 7, 3] as $day) { $kind = match ($day) { 6, 7 => "weekend", 1, 2, 3, 4, 5 => "weekday", }; echo "$day: $kind\n"; }
1: weekday 6: weekend 7: weekend 3: weekday
Compared with switch, match:
- is an expression — it produces a value you can assign, return or echo;
- uses strict comparison (
===); - has no fall-through and needs no
break; - throws an error for unhandled values instead of quietly doing nothing.
<?php $status = 404; echo match ($status) { 200 => "OK", 301, 302 => "Redirect", 404 => "Not found", default => "Unknown status", }, "\n"; echo match ("1") { 1 => "integer", "1" => "string", }, "\n"; echo match (418) { 200 => "OK", 404 => "Not found", };
Not found
string
Fatal error: Uncaught UnhandledMatchError: Unhandled match case 418 in main.php:17
Stack trace:
#0 {main}
thrown in main.php on line 17That last error is a feature: if a new status code appears that your code doesn't know about, you find out immediately instead of getting silent wrong behaviour. Add a default arm when there genuinely is a sensible fallback.
match(true) for ranges
match compares values, so ranges need a trick: match against true, and write a condition in each arm. The first condition that's true wins:
<?php foreach ([-5, 8, 19, 27, 38] as $t) { $feel = match (true) { $t < 0 => "freezing", $t < 15 => "cold", $t < 25 => "mild", $t <= 35 => "warm", default => "dangerously hot", }; echo "$t °C: $feel\n"; }
-5 °C: freezing 8 °C: cold 19 °C: mild 27 °C: warm 38 °C: dangerously hot
Arms can run code
An arm's result can be any expression — a function call, a calculation, even a throw:
<?php function price(string $size): float { return match ($size) { 'S' => 2.40, 'M' => 2.90, 'L' => round(2.90 * 1.15, 2), default => throw new InvalidArgumentException("Unknown size '$size'"), }; } echo price('M'), "\n"; echo price('L'), "\n"; echo price('XL'), "\n";
2.9
3.33
Fatal error: Uncaught InvalidArgumentException: Unknown size 'XL' in main.php:9
Stack trace:
#0 main.php(15): price('XL')
#1 {main}
thrown in main.php on line 9For a fixed set of values like sizes, an enum (lesson 40) combined with match is even better.
Which one to use?
- match — when you're choosing a value. This covers most cases.
- switch — when several cases need to run multiple statements, or you deliberately want fall-through.
- if/elseif — when the conditions test different things.
Try it yourself
Turn HTTP status codes into categories with match(true). Add a category for redirects (300–399).
200: success 201: success 404: client error 503: server error 99: unknown