Learning Objectives
- 用 if、elseif、else 依條件選擇要執行的程式,並說明「由上往下、第一個成立就離開」的規則
- 說出哪些值當成條件時是 false(0、0.0、""、"0"、空陣列、null),並注意 "0.0" 與 [0] 是 true
- 用提早 return 取代多層巢狀 if,讓程式比較好讀
- 在 HTML 裡用替代語法 if: … endif; 控制要輸出哪一段
- 使用 switch(寬鬆比較、break、多個 case 共用、default)與 match(嚴格比較、有回傳值),並說明兩者的差別
讓程式依情況做不同的事
到目前為止,程式都是從第一行執行到最後一行。真正的程式常常要「看情況」: 分數 90 以上給 A、購物滿千免運、登入的人看到自己的名字、沒登入的人看到登入按鈕。 控制結構(control structure)讓你寫出這種「如果…就…否則…」的邏輯:
if ($score >= 60) {
echo '及格';
} else {
echo '不及格';
}
本課介紹 PHP 的三種選擇方式:
| 寫法 | 適合 |
|---|---|
if/elseif/else |
任何條件,最常用 |
switch |
一個值和好幾個固定的值比對(舊寫法,很多既有程式在用) |
match |
和 switch 一樣用途,但比較嚴格、可以直接得到一個值(PHP 8.0 起) |
逐步執行動畫裡,注意黃色箭頭跳過了哪幾行:條件不成立的區塊不會執行,箭頭會直接跳過去。
if、elseif、else 與巢狀 if
$score 是 78。PHP 從第 4 行開始由上往下一個一個檢查條件,找到第一個成立的條件(第 8 行的 >= 70),執行它的區塊後就離開整個 if,後面的 else 不會再看。第 18 行開始是巢狀 if:外層條件成立,才會進去檢查內層。
條件的順序很重要;elseif 和 else if
elseif 鏈只會執行第一個成立的區塊,所以範圍大的條件要放後面。下面的寫法永遠不會得到 B:
if ($score >= 60) { // 85 分在這裡就成立了
$grade = 'D';
} elseif ($score >= 80) { // 永遠輪不到
$grade = 'B';
}
elseif 也可以分開寫成 else if,用大括號時兩者效果相同(else if 其實是「else 後面接一個新的 if」)。
唯一的差別在後面會介紹的替代語法:用冒號 elseif: 的時候不能寫成 else if:。本課一律寫 elseif。
區塊只有一行時可以省略大括號(if ($x > 0) echo '正數';),但之後加第二行時很容易出錯,建議一律寫大括號。
哪些值會被當成 false
if 的條件不一定要是 true/false,任何值都可以放進去,PHP 會先把它轉成布林值。
下面這些值會被當成 false,其他所有值都是 true:
| 型別 | 當成 false 的值 |
|---|---|
| 布林 | false |
| 整數 | 0 |
| 浮點數 | 0.0、-0.0 |
| 字串 | 空字串 "" 和 "0" |
| 陣列 | 空陣列 [] |
| null | null(包括沒有設定過的變數) |
容易猜錯的是字串:只有 "" 和 "0" 是 false,"0.0"、" "(一個空白)、"false" 都是 true;
陣列也一樣,[0] 有一個元素,所以是 true。
這會造成一個常見的錯誤:使用者在表單裡輸入 0,if ($input) 卻把它當成「沒有輸入」。
要判斷「是不是空字串」時,直接寫 $input !== ''(第 04 課的嚴格比較),不要依賴真假值的轉換。
想一想:條件與真假值
下面的程式會印出什麼?
<?php
$a = "0.0";
$b = "0";
$c = [0];
$d = 0.0;
echo $a ? 'T' : 'F', $b ? 'T' : 'F', $c ? 'T' : 'F', $d ? 'T' : 'F';
下面的程式會印出什麼?
<?php
$x = 85;
if ($x >= 60) {
echo 'D';
} elseif ($x >= 80) {
echo 'B';
} else {
echo 'F';
}
巢狀太深時:提早 return
條件一多,if 很容易一層包一層,讀的人要一直記住「現在在哪一層」。
寫成函式(function)的時候,可以先把特殊情況處理掉、直接 return,剩下的程式就不用再包在 else 裡。
這種寫法叫提早 return(early return),也有人叫「守衛子句」(guard clause)。
下面的範例用到函式:function 名稱(參數) { … } 定義一段可以重複呼叫的程式,return 把結果交回呼叫的地方,同時結束函式。
函式的完整寫法在第 07 課介紹,這裡先看兩種寫法的差別。
同一個運費規則,兩種寫法
運費規則:金額不合理回傳 -1;會員免運;滿 1000 元免運;其他收 60 元。feeNested 用巢狀 if,feeEarly 每遇到一種情況就直接 return。看動畫時注意:return 一執行,函式就結束,後面的程式不會再跑。
替代語法:和 HTML 混寫
PHP 常常夾在 HTML 裡,決定某一段 HTML 要不要輸出。用大括號的話,<?php } ?> 很難看出是在結束哪個區塊。
PHP 提供另一種寫法:左大括號換成冒號,右大括號換成 endif;:
<?php if ($isLoggedIn): ?>
<p>歡迎回來</p>
<?php elseif ($isGuest): ?>
<p>訪客你好</p>
<?php else: ?>
<a href="login.php">登入</a>
<?php endif; ?>
<?= $name ?> 是 <?php echo $name; ?> 的簡寫。?> 和 <?php 之間的 HTML 會原封不動送出,
條件不成立時,整段 HTML 都不會出現在輸出裡。迴圈也有同樣的寫法(foreach: … endforeach;),第 06 課介紹。
用條件決定輸出哪一段 HTML
看輸出區:已登入時只出現歡迎訊息,else: 之後的「登入」連結不會輸出。?> 後面緊接的一個換行會被 PHP 吃掉,所以 <?php … ?> 自己那一行不會在輸出裡留下空行。
switch:一個值和好幾個值比對
當你要拿同一個值和好幾個固定的值比對時,可以用 switch:
switch ($day) {
case 'sat':
case 'sun': // 多個 case 共用同一段程式
$type = '週末';
break; // 跳出 switch
case 'mon':
$type = '星期一';
break;
default: // 前面都不相符時
$type = '平日';
}
使用 switch 要注意三件事:
- 找到相符的 case 之後會一路往下執行,直到遇到
break或 switch 結束,不管下面的 case 是什麼。這叫 fall-through。 刻意利用時(例如上面的 sat、sun 共用),最好加一行註解說明;忘了寫break是最常見的錯誤。 - switch 用寬鬆比較
==:字串'1'和整數1會被當成相符。 default可以省略;省略時沒有相符的 case,switch 就什麼都不做。
fall-through、寬鬆比較與 switch (true)
$day 是 'fri'。第 9 行的 case 相符後,因為沒有 break,箭頭會繼續走到 mon、tue 的內容。最後一段是 switch (true):拿 true 和每個 case 的條件比對,第一個成立的條件就是相符的 case,常用來判斷範圍。
match:比較嚴格、而且有回傳值
PHP 8.0 加入 match。它和 switch 的用途一樣,但整個 match (…) { … } 是一個運算式(expression),
會算出一個值,可以直接指派給變數或 echo:
$message = match ($status) {
200, 201 => '成功', // 多個值用逗號分開
404 => '找不到頁面',
default => '其他狀態',
}; // 注意結尾有分號
| switch | match | |
|---|---|---|
| 比較方式 | 寬鬆 ==('1' 和 1 相符) |
嚴格 ===(型別也要相同) |
| 結果 | 敘述,不會產生值,要在 case 裡自己指派 | 運算式,直接得到一個值 |
| 多個值共用 | 連續寫多個 case |
同一行用逗號 200, 201 => |
| break | 要自己寫,忘了會 fall-through | 不需要,只執行一個分支 |
| 沒有相符也沒有 default | 什麼都不做 | 丟出 UnhandledMatchError |
| 每個分支 | 可以寫很多行 | 只能是一個運算式 |
match (true) 和 switch (true) 一樣可以寫範圍判斷。要一個分支裡做很多事,或需要 fall-through 的時候,用 switch 或 if;
只是「依某個值得到另一個值」時,match 比較短,也比較不容易出錯。
match 的四種情況
第 12 行的 $input 是字串 '1':switch 會和 1 相符,match 用 === 比較,所以跳過 1、選中 '1'。最後一段刻意示範沒有相符的分支、也沒有 default:程式以 Fatal error 結束,UnhandledMatchError 的訊息會指出是哪個值。
想一想:switch 與 match
下面的程式會印出什麼?
<?php
$n = 2;
switch ($n) {
case 1:
echo 'a';
case 2:
echo 'b';
case 3:
echo 'c';
break;
case 4:
echo 'd';
}
下面的程式會印出什麼?
<?php
$v = '5';
switch ($v) {
case 5:
echo 'A';
break;
default:
echo 'B';
}
echo match ($v) {
5 => 'A',
default => 'B',
};
下面的程式執行的結果是什麼?
<?php
$grade = 'C';
$point = match ($grade) {
'A' => 4,
'B' => 3,
};
echo $point;
動手做:計算停車費
停車場的收費規則如下,請完成程式,印出「車種 停車幾小時:多少元」。
- 停車 0 小時或負數:印出「時數不正確」
- 機車(
scooter)一律 20 元 - 汽車(
car)每小時 40 元,但超過 5 小時以 200 元計 - 其他車種:印出「不收這種車」
用 match 把車種換成中文名稱(機車、汽車,其他是 null),再用 if 計算費用。
重點回顧
if/elseif/else由上往下檢查,只執行第一個成立的區塊;範圍大的條件放後面- 當成 false 的值:
false、0、0.0、""、"0"、[]、null;"0.0"、" "、[0]都是 true - 判斷「是不是空字串」寫
$s !== '',不要只寫if ($s)(使用者輸入的"0"會被當成沒輸入) - 巢狀太深時,先處理特殊情況並提早
return - 與 HTML 混寫用替代語法
if: … elseif: … else: … endif; switch用寬鬆比較==,相符後會一路往下執行到break(fall-through);多個 case 可以共用一段程式match(PHP 8.0)用嚴格比較===、直接得到一個值、不需要 break;沒有相符又沒有default會丟出UnhandledMatchErrorswitch (true)、match (true)可以用來判斷範圍