本工具使用的開源套件

本工具程式碼中捆綁了 1 個開源套件。

PHP 速查 — 簡明參考

PHP 8.3 語法、類型、數組與最常用標準庫速查手冊,覆蓋約 80% 日常場景。

Php

PHP PHP 8.3 (LTS 風格)

Zend Engine (PHP 運行時) · 命令式 · OO · 函數式 · 動態(帶可選靜態類型)

學習路徑

先會用 php hello.php 與 php -S 內置服務器跑起來 → 掌握變量、類型與流程控制 → 深入函數、閉包與數組 → 理解引用語義與對象句柄 → 用類、接口與異常組織代碼 → 再按需學文件 I/O、cURL、時間與正則 → 用 Fiber/pcntl 處理併發。FAQ 節適合回頭避坑。

1.Hello World 與運行環境

運行 PHP 程序、內置 Web 服務器、命令行參數與退出碼。

最小程序

PHP 文件以 <?php 開始,CLI 用 php 文件名運行。echo 輸出字符串。

1
2
3
4
5
6
7
8
9
echo "Hello, world!\n";
// 單引號不解析轉義與插值
echo 'Hello, world!\n';
echo "\n";
// 雙引號內變量插值
$name = 'PHP';
echo "Hello, $name!\n";

CLI 運行

php 命令直接運行腳本。php -l 檢查語法,php -r 直接執行代碼。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 運行腳本文件:
// $ php hello.php
// 輸出 Hello, world!
// 直接執行一行代碼:
// $ php -r 'echo "hi\n";'
// 檢查語法(不執行):
// $ php -l hello.php
// 啓動交互式 shell:
// $ php -a
// 查看版本:
// $ php -v

內置 Web 服務器

php -S 啓動內置服務器,把目錄當文檔根,適合本地開發調試。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 在項目目錄啓動:
// $ php -S localhost:8000
// 指定文檔根目錄:
// $ php -S localhost:8000 -t public
// 指定路由腳本:
// $ php -S localhost:8000 router.php
// 啓動後瀏覽器訪問:
// http://localhost:8000/index.php
// 端口被佔用時會啓動失敗

文件結構

<?php 與 ?> 標記切分 PHP 與 HTML。純 PHP 文件推薦省略結束標籤。

1
2
3
4
5
6
7
echo "純 PHP 文件頂部直接寫代碼\n";
// 建議省略結束標籤 ?>
// 避免文件末尾換行被當作輸出
// 混寫模板時用 ?> 切回 HTML:
// <p><?= $title ?></p>
// 短標籤 <?= 等價 echo

命令行參數

$argv 是參數數組,$argc 是參數個數,argv[0] 是腳本名。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 運行:$ php args.php a b c
var_dump($argc); // 4
var_dump($argv);
// ["args.php", "a", "b", "c"]
// 取業務參數:
$name = $argv[1] ?? 'guest';
echo "hello, $name\n";
// 遍歷所有參數:
foreach (array_slice($argv, 1) as $arg) {
echo "arg: $arg\n";
}

退出碼

exit() 或 die() 結束腳本並返回狀態碼,0 成功,非 0 出錯。

1
2
3
4
5
6
7
8
9
10
11
12
// 正常結束(返回 0):
exit(0);
// 帶錯誤信息退出:
die("發生錯誤\n"); // 同 exit
// 帶狀態碼退出:
exit(1); // 非 0 表示失敗
// 腳本自然結束等價 exit(0)
// CLI 判斷成敗:
// $ php run.php && echo "ok"

文件引入

require 引入必須存在的文件,include 失敗僅警告,_once 防止重複引入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 引入必須存在的文件:
require 'config.php';
// 引入可選文件(失敗僅警告):
include 'optional.php';
// 防重複引入:
require_once 'lib/functions.php';
include_once 'helper.php';
// 用 __DIR__ 拼絕對路徑:
require __DIR__ . '/../src/bootstrap.php';
// include 返回布爾值可判斷成敗

環境檢查

php -i 打印配置,php -m 列出擴展,phpinfo() 輸出完整信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 命令行查看配置:
// $ php -i
// 查看某個配置項:
// $ php -i | grep memory_limit
// 列出已加載擴展:
// $ php -m
// 確認擴展已安裝:
// $ php -m | grep mbstring
// 頁面輸出完整信息:
phpinfo();
// 生產環境務必關閉,避免泄露配置

2.變量與常量

變量聲明、常量、類型聲明、嚴格模式與作用域。

變量聲明

變量以 $ 開頭,無需聲明類型,首次賦值即創建,名字大小寫敏感。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// $ 前綴 + 名字,無類型聲明:
$count = 0;
$name = 'Rex';
$price = 19.99;
$active = true;
// 變量名大小寫敏感:
$Foo = 1;
$foo = 2; // 兩個不同變量
// 未定義變量讀取觸發警告:
// echo $undefined;
// 名字不能以數字開頭

值複製賦值

普通賦值按值複製,標量獨立。數組也是複製,但底層寫時複製優化。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 標量賦值互不影響:
$a = 5;
$b = $a;
$b = 10;
echo $a; // 仍是 5
// 數組賦值也是複製:
$arr1 = [1, 2, 3];
$arr2 = $arr1;
$arr2[0] = 99;
echo $arr1[0]; // 1(不受影響)
// 引用賦值用 &,見指針節

常量

const 定義命名空間常量,define() 定義全局常量,定義後不可修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 命名空間級常量:
const MAX_SIZE = 1024;
// 全局常量:
define('APP_NAME', 'demo');
// 常量可用於表達式:
$half = MAX_SIZE / 2;
// 判斷是否已定義:
defined('APP_NAME'); // true
// 動態讀取常量名:
constant('MAX_SIZE'); // 1024
// 約定常量名全大寫

魔術常量

__DIR__、__FILE__、__LINE__ 等編譯期常量在對應位置求值,常用於日誌。

1
2
3
4
5
6
7
8
9
10
11
echo __DIR__; // 文件所在目錄
echo __FILE__; // 文件完整路徑
echo __LINE__; // 當前行號
echo __FUNCTION__; // 當前函數名
echo __CLASS__; // 當前類名
echo __NAMESPACE__; // 當前命名空間
// 日誌記錄位置:
error_log(__FILE__ . ':' . __LINE__);
// __DIR__ 拼接路徑最安全

作用域與 static

函數內變量默認局部,global 引入全局變量,static 保留多次調用間的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$global_count = 0;
function countUp(): void
{
static $n = 0; // 調用間保留
$n++;
echo $n;
}
// 函數內訪問全局:
function readGlobal(): int
{
global $global_count;
return $global_count;
}
countUp(); // 1
countUp(); // 2

參數與返回類型

參數與返回值可聲明類型,弱模式下自動轉換,配合 strict_types 變嚴格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 參數與返回類型:
function add(int $a, int $b): int
{
return $a + $b;
}
// 可空類型 ?int:
function find(?int $id): ?array
{
return $id === null ? null : [$id];
}
// 聯合類型 int|float:
function area(int|float $w): float
{
return (float) $w * 2;
}

嚴格模式

declare(strict_types=1) 嚴格校驗類型,不匹配直接拋 TypeError。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 必須寫在文件第一行:
declare(strict_types=1);
function add(int $a, int $b): int
{
return $a + $b;
}
add(1, 2); // 3
// add('1', 2); // 嚴格模式拋 TypeError
// 弱模式下 '1' 自動轉成 1
// 每個文件單獨聲明,不跨文件繼承

命名約定

類與命名空間大駝峯,函數與變量小駝峯,常量全大寫,遵循 PSR-12。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 類名大駝峯:
class UserProfile {}
// 函數與變量小駝峯:
function getUserName() {}
$userName = 'Rex';
// 常量全大寫:
const MAX_ITEMS = 100;
// 私有成員可加下劃線:
class Demo
{
private $privateData;
}
// PSR-12:類大駝峯,方法小駝峯

3.數據類型

標量、數組、null、枚舉與類型系統,理解弱轉換與類型檢查。

標量類型

int、float、string、bool 四種標量。字符串是字節序列,中文按多字節處理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$age = 30; // int
$pi = 3.14; // float
$ok = true; // bool
$name = 'Rex'; // string
// 整數溢出自動變浮點:
$big = PHP_INT_MAX + 1; // float
// 字符串是字節序列:
$s = 'Hello, 世界';
// 顯式類型轉換:
(int) '42'; // 42
(string) 123; // '123'

數組

PHP 數組是有序映射,整數與字符串鍵,同時充當列表與字典。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 列表:
$colors = ['red', 'green', 'blue'];
// 字典:
$user = ['name' => 'Rex', 'age' => 5];
// 混合鍵:
$mixed = [1, 'a', true];
// 追加元素:
$colors[] = 'yellow';
// 取長度:
count($colors); // 4
// 鍵值順序即插入順序

null 與空值

null 表示沒有值。未賦值變量、顯式 null、函數返回 null 都是 null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$value = null; // 顯式 null
// 判斷是否為 null:
$value === null; // true
is_null($value); // true
// 可空參數類型:
function greet(?string $name): void
{
echo $name ?? 'guest';
}
// 用 ?? 提供默認值:
$name = $input['name'] ?? 'guest';

callable 與 iterable

callable 表示可調用,iterable 表示可遍歷的數組或 Traversable 對象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// iterable 參數可收數組或對象:
function sumAll(iterable $items): int
{
$total = 0;
foreach ($items as $n) {
$total += $n;
}
return $total;
}
sumAll([1, 2, 3]); // 6
// callable 收函數名或閉包:
function apply(callable $fn, int $x): int
{
return $fn($x);
}
apply(fn($x) => $x * 2, 5); // 10

mixed 與 void

mixed 表示任意類型,void 無返回值,never 表示永不返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// mixed:任意類型:
function debug(mixed $v): void
{
var_dump($v);
}
// void:無返回值:
function logMsg(string $msg): void
{
echo $msg . "\n";
}
// never:永遠不返回:
function abort(): never
{
throw new \RuntimeException('終止');
}
// never 函數必須拋異常或 exit

枚舉 enum

enum(8.1+)定義一組具名常量,純枚舉或帶值枚舉,可加方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 純枚舉:
enum Color
{
case Red;
case Green;
case Blue;
}
// 帶值枚舉:
enum Status: string
{
case Ok = 'ok';
case NotFound = '404';
}
// 取值與比較:
$s = Status::Ok;
$s->value; // 'ok'
$s === Status::Ok; // true
// 配合 match 做安全匹配

