想學 PHP,卻被 $、大括號和一長串英文函數嚇到?先別急著關掉分頁,PHP 不會因為你喝咖啡喝得慢就罷工。這篇會用小範例帶你從輸出文字、變數、條件判斷與迴圈,一路走到陣列、函數和生成器。

讀完後,你應該能看懂基本 PHP 程式,知道每個語法在做什麼,也能自己動手改出幾個小練習。重點不是把整本手冊背下來,而是建立一張之後查文件時看得懂的地圖。

先認識 PHP 程式

PHP 是在伺服器端執行的腳本語言,常用來產生動態網頁內容。瀏覽器通常只會收到 PHP 執行後的結果,不會直接看到伺服器上的 PHP 原始碼。

開始標籤與結束標籤

PHP 程式通常以 <?php 開始。若檔案全部都是 PHP,結束標籤 ?> 可以省略;這能避免檔案結尾不小心多出空白,進而干擾標頭或其他輸出。

<?php

echo 'Hello, PHP!';

PHP 除了能輸出純文字,也可以和 HTML 混用:

<!doctype html>
<html lang="zh-Hant">
<body>
	<h1><?php echo '今天也要好好寫程式'; ?></h1>
</body>
</html>

echoprint 與註解

echoprint 都能輸出內容。echo 可以接受多個引數;print 只接受一個引數,而且會回傳 1。一般輸出文字時,使用 echo 就很足夠,不必為了選邊站讓咖啡冷掉。

<?php

echo 'PHP ', '入門';
print ',先從小範例開始。';

// 這是單行註解。

/*
 * 這是多行註解。
 * 註解不會被 PHP 執行。
 */

變數與資料類型

變數命名

PHP 變數以 $ 開頭,名稱必須以字母或底線開頭,之後可以接字母、數字或底線。PHP 變數名稱區分大小寫,所以 $name$Name 是兩個不同的變數;這種差異不大,卻很會讓除錯時間變長。

<?php

$name = '小明';
$coffeeCount = 2;
$_note = '變數名稱可以底線開頭';

常見資料類型

初學時最常遇到的類型包括整數 int、浮點數 float、字串 string、布林值 bool、陣列 array、物件 objectnull 與資源 resource。使用變數前,不必先宣告它的類型;但在較大型的程式中,明確的型別宣告通常更容易維護。

<?php

$quantity = 3;              // int
$price = 49.5;               // float
$title = 'PHP 入門';         // string
$isPublished = true;         // bool
$keywords = ['php', 'web'];  // array
$nothing = null;             // null

作用域與超全域變數

變數的作用域決定它在哪裡可用:

  • 函數外宣告的變數屬於全域作用域。
  • 函數內宣告的變數屬於區域作用域,函數結束後就不能直接從外部使用。
  • 函數內的 static 變數會在多次呼叫之間保留值。

雖然可以在函數內使用 global 讀取全域變數,但傳遞參數通常更清楚,也較容易測試。全域變數像是把所有東西都放在客廳桌上:找得到,但久了真的很難整理。

PHP 也提供超全域變數,例如 $_GET$_POST$_SESSION$_COOKIE$_SERVER。它們在不同作用域都能使用,但「能直接使用」不代表「可以直接信任」。

<?php

$name = $_GET['name'] ?? '訪客';
$safeName = htmlspecialchars($name, ENT_QUOTES, 'UTF-8');

echo "你好,{$safeName}!";

輸入資料應先驗證,再依輸出情境進行處理。上例用 htmlspecialchars() 輸出 HTML 文字,可以降低未處理輸入造成 XSS 的風險;若資料要存進資料庫,則應使用資料庫擴充套件提供的參數化查詢,不要把字串直接拼進 SQL。

條件判斷與運算子

ifelseifelse

條件判斷讓程式可以依資料做不同決定。if 條件成立時執行第一段;不成立時,PHP 會繼續檢查 elseif,最後才執行 else

<?php

$score = 85;

if ($score >= 90) {
	echo '優秀';
} elseif ($score >= 60) {
	echo '及格,繼續加油';
} else {
	echo '再練習一次';
}

比較與邏輯運算子

== 只比較值,可能觸發型別轉換;=== 同時比較值與型別。除非你確實需要寬鬆比較,否則通常優先使用 ===!==

常用邏輯運算子包括:&&(而且)、||(或者)以及 !(反轉布林值)。

<?php

$age = 20;
$hasTicket = true;

if ($age >= 18 && $hasTicket === true) {
	echo '可以入場';
}

switch

當同一個值需要和多個固定選項比較時,switch 可以讓程式更容易閱讀。每個 case 通常要用 break 結束;漏寫時會繼續執行下一個 case,這種「順便多做一點」通常不是驚喜。

<?php

$day = 'Monday';

switch ($day) {
	case 'Monday':
		echo '星期一';
		break;
	case 'Friday':
		echo '星期五';
		break;
	default:
		echo '其他日子';
}

迴圈:讓重複工作交給電腦

forwhiledo...while

知道大致重複次數時,可以用 for;只知道「條件成立就繼續」時,可以用 whiledo...while 會先執行一次內容,再檢查條件。

<?php

for ($i = 1; $i <= 3; $i++) {
	echo "第 {$i} 次\n";
}

