PHP 繼承了 Perl 強大的正則、C 的指標語義、Shell 的字串處理,但許多開發者僅停留在 `echo`、`if`、`foreach` 表層。本文深入三大核心領域:**變數/陣列進階操作 (引用、可變變數、函數作為值)**、**PCRE 正則表達式實戰 (遞迴、前瞻、效能)**、**模板引擎原理與手寫實作 (詞法分析、編譯、快取)**,並給出進階練習題。
<?php
$var = 'hello';
$varName = 'var';
echo $$varName; // hello
function greet(string $name): string { return "Hello $name"; }
$func = 'greet';
echo $func('World'); // Hello World
// 陣列作為呼叫式
$obj = new class { public function run() { return 'running'; } };
[$obj, 'run'](); // PHP 7.1+
?>
<?php
function byRef(&$v) { $v++; }
function byVal($v) { $v++; }
$a = 1;
byRef($a); echo $a; // 2
byVal($a); echo $a; // 2 (注意:標量無 COW 行為)
$arr = [1,2,3];
$ref = &$arr; // 硬引用
$copy = $arr; // COW:實際未複製,修改時才分離
$copy[0] = 99;
echo $arr[0]; // 1 (已分離)
$ref[0] = 88;
echo $arr[0]; // 88 (同一記憶體)
?>
<?php
$arr = [1,2,3];
foreach ($arr as &$v) $v *= 2; // $v 為引用
unset($v); // **必須 unset**,否則 $v 仍指向最後一個元素
foreach ($arr as $v) { /* ... */ } // 此處 $v 為值複製,但若未 unset 會修改 $arr[2]
?>
<?php
$users = [
['id'=>1, 'name'=>'A', 'roles'=>['admin']],
['id'=>2, 'name'=>'B', 'roles'=>['user']],
];
$names = array_column($users, 'name'); // ['A','B']
$ids = array_column($users, 'id', 'name'); // ['A'=>1, 'B'=>2]
$sum = array_reduce($users, fn($carry, $u) => $carry + $u['id'], 0); // 3
// Generator 避免大陣列記憶體
function fetchUsers(PDO $pdo): Generator {
$stmt = $pdo->query('SELECT * FROM users');
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) yield $row;
}
foreach (fetchUsers($pdo) as $user) { /* 處理單筆 */ }
?>
| 模式 | 說明 |
|---|---|
| `/pattern/iu` | i=大小寫不敏感, u=UTF-8 模式 (必加) |
| `\p{L}`, `\p{N}` | Unicode 字母/數字 (需 u 修飾符) |
| `(? | 命名捕獲組, `$matches['name']` 存取 |
| `(?:...)` | 非捕獲組, 分組不佔用編號 |
| `(?=...)`, `(?!...)` | 正向/負向前瞻 (零寬斷言) |
| `(?>...)` | 原子組, 禁止回溯, 提升效能 |
<?php
// 解析 CSS 色碼 #RGB, #RRGGBB, rgb(...), rgba(...)
$css = 'color: #f00; background: rgba(255,0,0,0.5); border: 1px solid #abcdef;';
preg_match_all('/
\#(?:[0-9a-f]{3}|[0-9a-f]{6})\b
| rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)
| rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[\d.]+?\s*\)
/ix', $css, $m);
print_r($m[0]);
// 郵件地址 (RFC 5322 簡化版)
$emailPattern = '/^(?[^@\s]+)@(?[^@\s]+\.[^@\s]+)$/';
preg_match($emailPattern, 'test@example.com', $m);
echo $m['local']; // test
?>
<?php
// 惡意輸入導致指數級回溯
$evil = str_repeat('a', 10000) . '!';
$pattern = '/(a+)+b/'; // 災難性
// preg_match($pattern, $evil); // 會卡死
// 優化:使用原子組或佔有量詞
$pattern = '/(?>a+)b/'; // 或 /a++b/ (PHP 不支援 ++,用原子組)
$pattern = '/a+b/'; // 簡單情況無回溯問題
?>
原則:避免巢狀量詞 `(.*)*`、在固定前綴後使用 `.*?`、善用 `(*SKIP)(*FAIL)` 排除區塊。
<?php
class TinyTemplate {
private string $cacheDir;
public function __construct(string $cacheDir) { $this->cacheDir = $cacheDir; }
public function render(string $tplName, array $vars = []): string {
$cacheFile = $this->cacheDir . '/' . md5($tplName) . '.php';
if (!is_file($cacheFile) || filemtime($cacheFile) < filemtime($tplName)) {
$this->compile($tplName, $cacheFile);
}
extract($vars, EXTR_SKIP);
ob_start();
include $cacheFile;
return ob_get_clean();
}
private function compile(string $tplFile, string $cacheFile): void {
$tpl = file_get_contents($tplFile);
// 簡單語法:{{ $var }} ->
$php = preg_replace_callback('/\{\{\s*(\$[\w->]+)\s*\}\}/', function($m) {
return '';
}, $tpl);
// {% if $cond %}...{% endif %}
$php = preg_replace('/\{\%\s*if\s+(\$[\w->]+)\s*\%\}(.*?)\{\%\s*endif\s*\%\}/s',
'$2', $php);
file_put_contents($cacheFile, 'render(__DIR__.'/tpl/home.html', ['title' => '首頁', 'user' => ['name'=>'Admin']]);
?>
| 引擎 | 特色 | 適用 |
|---|---|---|
| Twig | Symfony 標準、沙箱、繼承、擴充性強、編譯快取 | 大型專案、安全性要求高 |
| Blade | Laravel 內建、語法糖豐富、指令系統、零配置 | Laravel 專案 |
| Plates | 原生 PHP、無編譯、輕量、易學 | 中小專案、不想學新語法 |
| Latte | Nette 核心、類型安全、自動逃逸、效能極佳 | 追求型別安全、高效能 |