聯合與交集類型

int|float 表示兩者之一,交集類型要求同時滿足多個接口約束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 聯合類型(8.0+):
function format(int|float $n): string
{
return number_format((float) $n, 2);
}
// 可空簡寫 ?T 等價 T|null:
function maybe(?string $s): string
{
return $s ?? '空';
}
// 交集類型(8.1+):
function dump(\Countable&\Traversable $c): void
{
echo count($c);
}
// DNF 類型 8.2+:(A&B)|null

弱類型轉換

弱模式下運算時自動轉換類型,字符串數字比較規則特殊,見 FAQ。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 字符串與數字運算自動轉換:
$n = '3' + 2; // 5
$n = '3' * '2'; // 6
// 字符串強轉數字:
(int) '42px'; // 42
(float) '3.5.9'; // 3.5
// 布爾強轉:
(bool) '0'; // false
(bool) '0.0'; // true(非 '0')
// 嚴格模式下這些會報錯

類型檢查函數

is_int、is_string 等 is_* 系列判斷類型,返回布爾值。

1
2
3
4
5
6
7
8
9
10
11
12
is_int(3); // true
is_float(3.0); // true
is_string('x'); // true
is_bool(true); // true
is_array([1]); // true
is_null(null); // true
is_numeric('42'); // true(可轉數字)
is_callable('strlen'); // true
// 寬鬆判斷用 is_numeric:
is_int('42'); // false
is_numeric('42'); // true

4.引用與內存語義

PHP 沒有裸指針:變量引用、寫時複製、對象句柄與 null 處理。

引用是什麼

PHP 引用是變量的別名,兩個名字指向同一內容,一改俱改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 引用賦值,$b 成為 $a 的別名:
$a = 1;
$b = &$a;
$b = 2;
echo $a; // 2(同改)
// 數組元素也可引用:
$arr = [1, 2];
$last = &$arr[1];
$last = 99;
echo $arr[1]; // 99
// 引用不是地址指針
// 不能取地址、做指針運算

寫時複製

數組與對象賦值先共享數據,真正寫入時才複製,節省內存。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 賦值先共享,不立即複製:
$a = [1, 2, 3];
$b = $a;
echo $b[0]; // 1,只讀不復制
// 寫入時觸發複製:
$b[0] = 99;
echo $a[0]; // 1(不受影響)
// 大數組賦值很便宜:
// 只有修改才有複製開銷
// foreach 中修改建議另建數組

引用賦值

unset 只解除當前名字的引用,不影響其它別名指向的內容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$a = 'original';
$b = &$a;
// unset 只斷開 $b 的引用:
unset($b);
echo $a; // original(仍在)
// 數組元素間引用:
$data = [1, 2];
$alias = &$data[0];
$data[0] = 100;
echo $alias; // 100
// 觀察引用計數:
// debug_zval_dump($a)

引用參數

參數前加 & 按引用傳遞,函數內修改會反映到調用方變量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 引用參數修改原變量:
function increment(int &$n): void
{
$n++;
}
$x = 5;
increment($x);
echo $x; // 6
// 返回值帶狀態:
function parse(string $s, &$ok): int
{
$ok = is_numeric($s);
return (int) $s;
}
parse('42', $valid); // $valid 變 true

對象句柄

對象變量存的是句柄,賦值、傳參都指向同一實例,要副本用 clone。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Counter
{
public int $n = 0;
}
$c1 = new Counter();
$c2 = $c1; // 同一實例
$c2->n = 5;
echo $c1->n; // 5(共享)
// 獨立副本用 clone:
$c3 = clone $c1;
$c3->n = 0;
echo $c1->n; // 仍 5
// === 判斷是否同一實例:
$c1 === $c2; // true

null 處理

isset 判斷存在且非 null,is_null 判斷是否為 null,empty 判斷是否為空。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$arr = ['a' => null, 'b' => 1];
// isset:存在且非 null:
isset($arr['a']); // false(值是 null)
isset($arr['b']); // true
isset($arr['x']); // false(不存在)
// is_null:是否為 null:
is_null($arr['a']); // true
// 直接訪問不存在鍵會警告:
// $v = $arr['x'];
// 用 ?? 安全讀取:
$v = $arr['x'] ?? '默認';

空合併 ??

?? 左側不存在或為 null 時返回右側,可鏈式,可配合 throw。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 讀取可能不存在的鍵:
$name = $_GET['name'] ?? 'guest';
// 鏈式空合併:
$config = $a ?? $b ?? 'default';
// 與 throw 組合(8.0+):
$value = $input['key'] ?? throw new \InvalidArgumentException('缺 key');
// 不觸發未定義鍵警告:
// 直接下標訪問才會警告
// 賦值運算符 ??=(8.0+)

可變變量

$$var 用變量值作另一個變量的名字,可讀性差,建議用數組。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 可變變量:
$name = 'foo';
$foo = 42;
echo $$name; // 42(即 $foo)
// 數組方式更清晰:
$values = ['foo' => 42];
echo $values[$name]; // 42
// 嵌套可變變量易混淆:
// $$$name 幾乎無法閲讀
// 建議用關聯數組替代

unset 與內存

unset 釋放名字並遞減引用計數,歸零後內存可回收,循環引用由 GC 處理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 釋放大變量:
$big = str_repeat('x', 1000000);
unset($big); // 立刻可回收
// 釋放數組元素:
$arr = [1, 2, 3];
unset($arr[1]);
// 鍵不重排,值變 null
// 循環引用:
class Node
{
public ?Node $next = null;
}
$a = new Node();
$b = new Node();
$a->next = $b;
$b->next = $a; // 形成環
unset($a, $b);
// 由循環垃圾收集器清理

5.流程控制

條件、循環、switch、match 與 break/continue。

if / else

if 判斷條件執行分支,elseif 依次嘗試,else 兜底。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$n = 0;
if ($n > 0) {
echo "正數";
} elseif ($n === 0) {
echo "零";
} else {
echo "負數";
}
// 單行簡寫:
if ($n > 0) echo "正數";
// 用 return 提前退出:
// 減少嵌套層級

三元運算符

條件 ? 真值 : 假值 是表達式可賦值,嵌套會損害可讀性。

1
2
3
4
5
6
7
8
9
10
$score = 85;
$grade = $score >= 90 ? 'A' : 'B';
echo $grade; // B
// ?: 與 ?? 區別:
$x = $a ?: '默認'; // $a 為假時用默認
$y = $a ?? '默認'; // $a 為 null 時用默認
// 嵌套三元可讀性差:
// 建議拆成 if 或 match

switch 語句

switch 默認寬鬆比較,匹配後需 break 否則穿透,default 兜底。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$status = 200;
switch ($status) {
case 200:
case 201:
echo "成功";
break;
case 404:
echo "未找到";
break;
default:
echo "其它";
}
// case '0' 會寬鬆匹配 0,需注意
// 複雜判斷建議用 match

match 表達式

match(8.0+)嚴格比較有返回值,分支自動返回,無需 break。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$status = 404;
$label = match ($status) {
200, 201 => '成功',
404, 410 => '未找到',
default => '其它',
};
echo $label; // 未找到
// 無 default 且不匹配會拋
// UnhandledMatchError 異常
// match true 做條件判斷:
$result = match (true) {
$score >= 90 => 'A',
$score >= 80 => 'B',
default => 'C',
};

for 循環

for(初始化; 條件; 步進) 經典計數循環,步進可自由控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for ($i = 0; $i < 3; $i++) {
echo "$i ";
} // 0 1 2
// 遞減步進:
for ($i = 10; $i >= 0; $i -= 2) {
echo "$i ";
} // 10 8 6 4 2 0
// 多重循環:
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j < 2; $j++) {
echo "$i$j ";
}
}

foreach 遍歷

foreach 遍歷數組與對象,可同時取鍵值,引用修改需注意殘留。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$items = ['a' => 1, 'b' => 2];
// 只取值:
foreach ($items as $v) {
echo "$v ";
} // 1 2
// 取鍵值:
foreach ($items as $k => $v) {
echo "$k=$v ";
} // a=1 b=2
// 引用修改元素:
foreach ($items as &$v) {
$v *= 2;
}
unset($v); // 關鍵!斷開引用

while 與 do-while

while 先判斷後執行可能零次,do-while 至少執行一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// while 先判斷:
$n = 0;
while ($n < 3) {
echo $n++;
} // 012
// do-while 至少一次:
$i = 10;
do {
echo $i;
} while ($i < 3); // 10
// 逐行讀文件常見寫法:
$h = fopen('log.txt', 'r');
while (($line = fgets($h)) !== false) {
echo $line;
}
fclose($h);

break 與 continue

break 跳出循環或 switch,continue 跳到下一次迭代,可帶數字跳出多層。

1
2
3
4
5
6
7
8
9
10
11
12
13
for ($i = 0; $i < 10; $i++) {
if ($i % 2 === 0) continue; // 跳過偶數
if ($i > 5) break; // 提前結束
echo "$i ";
} // 1 3 5
// 跳出兩層循環:
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j < 3; $j++) {
if ($j === 1) break 2;
echo "$i$j ";
}
} // 00 10

6.函數與閉包

函數定義、默認參數、可變參數、命名參數、閉包與生成器。

函數定義

function 定義函數,參數帶類型,return 返回。函數名大小寫不敏感。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function add(int $a, int $b): int
{
return $a + $b;
}
// 調用:
add(1, 2); // 3
// 函數名大小寫不敏感:
ADD(1, 2); // 3
// 先調用後定義也可以:
sayHi();
function sayHi(): void
{
echo "hi\n";
}
// 重複定義同名函數致命錯誤

默認參數

參數可給默認值,調用時可省略。默認值須是常量表達式。

1
2
3
4
5
6
7
8
9
10
11
12
13
function greet(string $name, string $title = 'Mr.'): string
{
return "$title $name";
}
greet('Nick'); // Mr. Nick
greet('Anna', 'Ms.'); // Ms. Anna
// 默認值必須是常量表達式:
// 不能寫函數調用或變量
// 可選參數放在必選參數後面:
// function f($a, $b = 1, $c = 2)

可變參數