$count = 0;
while ($count < 3) {
	$count++;
}

do {
	echo '至少執行一次';
} while (false);

foreachbreakcontinue

foreach 適合逐一處理陣列元素。需要索引或鍵和值時,可以同時寫成 $key => $valuecontinue 跳過本輪,break 則離開整個迴圈。

<?php

$colors = ['紅', '綠', '藍'];

foreach ($colors as $index => $color) {
	if ($color === '綠') {
		continue;
	}

	echo "{$index}: {$color}\n";
}

陣列

PHP 的陣列同時具備有序列表與關聯式映射的用途。數值陣列通常從索引 0 開始;關聯陣列則使用有意義的鍵名。

<?php

$fruits = ['蘋果', '香蕉'];
$fruits[] = '芒果';

$user = [
	'name' => '小明',
	'email' => 'ming@example.com',
];

echo $user['name'];

unset() 可以移除元素,sort() 會以升冪排序數值索引陣列;排序函數各自對索引與鍵名有不同影響,使用前要先看文件,不要讓水果在陣列裡突然失去身份證。

foreach 與陣列函數

array_filter() 會保留回呼函數判定為真的元素,array_map() 則把回呼函數套用到每個元素。這兩個函數很適合把「篩選」和「轉換」寫得清楚。

<?php

$numbers = [1, 2, 3, 4, 5];

$evenNumbers = array_filter(
	$numbers,
	fn (int $number): bool => $number % 2 === 0
);

$squares = array_map(
	fn (int $number): int => $number * $number,
	$numbers
);

其他常見工具還有 array_merge()(合併)、array_slice()(取出一段)以及 array_diff()array_intersect()(比較差異或交集)。使用這些函數前,先確認它們如何處理數值索引與重複值。

函數:把重複工作包起來

參數與回傳值

函數把一段可重複使用的邏輯包起來。參數是輸入,return 是輸出;好的函數通常有清楚的名稱,也不偷偷修改外部狀態。

<?php

function greet(string $name): string
{
	return "你好,{$name}!";
}

echo greet('PHP');

匿名函數與閉包

匿名函數沒有固定名稱,常作為 array_map() 等函數的回呼。它也可以用 use 捕捉建立當下作用域的變數。

<?php

$prefix = 'Hi';
$greet = function (string $name) use ($prefix): string {
	return "{$prefix}, {$name}!";
};

echo $greet('PHP');

PHP 也支援短箭頭函數 fn,適合只有一個表達式的簡短回呼;若邏輯需要多行,普通匿名函數通常更好讀。

型別宣告與嚴格模式

函數可以為參數與回傳值加入型別。declare(strict_types=1); 必須放在檔案最前面(在 <?php 後),可讓該檔案中呼叫函數時的純量型別檢查採取嚴格模式。

<?php

declare(strict_types=1);

function add(int $a, int $b): int
{
	return $a + $b;
}

echo add(5, 5);

型別宣告能提早暴露錯誤,但不能取代輸入驗證。來自表單或 API 的資料仍然要先檢查、轉換,必要時再拒絕不符合預期的值。

可變參數與參照

...$numbers 可以接收任意數量的引數,函數內會得到一個陣列。參數前加 & 則是以參照傳遞,函數可以直接修改呼叫端的變數;這是刻意的行為,使用時要讓讀者一眼看出來。

<?php

function sum(int ...$numbers): int
{
	return array_sum($numbers);
}

function addExclamation(string &$text): void
{
	$text .= '!';
}

$message = '完成';
addExclamation($message);
echo sum(1, 2, 3);

生成器:需要時才產生資料

生成器使用 yield 一次產生一個值。呼叫生成器函數時,不會立刻把所有結果放進陣列;搭配 foreach 逐項讀取時,適合處理不想一次載入記憶體的資料。

<?php

function numbers(int $max): Generator
{
	for ($i = 0; $i < $max; $i++) {
		yield $i;
	}
}

foreach (numbers(3) as $number) {
	echo $number . PHP_EOL;
}

這不代表生成器自動讓所有程式變快,也不代表資料庫查詢就能無限吃。它只是提供「逐步取得值」的迭代方式;實際效益仍要看資料來源和程式的瓶頸。

10 個小練習

不要只看範例點頭,手指動起來才算真的學會。你可以依序完成:

  1. 輸出你的名字與今天的日期。
  2. 宣告商品名稱、價格與數量,計算小計。
  3. 判斷一個數字是正數、負數或零。
  4. switch 根據星期顯示工作日或休息日。
  5. for 輸出 1 到 10 的偶數。
  6. foreach 計算陣列中的數字總和。
  7. 將一個關聯陣列安全地輸出成 HTML。
  8. array_filter() 找出價格低於 100 的商品。
  9. 寫一個有參數型別與回傳型別的折扣函數。
  10. 用生成器逐一產生 1 到 100 的數字。

遇到錯誤時,先讀錯誤訊息,再縮小問題範圍;不要一看到紅字就把整杯咖啡灌進鍵盤。完成這 10 題後,可以接著學習類別與物件、例外處理、資料庫、HTTP、測試與框架。PHP 的世界很大,但你現在已經有一張能走路的地圖。

參考資料