`switch` 是 PHP 中處理多條件分支的核心結構,但許多開發者僅知基礎用法,忽略了**鬆散比較陷阱**、**穿透特性妙用**、**效能優勢**等進階特性。本文深度解析 switch 執行機制、常見坑點、最佳實踐與實戰模式。
<?php
switch ($expr) {
case 'value1':
// code
break;
case 'value2':
// code
break;
default:
// code
}
?>
關鍵點:`switch` 使用 **鬆散比較 (`==`)**,非嚴格比較 (`===`)。這是最大坑點來源。
<?php
$val = '0'; // 字串 '0'
switch ($val) {
case 0: // 整數 0 -> 匹配! ('0' == 0 為 true)
echo "匹配整數 0\n";
break;
case '0': // 永遠不會執行
echo "匹配字串 '0'\n";
break;
case false: // 匹配! ('0' == false 為 true)
echo "匹配 false\n";
break;
case null: // 匹配! ('0' == null 為 true)
echo "匹配 null\n";
break;
case '': // 匹配! ('0' == '' 為 false, 但 '' == 0 為 true)
echo "匹配空字串\n";
break;
}
// 輸出: "匹配整數 0" (第一個匹配的 case)
?>
| 表達式 | == 0 | == '' | == false | == null | == '0' |
|---|---|---|---|---|---|
| 0 (int) | T | T | T | T | T |
| '0' (string) | T | F | T | T | T |
| '' (string) | T | T | T | T | F |
| false (bool) | T | T | T | T | F |
| null | T | T | T | T | F |
| 'abc' (string) | F | F | F | F | F |
<?php
// 方法 1: 強制轉型 (推薦)
switch ((string)$val) {
case '0': echo "字串 0"; break;
case '1': echo "字串 1"; break;
}
// 方法 2: 使用 match 表達式 (PHP 8.0+,嚴格比較 ===)
$result = match ((string)$val) {
'0' => '字串 0',
'1' => '字串 1',
default => '其他',
};
// 方法 3: 顯式比較 (避免 switch)
if ($val === '0') { /* ... */ }
elseif ($val === 0) { /* ... */ }
?>
**無 `break` 會繼續執行下一個 `case`**,這不是 Bug,是功能。
<?php
switch ($status) {
case 'pending':
case 'confirmed':
case 'processing':
$message = '訂單處理中...';
break;
case 'shipped':
case 'delivered':
$message = '訂單已發貨/送達';
break;
case 'cancelled':
case 'refunded':
$message = '訂單已取消/退款';
break;
default:
$message = '未知狀態';
}
?>
<?php
/**
* 解析記憶體限制字串 (如 '128M', '2G', '512K')
* 利用穿透特性累乘
*/
function parseMemoryLimit($val) {
$val = trim($val);
$unit = strtolower($val[strlen($val) - 1]);
$num = (int)$val;
switch ($unit) {
case 'g': $num *= 1024; // 故意無 break
case 'm': $num *= 1024; // 故意無 break
case 'k': $num *= 1024; // 故意無 break
// 無單位視為位元組
}
return $num;
}
// 測試
echo parseMemoryLimit('2G'); // 2147483648
echo parseMemoryLimit('512M'); // 536870912
echo parseMemoryLimit('1024K'); // 1048576
echo parseMemoryLimit('100'); // 100
?>
<?php
function processOrder($order, $targetStage) {
$stages = ['created', 'paid', 'packed', 'shipped', 'delivered'];
$currentIndex = array_search($order['stage'], $stages);
$targetIndex = array_search($targetStage, $stages);
if ($currentIndex === false || $targetIndex === false || $targetIndex <= $currentIndex) {
return false; // 無效或倒退
}
// 從當前階段穿透執行到目標階段
for ($i = $currentIndex + 1; $i <= $targetIndex; $i++) {
$stage = $stages[$i];
// 執行該階段邏輯...
executeStage($order, $stage);
}
return true;
}
?>
<?php
// 基準測試: 1000萬次迴圈
$iterations = 10_000_000;
$val = 'case_50';
// if-elseif 鏈
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
if ($val === 'case_1') {}
elseif ($val === 'case_2') {}
// ... 50 個條件
elseif ($val === 'case_50') {}
}
echo "if-elseif: " . (microtime(true) - $start) . "s\n";
// switch
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
switch ($val) {
case 'case_1': break;
case 'case_2': break;
// ... 50 個 case
case 'case_50': break;
}
}
echo "switch: " . (microtime(true) - $start) . "s\n";
// 典型輸出:
// if-elseif: 2.45s
// switch: 1.12s (快 2 倍左右)
?>
原理:`switch` 內部使用跳躍表或優化比較,`$expr` 只求值一次;`if-elseif` 每次都要重新求值條件表達式。
<?php
interface PaymentStrategy {
public function pay(float $amount): bool;
}
class AlipayStrategy implements PaymentStrategy {
public function pay(float $amount): bool { /* ... */ return true; }
}
class WechatStrategy implements PaymentStrategy {
public function pay(float $amount): bool { /* ... */ return true; }
}
class UnionPayStrategy implements PaymentStrategy {
public function pay(float $amount): bool { /* ... */ return true; }
}
class PaymentFactory {
private static $strategies = [];
public static function get(string $type): PaymentStrategy {
return self::$strategies[$type] ??= self::create($type);
}
private static function create(string $type): PaymentStrategy {
// 這裡用 switch 做工廠路由
return match ($type) {
'alipay' => new AlipayStrategy(),
'wechat' => new WechatStrategy(),
'unionpay' => new UnionPayStrategy(),
default => throw new InvalidArgumentException("不支付類型: $type"),
};
}
}
// 使用
$payment = PaymentFactory::get('alipay');
$payment->pay(100.00);
?>
<?php
// match 優勢: 嚴格比較(===)、表達式(有返回值)、無穿透、模式匹配
$status = 'shipped';
$message = match ($status) {
'pending', 'confirmed' => '處理中',
'shipped', 'delivered' => '運輸中',
'cancelled', 'refunded' => '已關閉',
default => throw new Exception("未知狀態: $status"),
};
// 複雜條件
$result = match (true) {
$score >= 90 => '優秀',
$score >= 80 => '良好',
$score >= 60 => '及格',
default => '不及格',
};
// 類型匹配 (PHP 8.0+)
$handler = match (get_class($object)) {
User::class => new UserHandler(),
Order::class => new OrderHandler(),
default => new DefaultHandler(),
};
?>
// ❌ 反模式 1: 在 case 中做複雜邏輯
switch ($type) {
case 'A':
// 50 行業務代碼...
break;
}
// ✅ 正確: 提取方法/策略類
switch ($type) {
case 'A': handleTypeA($data); break;
}
// ❌ 反模式 2: 巢狀 switch (難以維護)
switch ($a) {
case 1:
switch ($b) { ... }
}
// ✅ 正確: 扁平化、提取函數、狀態機
// ❌ 反模式 3: switch 替代多態
switch ($animal->type) {
case 'dog': $animal->bark(); break;
case 'cat': $animal->meow(); break;
}
// ✅ 正確: 多態
$animal->makeSound(); // 各子類實現
?>
掌握 `switch` 的比較語義、穿透機制、效能特性,配合 PHP 8 `match` 表達式,能寫出更安全、高效、可維護的分支邏輯。核心原則:**類型安全第一,穿透有意圖,複雜度受控。**