...$args 收集所有剩餘參數為數組,放在參數列表最後。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function sum(int ...$nums): int
{
return array_sum($nums);
}
sum(1, 2, 3); // 6
sum(10, 20); // 30
sum(); // 0
// 傳入數組需展開:
$arr = [1, 2, 3];
sum(...$arr); // 6
// 類型聲明約束每個元素:
// int ...$nums 保證全為 int

命名參數

調用時用 參數名: 值 傳參(8.0+),可跳過可選參數,順序不限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function makeProfile(
string $name,
string $email = '',
int $age = 0,
): array {
return [$name, $email, $age];
}
// 跳過 email 直接給 age:
makeProfile(
name: 'Rex',
age: 5,
);
// ['Rex', '', 5]
// 與數組解包結合:
$args = ['name' => 'Rex', 'age' => 5];
makeProfile(...$args);

返回類型

返回類型聲明在冒號後,支持 void、?T、聯合與數組。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function nothing(): void {} // 無返回值
function maybe(): ?int // 可為 null
{
return null;
}
function both(): int|string // 聯合返回
{
return 'ok';
}
function shape(): array // 返回數組
{
return ['name' => 'Rex'];
}
function never(): never // 永不返回
{
throw new \Exception('終止');
}
// 返回類型不匹配拋 TypeError

閉包

匿名函數用 function() 定義,use 捕獲外部變量,可賦給變量或作回調。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 匿名函數賦給變量:
$square = function (int $x): int {
return $x * $x;
};
$square(4); // 16
// use 按值捕獲:
$factor = 2;
$mul = function ($x) use ($factor): int {
return $x * $factor;
};
$mul(5); // 10
// use 按引用捕獲可修改外部:
$count = 0;
$inc = function () use (&$count): void {
$count++;
};
$inc();
echo $count; // 1

箭頭函數

fn() => 表達式 自動按值捕獲外層變量,單表達式簡潔,常用於回調。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$factor = 2;
$double = fn(int $x) => $x * $factor;
$double(10); // 20
// 數組回調更簡潔:
$nums = [1, 2, 3, 4];
$evens = array_filter(
$nums,
fn($n) => $n % 2 === 0,
); // [2, 4]
// 箭頭函數只有一個表達式:
// 多語句需用普通閉包
// 值捕獲,改外部需閉包 use &

可調用對象

8.1+ 用 strlen(...) 創建 first-class callable,把函數變成一等值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 一等可調用(8.1+):
$len = strlen(...);
$len('hello'); // 5
// 對象方法:
$c = new Counter();
$inc = $c->increment(...);
$inc();
// callable 類型收參數:
function run(callable $fn): void
{
echo $fn();
}
run(fn() => 'done');
// call_user_func 是舊寫法
// 新代碼優先用 (...) 語法

生成器

yield 逐項產出值的函數叫生成器,惰性求值,處理大數據省內存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function rangeGen(int $n): Generator
{
for ($i = 0; $i < $n; $i++) {
yield $i;
}
}
foreach (rangeGen(5) as $v) {
echo "$v "; // 0 1 2 3 4
}
// 生成器惰性,邊用邊產:
$gen = rangeGen(3);
$gen->current(); // 0
$gen->next();
$gen->current(); // 1
// 鍵值生成:
yield 'key' => 'value';

7.字符串

字符串字面量、插值、拼接、多字節處理與常用函數。

字符串字面量

單引號按原樣輸出,雙引號解析轉義與變量。常用兩種風格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 單引號原樣輸出:
echo 'Hello\n'; // 不轉義
// 雙引號解析轉義:
echo "Hello\n"; // 換行
// 雙引號變量插值:
$name = 'Rex';
echo "hi $name"; // hi Rex
// 單引號內的單引號需轉義:
echo 'it\'s';
// 多行文本用拼接或 sprintf
// heredoc 易受縮進影響

變量插值

雙引號內 $var 直接插值,複雜表達式用花括號 {$arr['k']}。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$name = 'Rex';
$age = 5;
// 簡單變量:
echo "姓名 $name"; // 姓名 Rex
// 數組元素用花括號:
$user = ['name' => 'Rex'];
echo "{$user['name']}"; // Rex
// 對象屬性:
class Dog
{
public string $name = '旺財';
}
$dog = new Dog();
echo "{$dog->name}"; // 旺財
// 複雜表達式用花括號包住
// 簡潔場景用 . 拼接

字符串拼接

. 拼接兩個字符串,.= 追加。多次拼接用 implode 或 sprintf 更清晰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$first = 'Rex';
$last = 'Wang';
// 拼接:
$full = $first . ' ' . $last;
echo $full; // Rex Wang
// 追加:
$msg = '';
$msg .= 'hello ';
$msg .= 'world';
echo $msg; // hello world
// 數組轉字符串:
implode('-', [1, 2, 3]); // 1-2-3
// 數字拼接自動轉字符串:
echo '第' . 3 . '名'; // 第3名

多字節函數

處理中文用 mb_ 前綴函數按字符計數,strlen 按字節計數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$s = 'Hello, 世界';
// 字節數 vs 字符數:
strlen($s); // 12(字節)
mb_strlen($s); // 8(字符)
// 按字符截取:
mb_substr($s, 0, 5); // 'Hello'
// 字節截取會切壞中文
// 大小寫轉換:
mb_strtoupper('hello');
// 拆成字符數組:
mb_str_split($s);
// 處理中文務必用 mb_ 系列

格式化 sprintf

sprintf/printf 用 %s、%d 佔位符格式化,比拼接更易讀。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$name = 'Rex';
$age = 5;
// 格式化:
$line = sprintf('%s 今年 %d 歲', $name, $age);
echo $line; // Rex 今年 5 歲
// 直接輸出:
printf('%.2f', 3.14159); // 3.14
// 常用佔位符:
// %s 字符串 %d 整數 %f 浮點
// %b 二進制 %x 十六進制
// %-5s 左對齊 %05d 補零
// 參數順序可交換:
sprintf('%2$s-%1$s', 'a', 'b'); // b-a

查找與替換

strpos 找位置,str_contains 判斷包含,str_replace 批量替換。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$s = 'Hello, world!';
// 判斷包含(8.0+):
str_contains($s, 'world'); // true
// 找位置:
strpos($s, 'world'); // 7
strpos($s, 'xyz'); // false
// 替換:
str_replace('world', 'PHP', $s);
// 'Hello, PHP!'
// 忽略大小寫替換:
str_ireplace('HELLO', 'Hi', $s);
// 按位置替換:
// substr_replace($s, 'X', 0, 5)

拆分與合併

explode 按分隔符拆成數組,implode 把數組拼回字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 拆分:
$parts = explode(',', 'a,b,c');
// ['a', 'b', 'c']
// 限制數量:
explode(',', 'a,b,c', 2);
// ['a', 'b,c']
// 空串元素保留:
explode(',', 'a,,c');
// ['a', '', 'c']
// 合併:
implode(' | ', ['a', 'b']);
// 'a | b'
// 按空白拆分:
preg_split('/\s+/', 'a b c');
// ['a', 'b', 'c']

大小寫與去空白

strtolower/strtoupper 轉大小寫,trim 去首尾空白,ltrim/rtrim 只去一邊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$s = ' Hello World ';
// 去首尾空白:
trim($s); // 'Hello World'
// 只去一邊:
ltrim(' hi'); // 'hi'
rtrim('hi '); // 'hi'
// 大小寫:
strtolower('ABC'); // 'abc'
strtoupper('abc'); // 'ABC'
ucfirst('hello'); // 'Hello'
ucwords('hello world');// 'Hello World'
// 去除指定字符:
trim('...hi...', '.'); // 'hi'

8.數組與集合

數組創建、增刪改查、遍歷、排序與 Spl 數據結構。

創建數組

[] 或 array() 創建數組。數組字面量自動分配遞增整數鍵。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 字面量創建:
$list = [1, 2, 3];
// 指定鍵:
$map = ['a' => 1, 'b' => 2];
// 省略鍵自動遞增:
$list = ['x', 'y']; // 鍵 0,1
// 空數組:
$empty = [];
// 指定鍵追加:
$arr[5] = 'five';
$arr[] = 'six'; // 鍵 6
// 數字字符串鍵自動轉整數

訪問與修改

下標訪問元素,[] 追加,unset 刪除。鍵不存在時讀取會警告。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$user = ['name' => 'Rex', 'age' => 5];
// 訪問:
echo $user['name']; // Rex
// 修改:
$user['age'] = 6;
// 追加:
$user['city'] = '北京';
// 刪除:
unset($user['age']);
// 安全讀取避免警告:
$age = $user['age'] ?? '未知';
// 嵌套訪問:
$data['a']['b']['c'];

增刪與計數

array_push/array_pop 操作棧尾,array_shift/unshift 操作隊頭,count 計數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$stack = [1, 2, 3];
// 尾部入棧:
$stack[] = 4; // 推薦
array_push($stack, 5, 6);
// 尾部彈出:
$last = array_pop($stack); // 6
// 頭部彈出:
$first = array_shift($stack); // 1
// 頭部插入:
array_unshift($stack, 0);
// 計數:
count($stack);
// 判斷存在:
in_array(2, $stack); // true

遍歷數組

foreach 遍歷值或鍵值對,列表解構遍歷二維數組。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$users = [
['name' => 'Rex', 'age' => 5],
['name' => 'Anna', 'age' => 30],
];
// 只取值:
foreach ($users as $u) {
echo $u['name'];
}
// 取鍵值:
foreach ($users as $i => $u) {
echo "$i: {$u['name']}";
}
// 列表解構:
foreach ($users as ['name' => $n]) {
echo $n;
}
// while+each 是舊寫法,勿用

映射與過濾

array_map 逐元素轉換,array_filter 按回調過濾,array_reduce 歸約。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$nums = [1, 2, 3, 4];
// 映射:
$doubled = array_map(
fn($n) => $n * 2,
$nums,
); // [2, 4, 6, 8]
// 過濾:
$evens = array_filter(
$nums,
fn($n) => $n % 2 === 0,
); // [2, 4]
// 歸約:
$sum = array_reduce(
$nums,
fn($acc, $n) => $acc + $n,
0,
); // 10
// 多數組同時映射:
array_map(fn($a, $b) => $a + $b, [1, 2], [3, 4]);

排序

