PHP 5.3 引入閉包與匿名函數,為語言帶來函數式編程風格。然而實際開發中,PHP 閉包在陣列操作上的表現與 JavaScript、Scala、Groovy 等語言存在明顯差距。本文透過具體案例對比,探討 PHP 閉包的優缺點,並示範如何透過 ArrayObject 封裝打造更優雅的鏈式 API。
| 語言 | 程式碼 |
|---|---|
| PHP (傳統) | $res = [];
foreach ($nums as $n) {
if ($n > 15) $res[] = $n;
} |
| PHP 5.3+ 閉包 | $res = array_filter($nums, function($v) { return $v > 15; }); |
| JavaScript 1.6+ | var res = nums.filter(function(c){ return c > 15; }); |
| Groovy | def res = nums.findAll { it > 15 } |
| Scala | val res = nums filter (_ > 15) |
PHP 版本雖比傳統寫法簡潔,但字符數仍多於其他語言;且 array_filter 回調只接收值,無法取得鍵名。
需求:找出 >15 的元素,乘 2 再加上外部變數 $x。
| 語言 | 程式碼 |
|---|---|
| JavaScript | var i = 1; var res = nums.filter(c => c > 15).map(c => c * 2 + i); |
| Groovy | def x = 1
def res = nums.findAll { it > 15 }.collect { it * 2 + x } |
| Scala | val x = 1 val res = nums.filter(_ > 15).map(_ * 2 + x) |
| PHP | $x = 1;
$res = array_map(
function($v) use ($x) { return $v * 2 + $x; },
array_filter($nums, function($v) { return $v > 15; })
); |
PHP 必須使用 use 關鍵字顯式捕獲外部變數,且巢狀函數調用導致可讀性下降。更嚴重的是:無法在篩選階段使用鍵名進行判斷。
PHP SPL 提供 ArrayObject,可像陣列一樣操作物件。繼承並擴充 map、filter 方法,即可實現優雅的鏈式調用:
class Arr extends ArrayObject {
public static function make($array) {
return new self($array);
}
public function map(callable $func) {
$res = new self();
foreach ($this as $k => $v) {
$res[$k] = $func($k, $v); // 同時傳遞鍵與值
}
return $res;
}
public function filter(callable $func) {
$res = new self();
foreach ($this as $k => $v) {
if ($func($k, $v)) {
$res[$k] = $v;
}
}
return $res;
}
// 可續增 reduce、walk、each 等方法
}
$nums = [10, 20, 30, 40];
$res = Arr::make($nums)
->filter(function($k, $v) { return $v > 15; })
->map(function($k, $v) { return $v * 2; });
// 結果:[20 => 40, 30 => 60, 40 => 80] 保留原鍵名
優勢:
$key、$valuearray_filter、array_map 參數順序混淆reduce、walk、flatMap 等函數式常用操作Arr 實例,大陣列下有額外開銷fn($v) => $v > 15 簡化語法;array_filter($arr, 'callback', ARRAY_FILTER_USE_KEY) 可在內建函數中使用鍵名PHP 閉包雖非完美,但在 ArrayObject 封裝輔助下,能構建出語義優雅、功能強大的函數式陣列操作 API。選擇何種風格,取決於團隊習慣、專案規模與效能要求。隨著 PHP 版本演進(箭頭函數、array_is_list、array_any/array_all 等新函數),原生函數式支援持續增強,開發者可按需組合使用。