sort 排序值、ksort 按鍵、asort 按值且保留鍵關聯,usort 自定義比較。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$nums = [3, 1, 4, 1, 5];
// 按值排序(重建鍵):
sort($nums); // [1, 1, 3, 4, 5]
rsort($nums); // 降序
// 保留鍵排序:
$ages = ['a' => 30, 'b' => 20];
asort($ages); // 按值升序,鍵保留
ksort($ages); // 按鍵排序
// 自定義比較:
usort(
$users,
fn($x, $y) => $x['age'] <=> $y['age'],
);
// 多維列排序:
array_multisort($ages, SORT_DESC);

合併與切片

array_merge 合併數組,array_slice 取子數組,array_combine 拼鍵值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 合併(字符串鍵後者覆蓋):
$a = ['a' => 1, 'b' => 2];
$b = ['b' => 3, 'c' => 4];
$m = array_merge($a, $b);
// ['a' => 1, 'b' => 3, 'c' => 4]
// 數字鍵合併會重排:
array_merge([1], [2]); // [1, 2]
// 取子數組:
$nums = [1, 2, 3, 4, 5];
array_slice($nums, 1, 2); // [2, 3]
// 鍵值拼數組:
array_combine(['a', 'b'], [1, 2]);
// ['a' => 1, 'b' => 2]
// 去重:
array_unique([1, 1, 2]); // [1, 2]

解構賦值

列表 [] 或鍵值 [] 解構把數組元素賦給多個變量,foreach 中也可用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 列表解構:
[$a, $b] = [1, 2];
echo $a; // 1
// 指定鍵解構:
[0 => $x, 2 => $z] = [1, 2, 3];
echo $x; // 1
// 鍵值解構:
['name' => $n] = ['name' => 'Rex'];
echo $n; // Rex
// 剩餘收集:
[$first, ...$rest] = [1, 2, 3];
// $rest = [2, 3]
// 函數返回數組解構:
[$min, $max] = minMax($nums);

Spl 數據結構

SplStack、SplQueue、SplFixedArray 提供固定功能的數據結構。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 棧(後進先出):
$stack = new SplStack();
$stack->push('a');
$stack->push('b');
$stack->pop(); // 'b'
// 隊列(先進先出):
$queue = new SplQueue();
$queue->enqueue('a');
$queue->enqueue('b');
$queue->dequeue(); // 'a'
// 固定大小數組:
$fixed = new SplFixedArray(3);
$fixed[0] = 'hi';
$fixed->setSize(5);
// 對象集合:
$store = new SplObjectStorage();
$store->attach($obj, 'meta');
$store->contains($obj);

9.內存與性能

引用計數、循環引用 GC、WeakReference 與內存測量。

引用計數

zval 記錄引用數,歸零時立即回收。賦值、傳參都會增加計數。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 每個變量帶引用計數:
$a = 'hello'; // 計數 1
$b = $a; // 計數 2
unset($a); // 計數 1
unset($b); // 計數 0,回收
// 寫時複製:只有寫入才複製
// 觀察計數(需 xdebug):
// xdebug_debug_zval('a')

循環引用 GC

對象互相引用時引用計數永不歸零,由循環垃圾收集器定期清理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Node
{
public ?Node $next = null;
}
$a = new Node();
$b = new Node();
$a->next = $b;
$b->next = $a; // 形成環
unset($a, $b);
// 引用計數仍為 1,不會歸零
// GC 緩衝區滿時自動掃描:
gc_collect_cycles(); // 手動回收
// 長期對象建議主動解鏈:
// $a->next = null;

WeakReference 弱引用

WeakReference(8.0+)持有對象但不阻止回收,適合緩存等場景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Cache
{
public array $data = [];
}
$obj = new Cache();
// 弱引用不增加強引用計數:
$weak = WeakReference::create($obj);
$weak->get(); // Cache 實例
unset($obj);
// 對象被回收後:
$weak->get(); // null
// 弱映射 8.4+:WeakMap
// 適合給對象關聯附加數據

內存測量

memory_get_usage 查看當前佔用,memory_get_peak_usage 看峯值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 當前內存佔用(字節):
$used = memory_get_usage();
// 峯值佔用:
$peak = memory_get_peak_usage();
// 轉 MB 展示:
printf('%.2f MB', $peak / 1048576);
// 修改內存上限:
// ini_set('memory_limit', '512M');
// CLI 啓動時指定:
// $ php -d memory_limit=1G script.php
// 大數組處理後及時 unset

及時釋放

大變量用完後 unset 讓引用計數歸零,降低峯值,批量處理更穩。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 大數組處理完釋放:
$lines = file('big.log');
unset($lines);
// 循環中分批處理:
$result = [];
for ($i = 0; $i < 1000; $i++) {
$result[] = str_repeat('x', 100000);
if ($i % 100 === 99) {
// 分批寫盤後清空:
$result = [];
}
}
// 對象引用置空幫助回收:
// $obj = null;

OPcache 緩存

OPcache 緩存編譯後的字節碼,避免每次請求重複編譯,生產必開。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 確認已啓用:
// $ php -m | grep opcache
// php.ini 建議:
// opcache.enable=1
// opcache.memory_consumption=128
// opcache.max_accelerated_files=10000
// CLI 不默認啓用:
// $ php -d opcache.enable=1 script.php
// 緩存字節碼,跳過編譯:
// 高流量頁面吞吐明顯提升
// 開發環境可臨時關閉

大數據處理

超大數組內存吃緊,用生成器、流式讀取與 SplFixedArray 控制佔用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 生成器替代完整數組:
function readLines(string $file): Generator
{
$h = fopen($file, 'r');
while (($line = fgets($h)) !== false) {
yield rtrim($line);
}
fclose($h);
}
// 邊讀邊處理,內存恆定:
foreach (readLines('big.log') as $line) {
process($line);
}
// 大量整數用 SplFixedArray:
$fixed = new SplFixedArray(1000000);
// 避免一次讀入整份大文件

性能要點

避免字符串拼接循環、預分配、複用連接與減少冗餘查詢。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 拼接避免循環累加:
$parts = [];
foreach ($items as $it) {
$parts[] = $it->name;
}
$result = implode(',', $parts);
// 複用連接對象:
$pdo = new PDO($dsn); // 單例共享
// 循環外取計數:
$count = count($list);
// 避免重複查詢數據庫:
// 先查一次,內存過濾
// 結合 opcache 與緩衝
// 熱點路徑保持簡單

10.面向對象

類、構造、可見性、繼承、接口、trait 與只讀屬性。

類定義

class 定義類,屬性帶類型,方法在類內聲明。new 創建實例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class User
{
public string $name = '';
public int $age = 0;
public function describe(): string
{
return "{$this->name}, {$this->age}";
}
}
$u = new User();
$u->name = 'Rex';
$u->age = 5;
echo $u->describe(); // Rex, 5
// 屬性需聲明時初始化
// 或構造函數中賦值

構造與屬性提升

__construct 構造方法初始化對象。8.0+ 構造參數直接提升為屬性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 傳統寫法:
class OldUser
{
private string $name;
public function __construct(string $name)
{
$this->name = $name;
}
}
// 屬性提升(8.0+):
class User
{
public function __construct(
public string $name,
public int $age,
) {}
}
$u = new User('Rex', 5);
echo $u->name; // Rex
// 提升參數自動成為屬性

可見性

public 公開、protected 子類可見、private 僅本類,默認 public。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class BankAccount
{
public string $owner = '';
protected float $balance = 0;
private string $secret = 'xx';
public function deposit(float $n): void
{
$this->balance += $n;
}
}
class Child extends BankAccount
{
public function peek(): float
{
return $this->balance; // protected 可訪問
}
}
$acc = new BankAccount();
$acc->owner = 'Rex';
// $acc->balance = 1; // 不可訪問

繼承

extends 繼承父類,重寫方法,parent:: 調用父類方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Animal
{
public function __construct(
protected string $name,
) {}
public function speak(): string
{
return "$this->name: ...";
}
}
class Dog extends Animal
{
public function speak(): string
{
return parent::speak() . ' 汪汪';
}
}
$dog = new Dog('旺財');
echo $dog->speak(); // 旺財: ... 汪汪
// 子類覆蓋父類方法與屬性
// final 方法不可被覆蓋

接口

interface 定義方法簽名,implements 實現。類可實現多個接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
interface Speakable
{
public function speak(): string;
}
interface Runnable
{
public function run(): void;
}
// 實現多個接口:
class Robot implements Speakable, Runnable
{
public function speak(): string
{
return 'beep';
}
public function run(): void
{
echo "running\n";
}
}
// 接口可繼承接口:
// interface X extends Speakable {}
// 依賴接口編程便於替換

抽象類

abstract class 不能被實例化,抽象方法由子類實現。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
abstract class Shape
{
abstract public function area(): float;
public function describe(): string
{
return '面積: ' . $this->area();
}
}
class Circle extends Shape
{
public function __construct(
private float $r,
) {}
public function area(): float
{
return 3.14159 * $this->r * $this->r;
}
}
$c = new Circle(2);
echo $c->describe(); // 面積: 12.56636

trait 複用

trait 橫向複用方法到多個類,use 引入,可組合多個 trait。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
trait Loggable
{
public function log(string $msg): void
{
echo "[LOG] $msg\n";
}
}
trait Timestampable
{
public function createdAt(): string
{
return date('Y-m-d H:i:s');
}
}
class Order
{
use Loggable, Timestampable;
public function create(): void
{
$this->log('創建訂單');
}
}
$order = new Order();
$order->create(); // [LOG] 創建訂單
// 類中的方法優先於 trait

靜態成員

static 屬性與方法屬於類而非實例,self:: 或類名:: 訪問。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Counter
{
private static int $total = 0;
public static function hit(): void
{
self::$total++;
}
public static function get(): int
{
return self::$total;
}
}
Counter::hit();
Counter::hit();
echo Counter::get(); // 2
// 靜態方法內無 $this:
// 不能訪問非靜態成員
// 靜態屬性所有實例共享
// 延遲靜態綁定用 static::

只讀屬性

readonly 屬性(8.1+)只能在聲明與構造中賦值一次,之後只讀。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Point
{
public function __construct(
public readonly float $x,
public readonly float $y,
) {}
}
$p = new Point(1.0, 2.0);
echo $p->x; // 1
// $p->x = 3; // 只讀錯誤
// 只讀類(8.2+):
readonly class Config
{
public function __construct(
public string $dsn,
) {}
}
// 適合值對象與不可變配置
// 屬性提升 + readonly 常用

11.錯誤處理

異常層級、try/catch/finally、自定義異常與錯誤處理。

try / catch

try 塊拋出的異常由 catch 捕獲,捕獲後程序繼續運行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
$n = 1 / 0; // 8.0+ 拋 DivisionByZeroError
} catch (\DivisionByZeroError $e) {
echo '除數不能為零';
}
// 捕獲異常:
try {
throw new \RuntimeException('出錯了');
} catch (\RuntimeException $e) {
echo $e->getMessage(); // 出錯了
}
// 未捕獲異常導致致命錯誤
// 腳本終止,錯誤碼 255

Throwable 層級

Throwable 是基類,Error 表示引擎錯誤,Exception 表示程序異常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Throwable
// ├── Error(引擎級)
// │ ├── TypeError
// │ ├── ValueError
// │ └── DivisionByZeroError
// └── Exception(程序級)
// ├── LogicException
// └── RuntimeException
// 捕獲任意錯誤/異常:
try {
risky();
} catch (\Throwable $e) {
echo $e->getMessage();
}
// Error 與 Exception 都實現 Throwable
// 可分別 catch 精準處理

自定義異常

繼承 Exception 或 RuntimeException 定義業務異常,攜帶額外字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ValidationException extends \RuntimeException
{
public function __construct(
public readonly string $field,
string $message = '',
) {
parent::__construct(
$message ?: "字段 {$field} 無效",
);
}
}
// 拋出:
throw new ValidationException('email');
// 捕獲時取字段:
try {
validate($input);
} catch (ValidationException $e) {
echo $e->field; // email
echo $e->getMessage();
}

多重捕獲

catch 多個異常類型用 | 分隔,按順序匹配,子類先於父類。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
parse($input);
} catch (\InvalidArgumentException | \LengthException $e) {
// 兩類異常同樣處理
echo $e->getMessage();
} catch (\Exception $e) {
// 其它異常單獨處理
logError($e);
}
// 匹配順序從上到下:
// 先寫具體異常,再寫父類
// 8.0+ 可省略變量名:
// catch (\Exception) { ... }

finally

finally 無論是否拋異常都會執行,用於清理資源、關閉連接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function process(): string
{
$conn = openConnection();
try {
return doWork($conn);
} finally {
$conn->close(); // 必執行
}
}
// 即使 return 也先執行 finally
// 釋放文件句柄:
$h = fopen('a.txt', 'r');
try {
// 處理
} finally {
fclose($h);
}
// 可只用 finally,不捕獲異常

全局異常處理器

set_exception_handler 兜底處理未捕獲異常,統一日誌與響應。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
set_exception_handler(function (\Throwable $e): void {
// 記錄日誌:
error_log($e->getMessage());
// 返回 500:
http_response_code(500);
echo '服務器開小差了';
});
// 之後未捕獲異常都走這裏
// 恢復默認處理器:
restore_exception_handler();
// set_error_handler 處理非異常錯誤
// 8.0+ 大多數錯誤也是異常

Error 與 Exception

引擎錯誤是 Error(類型/參數),業務異常是 Exception,分開捕獲。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// TypeError:類型不匹配:
function add(int $a, int $b): int
{
return $a + $b;
}
try {
add('a', 'b'); // 拋 TypeError
} catch (\TypeError $e) {
echo '參數類型錯誤';
}
// ValueError:值不合法:
try {
array_chunk([1, 2], 0); // 拋 ValueError
} catch (\ValueError $e) {
echo '長度必須為正';
}
// 引擎錯誤多數繼承 Error
// 與 Exception 分開捕獲

異常鏈

catch 中拋出新異常時把原異常作為第三個參數,getPrevious 追溯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
try {
$data = fetchFromApi();
} catch (\HttpException $e) {
throw new \RuntimeException(
'獲取數據失敗',
0,
$e, // 前一個異常
);
}
// 追溯原因:
try {
loadAll();
} catch (\RuntimeException $e) {
$prev = $e->getPrevious();
if ($prev) {
echo $prev->getMessage();
}
}
// 日誌記錄完整調用棧:
// error_log($e->getTraceAsString());

12.文件 I/O

讀取寫入文件、JSON、CSV 與標準流。

讀取文件

file_get_contents 讀整個文件為字符串,小文件首選,失敗返回 false。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 讀整個文件:
$text = file_get_contents('data.txt');
if ($text === false) {
echo '讀取失敗';
}
// 讀為數組(每行一個元素):
$lines = file('log.txt');
// 忽略換行與空行:
$lines = file(
'log.txt',
FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES,
);
// 遠程讀取(需 allow_url_fopen):
$json = file_get_contents('https://example.com/api');

寫入文件

file_put_contents 寫字符串,追加用 FILE_APPEND,返回寫入字節數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 覆蓋寫入:
$n = file_put_contents('out.txt', 'Hello!');
// 追加模式:
file_put_contents('log.txt', "更多\n", FILE_APPEND);
// 寫入數組:
$lines = ['a', 'b'];
file_put_contents('list.txt', implode("\n", $lines));
// 檢查寫入失敗:
if (file_put_contents('x.txt', 'data') === false) {
echo '寫入失敗';
}
// 權限問題會返回 false

逐行讀取

fopen + fgets 逐行讀取,內存恆定,適合大文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$h = fopen('big.log', 'r');
if ($h === false) {
exit('無法打開文件');
}
while (($line = fgets($h)) !== false) {
// 去掉行尾換行:
$line = rtrim($line, "\r\n");
process($line);
}
fclose($h);
// 生成器封裝更友好:
// function lines(string $f): Generator
// 大文件流式處理,勿一次讀入

打開模式

fopen 的 r/w/a 模式控制讀寫行為,w 會清空原內容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// r:只讀,文件需存在
$h = fopen('a.txt', 'r');
// r+:讀寫,文件需存在
$h = fopen('a.txt', 'r+');
// w:寫入,清空或創建
$h = fopen('a.txt', 'w');
// a:追加,寫入末尾
$h = fopen('a.txt', 'a');
// b:二進制模式(Windows 需要)
$h = fopen('img.bin', 'wb');
// 寫完記得 fclose 釋放句柄
// 句柄泄漏在長進程會堆積

JSON 編解碼

json_encode 轉 JSON,json_decode 解析。第二參數 true 得數組。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$data = ['name' => 'Rex', 'age' => 5];
// 編碼:
$json = json_encode($data);
// {"name":"Rex","age":5}
// 美化與中文不轉義:
json_encode(
$data,
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE,
);
// 解析為數組:
$arr = json_decode($json, true);
// 解析為對象:
$obj = json_decode($json);
// 檢查錯誤:
if (json_last_error() !== JSON_ERROR_NONE) {
echo json_last_error_msg();
}

CSV 讀寫

fgetcsv 解析一行 CSV,fputcsv 寫一行,自動處理引號與轉義。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 讀取:
$h = fopen('data.csv', 'r');
$headers = fgetcsv($h);
while (($row = fgetcsv($h)) !== false) {
$item = array_combine($headers, $row);
echo $item['name'];
}
fclose($h);
// 寫入:
$h = fopen('out.csv', 'w');
fputcsv($h, ['name', 'age']);
fputcsv($h, ['Rex', 5]);
fclose($h);
// 逗號/引號自動轉義
// 分隔符可自定義:fgetcsv($h, 0, ';')

目錄與 glob

glob 按模式匹配文件,scandir 列目錄,mkdir 建目錄。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 通配符匹配文件:
foreach (glob('data/*.csv') as $file) {
echo $file;
}
// 列目錄:
$items = scandir('.');
// 判斷類型:
is_dir('src'); // true
is_file('a.txt'); // true
// 創建目錄(含父級):
mkdir('logs/2026', 0755, true);
// 刪除文件:
unlink('tmp.txt');
// 判斷存在:
file_exists('a.txt');

標準流

STDIN、STDOUT、STDERR 常亮對應標準輸入輸出,CLI 常用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 讀取一行輸入:
$line = fgets(STDIN);
$name = trim($line);
// 輸出到標準輸出:
fwrite(STDOUT, "hello $name\n");
// 輸出到錯誤流:
fwrite(STDERR, "警告:出錯了\n");
// 逐行處理輸入:
while (($line = fgets(STDIN)) !== false) {
echo strtoupper($line);
}
// 配合管道:
// echo "hi" | php app.php

13.常見坑

日常開發最容易踩的坑與正確寫法。

== 與 ===

== 寬鬆比較會轉換類型,=== 嚴格比較類型與值。判斷儘量用 ===。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// BAD:寬鬆比較的意外
0 == 'foo'; // true
0 == ''; // true
'1' == 1; // true
// GOOD:嚴格比較
0 === 'foo'; // false
'1' === 1; // false
// 判零要嚴格區分:
$count = 0;
if ($count == false) { /* 誤判 */ }
if ($count === 0) { /* 正確 */ }
// 慣例:除特殊情況都用 ===

foreach 引用陷阱

foreach 用 & 修改元素後,最後一個元素仍指向原數組,複用 $item 會污染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$items = [1, 2, 3];
// BAD:循環後 $item 殘留引用
foreach ($items as &$item) {
$item *= 2;
}
// 後續複用 $item 會意外改數組
// GOOD:用完 unset 斷開
foreach ($items as &$item) {
$item *= 2;
}
unset($item);
// 或改用下標修改:
foreach ($items as $i => $v) {
$items[$i] = $v * 2;
}

isset 與鍵存在

isset 認為值為 null 的鍵不存在,判斷鍵是否真實存在用 array_key_exists。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$arr = ['a' => null];
// BAD:鍵存在但值 null
isset($arr['a']); // false
// GOOD:判斷鍵是否真實存在
array_key_exists('a', $arr); // true
// isset 用於讀取前判斷:
$name = isset($arr['b']) ? $arr['b'] : '默認';
// ?? 等價 isset 且給默認:
$name = $arr['b'] ?? '默認';
// 需區分 null 與缺失時用 array_key_exists

弱類型比較坑

弱比較下字符串與數字比較規則特殊,'0' 與 false 相等,需顯式轉換。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// BAD:弱比較的意外
'0' == false; // true
'0' == null; // true
'abc' == 0; // true
'1e3' == 1000; // true(科學計數)
// GOOD:顯式轉換後再比較
(int) 'abc' === 0; // 明確意圖
filter_var('1e3', FILTER_VALIDATE_INT);
// 哈希比較用 hash_equals:
// hash_equals($a, $b) 防時序攻擊
// 數據庫結果注意返回類型

未定義鍵警告

訪問不存在的數組鍵觸發警告(8.0+),用 ?? 或 isset 提前判斷。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$user = ['name' => 'Rex'];
// BAD:直接訪問不存在的鍵
$city = $user['city'];
// PHP Warning: Undefined array key
// GOOD:空合併提供默認
$city = $user['city'] ?? '未知';
// 或先判斷再訪問:
$city = isset($user['city'])
? $user['city']
: '';
// 參數默認值:
function f(array $cfg = []) {
$x = $cfg['x'] ?? 0;
}

輸出後再發響應頭

已輸出內容後 header() 會失敗,先開啓緩衝或先發響應頭。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// BAD:輸出後才發響應頭
echo "hello\n";
// header('Location: /login'); // 報錯
// GOOD:先發響應頭再輸出
header('Location: /login');
exit;
// 無法控制時先開緩衝:
ob_start();
echo "later\n";
ob_end_flush();
// 檢查是否已輸出:
// headers_sent()

empty 與 isset

empty 對 0、''、'0' 等都返回 true,isset 只關心是否為 null,選擇要合語義。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$age = 0;
$name = '';
// BAD:empty 把 0 當空
if (empty($age)) { /* 誤判為未設置 */ }
// GOOD:數字 0 是合法值
if (!isset($age) || $age === '') { /* 未設置 */ }
// empty 語義:
empty(0); // true
empty('0'); // true
empty(''); // true
empty(null); // true
// 表單空值判斷:
// trim($val) !== '' 更明確

SQL 注入防護

拼接 SQL 字符串有注入風險,用 PDO 預處理參數綁定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$id = $_GET['id'];
// BAD:字符串拼接 SQL(注入風險)
// $sql = "SELECT * FROM users WHERE id = $id";
// GOOD:預處理 + 綁定參數
$pdo = new PDO($dsn, $user, $pass);
$stmt = $pdo->prepare(
'SELECT * FROM users WHERE id = ?',
);
$stmt->execute([$id]);
$rows = $stmt->fetchAll();
// 命名參數:
$stmt = $pdo->prepare(
'SELECT * FROM users WHERE id = :id',
);
$stmt->execute([':id' => $id]);
// 輸入校驗:
// filter_var($id, FILTER_VALIDATE_INT)

14.併發與協程

PHP 的併發模型:多進程、協程、Fiber 與第三方擴展。

進程模型

PHP 默認單線程,每請求獨立進程執行,變量隨請求釋放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// PHP 默認單線程:
// 每個請求獨立進程/線程執行
// 共享內存有限,寫時複製:
// 子進程不會複製整份內存
// 無共享狀態問題:
// 變量隨請求結束釋放
// 需要併發時的選擇:
// 1. 多進程 pcntl_fork
// 2. 協程 Fiber / Swoole
// 3. parallel 擴展真線程
// CLI 服務器模式:
// $ php -S localhost:8000

pcntl_fork 多進程

pcntl_fork 派生子進程,父子進程各自執行,僅 CLI 可用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 僅 CLI,需啓用 pcntl 擴展:
// $ php -m | grep pcntl
$pid = pcntl_fork();
if ($pid === -1) {
exit('fork 失敗');
}
if ($pid === 0) {
// 子進程:返回值 0
echo "子進程 PID " . getmypid() . "\n";
exit(0);
}
// 父進程:等待子進程
pcntl_wait($status);
echo "父進程 PID " . getmypid() . "\n";
// 子進程共享代碼與內存快照
// 改動互不影響(寫時複製)

信號處理

pcntl_signal 註冊信號處理器,dispatch 派發掛起信號。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
declare(ticks = 1);
// 註冊信號處理器:
pcntl_signal(SIGINT, function (int $signo): void {
echo "收到信號 $signo,退出\n";
exit(0);
});
// 處理掛起信號:
while (true) {
pcntl_signal_dispatch();
usleep(100000);
}
// 常用信號:
// SIGTERM 終止 SIGINT Ctrl+C
// SIGHUP 掛斷 SIGCHLD 子進程退出
// 生產環境可監聽信號優雅停機

parallel 擴展

parallel 是 PECL 擴展,提供真正的多線程 worker,非內置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// parallel 是 PECL 擴展,非內置:
// $ pecl install parallel
use parallel\Runtime;
use parallel\Future;
// 啓動工作線程:
$rt = new Runtime();
$future = $rt->run(function (int $n): int {
return $n * $n;
}, [7]);
// 阻塞取結果:
$result = $future->value(); // 49
// 線程共享變量需顯式傳參
// 不能捕獲外部變量
// 生產多核密集任務可用
// Web 環境通常選多進程更穩

Swoole 協程

Swoole 高性能網絡框架,協程調度 + 常駐內存,適合高併發。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Swoole 是高性能網絡框架擴展:
// $ pecl install swoole
// 創建協程服務器:
use Swoole\Coroutine\Http\Server;
$server = new Server('0.0.0.0', 9501);
$server->handle('/', function ($req, $res) {
// 每個連接一個協程:
$res->end("hello\n");
});
$server->start();
// 常駐內存 + 協程調度:
// 大量併發下內存遠小於線程模型
// 也支持多進程 worker:
// Swoole\Server 的 worker_num 配置

Fiber 協程

Fiber(8.1+)原生協程,suspend 掛起、resume 恢復,手動調度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Fiber(8.1+)協程,手動切換:
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('first');
echo "恢復,收到 $value\n";
});
// 啓動,運行到首個 suspend:
$value = $fiber->start();
echo "掛起返回 $value\n"; // first
// 恢復並傳值:
$fiber->resume('second');
// 恢復,收到 second
// 單個線程內協作,非並行
// 用於異步代碼轉同步寫法

異步庫

無內置 async/await,用 ReactPHP、Amp 等事件循環庫實現異步。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 無內置 async/await 關鍵字:
// 用事件循環庫實現異步 I/O
// ReactPHP 事件循環示意:
use React\EventLoop\Factory;
$loop = Factory::create();
$loop->addTimer(1.0, function () {
echo "ok\n";
});
$loop->run();
// 異步庫常見抽象:
// Promise / Deferred / await
// 簡單併發用多進程即可
// 高併發 I/O 用 Swoole/ReactPHP

進程間通信

多進程共享數據走消息隊列、文件或共享內存,避免直接改同一文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 多進程間共享數據:
// 1. 文件/數據庫
// 2. 消息隊列(Redis 等)
// 3. 共享內存 shmop
// 4. 管道
// 消息隊列簡單示意:
// Redis 列表做任務隊列:
// $redis->lpush('jobs', $task);
// 子進程寫管道:
// posix_kill($pid, SIGUSR1);
// 任務結果收集:
// 每個 worker 寫獨立文件
// 生產系統常用:
// RabbitMQ / Kafka / Redis Stream
// 避免多進程直接改同一文件

15.網絡

HTTP 客户端、curl、URL 解析、會話與套接字。

HTTP 請求

file_get_contents 發簡單請求,stream_context_create 配超時與頭部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 簡單 GET(allow_url_fopen):
$body = file_get_contents(
'https://example.com/api',
);
// 帶超時:
$ctx = stream_context_create([
'http' => ['timeout' => 5],
]);
$body = file_get_contents(
'https://example.com',
false,
$ctx,
);
// 生產更推薦 curl 或 guzzle
// 支持狀態碼與詳細控制

curl 擴展

curl_init + curl_exec 完整控制請求,讀狀態碼與錯誤信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// curl 擴展(CLI 內置):
$ch = curl_init(
'https://api.example.com/data',
);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer xxx',
],
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
// 出錯檢查:
if (curl_errno($ch)) {
echo curl_error($ch);
}
// POST 數據:
// CURLOPT_POST + CURLOPT_POSTFIELDS

POST 與 Guzzle

stream_context 發 POST JSON,工程實踐優先用 Guzzle 客户端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// POST JSON:
$data = json_encode(['name' => 'Rex']);
$ctx = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => $data,
],
]);
$res = file_get_contents(
'https://example.com/api',
false,
$ctx,
);
// Guzzle 更符合工程實踐:
// $client->request('POST', $url, [
// 'json' => $payload,
// ]);
// 請求頭分隔用 CRLF

請求與響應頭

header() 發送響應頭,http_response_code 設狀態碼,須在輸出前調用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 讀取響應頭:
$ctx = stream_context_create([
'http' => ['ignore_errors' => true],
]);
$body = file_get_contents(
'https://example.com',
false,
$ctx,
);
$headers = $http_response_header ?? [];
// 發送響應頭:
header('Content-Type: application/json');
header('Cache-Control: max-age=3600');
// 設置狀態碼:
http_response_code(404);
// 讀取請求頭:
// getallheaders() 返回關聯數組
// 輸出前必須先發 header

URL 解析

parse_url 拆解 URL 各組件,parse_str 解析查詢串,http_build_query 拼接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$url = 'https://user:[email protected]:8080/a?q=1#top';
// 解析各組件:
$parts = parse_url($url);
// [
// 'scheme' => 'https',
// 'host' => 'example.com',
// 'port' => 8080,
// 'path' => '/a',
// 'query' => 'q=1',
// 'fragment' => 'top',
// ]
// 查詢字符串:
parse_str('a=1&b=2', $out);
// $out = ['a' => '1', 'b' => '2']
// 拼 URL 編碼參數:
http_build_query(['a' => 1, 'b' => 2]);
// a=1&b=2

Cookie 與 Session

session_start 啓動會話,setcookie 寫 cookie,$_COOKIE 讀取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 啓動會話:
session_start();
// 自動生成會話 cookie
// 存取會話數據:
$_SESSION['user_id'] = 123;
echo $_SESSION['user_id'];
// 設置 cookie:
setcookie('theme', 'dark', [
'expires' => time() + 3600,
'path' => '/',
'httponly' => true,
'samesite' => 'Lax',
]);
// 讀取:
echo $_COOKIE['theme'] ?? '默認';
// 銷燬會話:
session_destroy();
// 會話數據存服務端,cookie 只存 id

Socket 套接字

stream_socket_client 連接遠程,fwrite 發送、fgets 讀取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 客户端套接字:
$sock = stream_socket_client(
'tcp://example.com:80',
$errno,
$errstr,
10,
);
if (!$sock) {
echo "連接失敗: $errstr";
}
fwrite($sock, "GET / HTTP/1.0\r\n\r\n");
while (!feof($sock)) {
echo fgets($sock);
}
fclose($sock);
// 服務端監聽:
// stream_socket_server('tcp://0.0.0.0:9000')
// 生產協議服務器用 Swoole
// 手寫 socket 易出邊界問題

SSE 與 WebSocket

SSE 單向文本推送,WebSocket 雙向通信,按需選擇。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// SSE:服務器推送文本流
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
while (true) {
echo "data: " . json_encode(['t' => time()]) . "\n\n";
flush();
sleep(1);
}
// 客户端用 EventSource 接收
// 單向、基於 HTTP,簡單可靠
// WebSocket 雙向通信:
// 需擴展如 Swoole、Ratchet
// 選型:簡單推送用 SSE
// 雙向交互才用 WebSocket

16.日期與時間

時間戳、格式化、解析、時區與時間運算。

獲取當前時間

time() 得秒級時間戳,microtime 得毫秒級,date 格式化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 當前時間戳(秒):
$ts = time();
// 毫秒級:
$ms = microtime(true);
// 格式化當前時間:
echo date('Y-m-d H:i:s'); // 2026-08-02 12:30:00
// 讀取默認時區:
date_default_timezone_get();
// 設置時區:
// date_default_timezone_set('Asia/Shanghai');
// 舊寫法 time() 函數等價 date() 第 2 參數

格式化日期

date() 用格式符渲染時間戳,支持年月日時分秒與本地化寫法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ts = strtotime('2026-08-02 12:30:00');
// 格式化:
echo date('Y-m-d', $ts); // 2026-08-02
echo date('H:i:s', $ts); // 12:30:00
echo date('D, d M Y', $ts); // Sun, 02 Aug 2026
echo date('Y年m月d日', $ts); // 2026年08月02日
// 常用格式符:
// Y 四位年 m 兩位月 d 兩位日
// H 24小時 i 分鐘 s 秒
// D 星期簡寫 M 月份簡寫
// 轉時間戳(反之):
// strtotime('2026-08-02')

解析日期

strtotime 解析日期文本與相對時間,失敗返回 false。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 解析日期字符串為時間戳:
$ts = strtotime('2026-08-02 12:30:00');
echo $ts;
// 相對時間:
strtotime('+1 day'); // 明天
strtotime('next Monday'); // 下週一
strtotime('-2 weeks'); // 兩週前
// 解析失敗返回 false:
$bad = strtotime('不是日期');
// false
// 複雜解析用 DateTime:
// new DateTime('2026-08-02 12:30:00')
// 注意時區影響解析結果

時間戳

Unix 時間戳是 UTC 秒數,DateTime 提供時間戳與對象互轉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 時間戳(epoch 秒):
$ts = time();
// 創建指定時刻:
$dt = new DateTime('2026-08-02 12:30:00');
$ts = $dt->getTimestamp();
// 時間戳格式化:
$dt = new DateTime();
$dt->setTimestamp($ts);
echo $dt->format('Y-m-d');
// 毫秒時間戳(服務端):
$ms = (int) (microtime(true) * 1000);
// 時間戳是 UTC 秒數
// 顯示時按本地時區換算

時區

date_default_timezone_set 設默認時區,DateTime 可帶獨立時區。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 設置默認時區:
date_default_timezone_set('Asia/Shanghai');
// 查看所有時區:
// DateTimeZone::listIdentifiers()
// DateTime 指定時區:
$dt = new DateTime('now', new DateTimeZone('UTC'));
echo $dt->format('Y-m-d H:i:s');
// 時區換算:
$dt->setTimezone(new DateTimeZone('Asia/Tokyo'));
echo $dt->format('Y-m-d H:i:s P');
// 生產統一存 UTC
// 展示層按用户時區轉換
// 不要用 date('T') 猜時區

時間間隔

DateInterval 表示時長,DateTime add/sub 加減間隔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 兩個時刻相差:
$start = new DateTime('2026-08-01');
$end = new DateTime('2026-08-02');
$diff = $start->diff($end);
echo $diff->days; // 1
// 添加時間:
$dt = new DateTime('2026-08-02');
$dt->add(new DateInterval('P3D')); // 加 3 天
echo $dt->format('Y-m-d'); // 2026-08-05
// 減去時間:
$dt->sub(new DateInterval('PT2H')); // 減 2 小時
// DateInterval 格式:
// P 週期 D 天 T 時間部分
// P1Y 一年 P2M 兩月 PT1H 一小時
// 間隔單位天數換算:
// $diff->days 總天數

時間差計算

DateTime diff 返回 DateInterval,可讀年齡、剩餘天數等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 計算年齡:
$birth = new DateTime('1990-05-20');
$now = new DateTime();
$diff = $birth->diff($now);
echo $diff->y . ' 歲'; // 整數年
// 差值各字段:
$diff->y; // 年
$diff->m; // 月
$diff->d; // 日
$diff->h; // 小時
$diff->i; // 分鐘
// 人類可讀格式:
echo $diff->format('%y 年 %m 月 %d 天');
// 總秒數:
// $diff->s + $diff->i * 60 + ...
// 負數需翻轉 invert 標誌

休眠與定時器

sleep 阻塞暫停,事件循環定時器可非阻塞調度延時任務。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 暫停執行(秒):
sleep(2);
// 微秒暫停:
usleep(500000); // 0.5 秒
// 任務延遲(ReactPHP):
use React\EventLoop\Factory;
$loop = Factory::create();
$loop->addTimer(1.0, function () {
echo "1 秒後執行\n";
});
$loop->run();
// Swoole 定時器:
// Swoole\Timer::tick(1000, fn() => ...)
// CLI 長任務注意 sleep 阻塞
// 高併發場景用事件循環定時器

17.進程與 CLI

命令行參數、輸入輸出、環境變量與進程控制。

命令行參數

$argv 數組存放命令行參數,$argv[0] 是腳本名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// $ php app.php arg1 arg2
// $argv[0] = 'app.php'
// $argv[1] = 'arg1'
// $argv[2] = 'arg2'
// 參數個數:
echo count($argv); // 3
// 忽略腳本名遍歷:
foreach (array_slice($argv, 1) as $arg) {
echo $arg;
}
// 解析選項:
// getopt('a:b::') 見下節
// 讀取 stdin 輸入:
// fgets(STDIN)

讀取輸入

fgets(STDIN) 讀一行,stream_get_contents 讀全部,trim 去換行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 交互輸入:
echo '請輸入姓名:';
$name = trim(fgets(STDIN));
// 批量逐行讀取:
while (($line = fgets(STDIN)) !== false) {
echo strtoupper($line);
}
// 讀取全部輸入:
$all = stream_get_contents(STDIN);
// 判斷是否管道輸入:
// stream_isatty(STDIN)
// 輸入可能為空:
// 交互前檢查 posix_isatty

執行外部命令

shell_exec 取輸出,exec 取狀態碼,傳參用 escapeshellarg 防注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 執行外部命令並返回輸出:
$out = shell_exec('ls -la');
echo $out;
// 獲取狀態碼:
exec('git status', $lines, $code);
// $code 0 表示成功
// 數組輸出每行:
// $lines 每行一個元素
// 禁止把用户輸入拼進命令:
// $cmd = 'rm ' . $_GET['f']; // 危險
// 需傳參時用 escapeshellarg:
// exec('cat ' . escapeshellarg($file))

環境變量

getenv 讀取環境變量,putenv 設置,敏感配置走環境變量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 讀取環境變量:
echo getenv('HOME');
// 設置環境變量:
putenv('APP_ENV=production');
echo getenv('APP_ENV');
// 讀取全部:
print_r(getenv());
// $_ENV 超全局:
echo $_ENV['PATH'] ?? '';
// .env 文件常用庫:
// vlucas/phpdotenv
// 敏感信息走環境變量
// 勿硬編碼進代碼庫

進程信息

getmypid 取當前 PID,posix_* 系列查用户與父進程,hrtime 計時。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 當前進程 PID:
$pid = getmypid();
// 父進程 PID:
echo posix_getppid();
// 當前用户:
echo posix_getuid();
echo posix_getpwuid(posix_getuid())['name'];
// 運行時長:
// 用 hrtime() 計微秒:
$start = hrtime(true);
work();
$ms = (hrtime(true) - $start) / 1e6;
echo "$ms ms";
// CLI 進程信息:
// 命令名 / proc/self/cmdline

進程信號

pcntl_signal 控制信號處理,pcntl_wait 等待子進程退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 忽略信號:
pcntl_signal(SIGTERM, SIG_IGN);
// 默認處理:
pcntl_signal(SIGTERM, SIG_DFL);
// 等待子進程(非阻塞):
$pid = pcntl_fork();
if ($pid === 0) { exit(0); }
// 阻塞等待:
pcntl_wait($status);
// 檢查退出碼:
echo pcntl_wexitstatus($status);
// 向進程發信號:
// posix_kill($pid, SIGTERM)
// 信號處理裏避免複雜操作

管道與終端

stream_isatty 判斷交互終端,stderr 輸出進度,exit 控制退出碼。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 管道從 stdin 讀:
// echo "hi" | php app.php
// 輸出流重定向:
// php app.php > out.txt 2>&1
// 判斷是否交互終端:
if (stream_isatty(STDIN)) {
echo "交互模式\n";
} else {
echo "管道輸入\n";
}
// 進度輸出到 stderr:
fwrite(STDERR, "處理中...\n");
// 後台執行並返回:
// $ php app.php &
// 退出碼控制:
exit(0); // 成功
exit(1); // 失敗

退出碼

exit() 傳退出碼,0 成功非 0 失敗,shell 用 $? 讀取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 正常退出:
exit(0);
// 業務錯誤:
if (!$data) {
exit(1); // 通用錯誤
}
// 自定義碼:
exit(2); // 用法錯誤
exit(3); // 輸入無效
// 腳本內提前退出:
return; // 或 exit
// shell 檢查:
// php app.php
// echo $? // 上一命令退出碼
// 0 成功,非 0 失敗
// 128+ 表示被信號終止

18.正則表達式

匹配、捕獲、替換、拆分與多字節模式。

preg_match

preg_match 匹配首個結果,捕獲組存進 $m,命名組按名取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 匹配第一個結果:
$s = '訂單號 A123 和 B456';
if (preg_match('/[A-Z]\d{3}/', $s, $m)) {
echo $m[0]; // A123
}
// 捕獲組:
preg_match('/([A-Z])(\d{3})/', $s, $m);
echo $m[1]; // A
echo $m[2]; // 123
// 命名組:
preg_match('/(?<type>[A-Z])(?<num>\d{3})/', $s, $m);
echo $m['type']; // A
// 正則錯誤會返回 false

preg_match_all

匹配所有結果,默認按組分組,PREG_SET_ORDER 按匹配分組。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$s = '訂單號 A123 和 B456';
preg_match_all('/[A-Z]\d{3}/', $s, $m);
print_r($m[0]);
// ['A123', 'B456']
// 分組結構:
preg_match_all('/([A-Z])(\d{3})/', $s, $m);
// $m[0] 全匹配
// $m[1] 第 1 組
// $m[2] 第 2 組
// 鍵值捕獲:
preg_match_all(
'/(\w+)=(\d+)/',
'a=1&b=2',
$m,
PREG_SET_ORDER,
);
// $m[0] = ['a=1', 'a', '1']
// 第二個參數決定返回結構

preg_replace

按正則替換,用 $1 回引用捕獲組,preg_replace_callback 回調處理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 替換匹配:
$s = '電話 138-1234-5678';
$r = preg_replace('/\d{3}-\d{4}-\d{4}/', '[隱藏]', $s);
// '電話 [隱藏]'
// 用捕獲組重構:
preg_replace(
'/(\d{4})(\d{4})/',
'$1-$2',
'13812345678',
);
// '1381-2345-678'(示意)
// 回引用:
preg_replace('/(\w+)\s+\1/', '$1', 'hi hi there');
// 'hi there'
// 按數組批量替換:
preg_replace(['/a/', '/b/'], ['A', 'B'], 'abc');
// 回調替換:
preg_replace_callback('/\d+/', fn($m) => $m[0] * 2, '1 2');

常用模式

字符類、量詞與貪婪匹配,正則分隔符與轉義規則。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 字符類:
\d // 數字 [0-9]
\w // 單詞字符 [A-Za-z0-9_]
\s // 空白
// 量詞:
* // 0 或多次
+ // 1 或多次
? // 0 或 1 次
{n,m} // n 到 m 次
// 貪婪與懶惰:
'<b>x</b>'
/<.*>/ // 貪婪:<b>x</b>
/<.*?>/ // 懶惰:<b>
// 轉義:
\. \( \) \\ // 需 \\ 前綴
// 分隔符:
// 首字符是定界符,最後補修飾符
// '/' 內轉義寫成 '\/'

捕獲與斷言

捕獲組、非捕獲組、命名組與前後斷言。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 捕獲組:
/(\d{4})-(\d{2})/
// 匹配 2026-08
// 非捕獲組:
/(?:ab)+/
// 只分組不捕獲,性能更好
// 命名組:
/(?P<year>\d{4})-(?P<month>\d{2})/
// $m['year']
// 後向引用:
/(\w+)\s+\1/
// \1 引用第 1 組內容
// 斷言:
/(?=\d{5}$)/ // 前瞻
/(?<=^\d{3})/ // 後顧
// 組內交替:
/gr(a|e)y/

錨點與邊界

^ 行首 $ 行尾,\b 詞邊界,m 修飾符多行匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 行首行尾:
/^php/ // 以 php 開頭
/php$/ // 以 php 結尾
// 詞邊界:
/\bcat\b/ // 匹配獨立單詞 cat
// 不匹配 category、concatenate
// 多行模式:
// 修飾符 m 使 ^ $ 匹配每行
preg_match_all(
'/^\d+/m',
"12\nabc\n34",
$m,
);
// ['12', '34']
// 精確匹配:
// /^...$/ 加 \z 防尾隨換行
// 單詞邊界處理中文不適用

正則拆分

preg_split 按正則拆字符串,可保留分隔符與限制數量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 按正則拆分:
$parts = preg_split('/[,;]/', 'a,b;c');
// ['a', 'b', 'c']
// 保留分隔符:
preg_split('/(,)/', 'a,b', -1, PREG_SPLIT_DELIM_CAPTURE);
// ['a', ',', 'b']
// 限制數量:
preg_split('/\s+/', 'a b c d', 2);
// ['a', 'b c d']
// 分隔空白:
preg_split('/\s+/', ' a b ');
// ['', 'a', 'b'](首尾空)
// 需去空元素時組合 array_filter
// 簡單分隔用 explode 即可

多字節與 u 修飾符

處理中文加 u 修飾符,preg_quote 轉義用户輸入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 中文處理用 u 修飾符:
preg_match('/\p{Han}+/u', '你好世界abc', $m);
echo $m[0]; // 你好世界
// 多字節字符類:
/\p{Han}/ // 漢字
/\p{L}/ // 任意文字
/\p{N}/ // 數字
// 反轉義(危險):
// 用户輸入正則前先轉義:
$lit = preg_quote($_GET['q'], '/');
// 反轉義:
preg_match(
'/' . $lit . '/u',
'a+b',
$m,
);
// 用 u 修飾符避免亂碼

19.構建與工具鏈

Composer 依賴管理、自動加載與 CLI 構建流程。

初始化項目

composer init 生成 composer.json,聲明依賴與自動加載規則。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 初始化項目:
// $ composer init
// 生成 composer.json:
// {
// "name": "vendor/demo",
// "type": "project",
// "require": { "php": "^8.1" },
// "autoload": { "psr-4": { "App\\": "src/" } }
// }
// 交互式嚮導生成
// 之後執行 composer install
// PHP 版本約束寫在 require
// 用 ^8.1 表示 8.1.x 及以上

安裝依賴

composer install 按 lock 安裝,require 添加新依賴。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 安裝依賴:
// $ composer install
// 只裝生產依賴:
// $ composer install --no-dev
// 安裝單個包:
// $ composer require monolog/monolog
// 卸載:
// $ composer remove monolog/monolog
// 更新到最新約束版本:
// $ composer update
// 檢查依賴問題:
// $ composer validate
// $ composer audit
// 生產環境加 --no-dev
// 減少攻擊面與體積
// composer.lock 必須提交
// 鎖定精確版本可復現構建

引入依賴

require vendor/autoload.php 後即可 use 依賴類,自動加載生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 在項目裏使用依賴:
require __DIR__ . '/vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('app');
$log->pushHandler(
new StreamHandler('php://stderr'),
);
$log->info('啓動完成');
// 自動加載器由 install 生成
// 每次改動 autoload 配置需 dump:
// $ composer dump-autoload
// 類名到文件映射:
// PSR-4 命名空間對應目錄

PSR-4 自動加載

命名空間與目錄一一對應,App\ 映射到 src/,需 dump-autoload。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// composer.json:
// {
// "autoload": {
// "psr-4": {
// "App\\": "src/"
// }
// }
// }
// src/User.php:
// namespace App;
// class User {}
// 自動加載後:
use App\User;
$u = new User();
// 命名空間路徑一一對應:
// App\\Controller\\Home
// → src/Controller/Home.php
// 配置改動後重生成:
// $ composer dump-autoload
// PSR-4 只映射類前綴
// 其餘命名空間不受影響

Composer 腳本

composer.json scripts 定義命令別名,支持安裝鈎子回調。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// composer.json 腳本:
// {
// "scripts": {
// "test": "phpunit",
// "lint": "php -l src/*.php"
// }
// }
// 運行:
// $ composer test
// $ composer lint
// 內置腳本鈎子:
// pre-install-cmd、post-install-cmd
// 生命週期回調:
// 在安裝前後執行清理/權限設置
// 自定義命令組織到 scripts
// 避免手記長命令
// 跨平台路徑注意
// 用 / 而非反斜槓

版本約束

^、~、>= 等約束語法控制依賴版本範圍,lock 鎖精確版。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 版本約束語法:
// ^8.1 允許 8.1.x(>=8.1 <9)
// ~8.1 允許 8.1.x(<8.2)
// >=8.1 任意 >=8.1
// * 任意版本
// 1.2.3 精確版本
// 分支引用:
// dev-master
// v2.0.0-beta
// 鎖定精確版本:
// composer.lock 記錄實際版本
// 安裝指定版本:
// $ composer require foo/bar:^2.0
// 查看可升級:
// $ composer outdated
// 生產用鎖定版本部署

CLI 發佈命令

框架命令(artisan / console)執行遷移、緩存與定時任務。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 查看命令幫助:
// $ php bin/console list
// 運行自定義命令:
// $ php artisan migrate
// $ php bin/console cache:clear
// 生成應用密鑰:
// $ php artisan key:generate
// 數據庫遷移:
// $ php artisan migrate --seed
// 定時任務:
// $ crontab -e
// * * * * * php /path/app/cron.php
// 生產命令加 --env=production
// 與 CI 集成自動化部署
// 常用框架命令前綴:
// Symfony bin/console
// Laravel artisan

PHP 配置

php -i / php --ini / php -m 檢查運行時,ini_get 查詢配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 查看配置:
// $ php -i 全部配置
// $ php --ini 加載的 ini 路徑
// $ php -m 已加載擴展
// 檢查擴展:
// $ php -m | grep curl
// 運行時查詢:
echo ini_get('memory_limit');
echo ini_get('upload_max_filesize');
// 運行時修改:
ini_set('memory_limit', '512M');
// CLI 參數指定配置:
// $ php -d error_reporting=E_ALL app.php
// 生產配置檢查:
// $ php -v
// $ php --ri opcache

官方鏈接

直達官方文檔與資源。

關於本速查

本頁是 PHP 8.3 的自包含速查手冊,覆蓋語言核心與最常用標準庫在真實項目中約 80% 的常見用法。內容偏向現代慣用法:declare(strict_types=1) 嚴格模式、enum 枚舉、readonly 只讀屬性、match 表達式、命名參數、箭頭函數與 first-class callable。權威參考見官方 PHP 手冊與 PHP The Right Way。 19 個章節各自聚焦一個主題——從第一個程序到數組、面向對象與常見誤區。每節拆成 8–14 個帶示例的小節(每個 5–20 行),共約 150 個主題。代碼片段刻意短小、自解釋。 所有處理都在瀏覽器中完成——無上傳、無追蹤。本頁是 GuruToolkit 免費開發者工具集的一部分;代碼片段可自由使用,無任何擔保。

版本 2.1.0