PHP 箭頭函數 (Arrow Function)
箭頭函數是 PHP 7.4 引入的新語法,提供了更簡潔的方式來撰寫簡單的匿名函數。箭頭函數使用 fn 關鍵字,並且會自動捕獲外部變數。
基本語法
fn(參數) => 表達式
<?php
// 傳統匿名函數
$double = function($n) {
return $n * 2;
};
// 箭頭函數
$double = fn($n) => $n * 2;
echo $double(5); // 10
?>
自動捕獲變數
箭頭函數最大的特點是會自動按值捕獲外部變數,不需要使用 use:
<?php
$factor = 2;
// 傳統匿名函數需要 use
$multiply = function($n) use ($factor) {
return $n * $factor;
};
// 箭頭函數自動捕獲
$multiply = fn($n) => $n * $factor;
echo $multiply(5); // 10
?>
捕獲多個變數
<?php
$greeting = "Hello";
$punctuation = "!";
$greet = fn($name) => "$greeting, $name$punctuation";
echo $greet("Alice"); // Hello, Alice!
?>
捕獲的是定義時的值
<?php
$x = 10;
$getX = fn() => $x;
$x = 20;
echo $getX(); // 10(捕獲的是定義時的值)
?>
箭頭函數是按值捕獲,無法傳址捕獲。如果需要修改外部變數,請使用傳統匿名函數搭配
use (&$var)。型別宣告
箭頭函數支援參數和回傳值的型別宣告:
<?php
// 參數型別
$double = fn(int $n) => $n * 2;
// 回傳型別
$double = fn(int $n): int => $n * 2;
// 可為 null 的型別
$greet = fn(?string $name): string => "Hello, " . ($name ?? "Guest");
echo $greet(null); // Hello, Guest
?>
作為回呼函數
箭頭函數非常適合用於需要回呼函數的場合:
<?php
$numbers = [1, 2, 3, 4, 5];
// array_map
$doubled = array_map(fn($n) => $n * 2, $numbers);
// [2, 4, 6, 8, 10]
// array_filter
$evens = array_filter($numbers, fn($n) => $n % 2 === 0);
// [2, 4]
// array_reduce
$sum = array_reduce($numbers, fn($carry, $n) => $carry + $n, 0);
// 15
// usort
$names = ['Charlie', 'Alice', 'Bob'];
usort($names, fn($a, $b) => strcmp($a, $b));
// ['Alice', 'Bob', 'Charlie']
?>
巢狀箭頭函數
箭頭函數可以巢狀使用:
<?php
$add = fn($a) => fn($b) => $a + $b;
echo $add(3)(5); // 8
$add3 = $add(3);
echo $add3(7); // 10
?>
限制
只能有單一表達式
箭頭函數的主體只能是單一表達式,不能有多行程式碼或陳述式:
<?php
// 正確:單一表達式
$double = fn($n) => $n * 2;
// 錯誤:不能有多行
// $process = fn($n) => {
// $result = $n * 2;
// return $result;
// };
// 需要多行時使用傳統匿名函數
$process = function($n) {
$result = $n * 2;
return $result;
};
?>
沒有回傳值時
如果不需要回傳值,箭頭函數仍會回傳表達式的結果:
<?php
$log = fn($msg) => print($msg); // print 會回傳 1
$result = $log("Hello");
echo $result; // 1
// 如果真的不需要回傳值,可能傳統匿名函數更合適
$log = function($msg): void {
echo $msg;
};
?>
無法傳址捕獲
<?php
$count = 0;
// 箭頭函數無法傳址捕獲
$increment = fn() => $count++; // 這不會修改外部的 $count
$increment();
echo $count; // 0
// 需要使用傳統匿名函數
$increment = function() use (&$count) {
$count++;
};
$increment();
echo $count; // 1
?>
箭頭函數 vs 匿名函數
| 特性 | 箭頭函數 | 匿名函數 |
|---|---|---|
| 語法 | fn() => expr | function() { ... } |
| 主體 | 單一表達式 | 多行程式碼 |
| 變數捕獲 | 自動按值捕獲 | 需要 use |
| 傳址捕獲 | 不支援 | 支援 use (&$var) |
| $this | 自動繼承 | 自動繼承 |
| PHP 版本 | 7.4+ | 5.3+ |
何時使用箭頭函數
適合使用
- 簡單的回呼函數
- 需要捕獲外部變數的單行函數
- 資料轉換和過濾
<?php
// 資料轉換
$users = [
['name' => 'Alice', 'age' => 25],
['name' => 'Bob', 'age' => 30],
];
$names = array_map(fn($u) => $u['name'], $users);
$adults = array_filter($users, fn($u) => $u['age'] >= 18);
?>
不適合使用
- 需要多行邏輯
- 需要修改外部變數
- 需要 void 回傳型別
<?php
// 需要多行邏輯 - 使用匿名函數
$process = function($data) {
$result = [];
foreach ($data as $item) {
if ($item['active']) {
$result[] = transform($item);
}
}
return $result;
};
// 需要修改外部變數 - 使用匿名函數
$counter = 0;
$increment = function() use (&$counter) {
$counter++;
};
?>
實際應用範例
排序
<?php
$products = [
['name' => 'Apple', 'price' => 100],
['name' => 'Banana', 'price' => 50],
['name' => 'Cherry', 'price' => 200],
];
// 按價格排序
usort($products, fn($a, $b) => $a['price'] <=> $b['price']);
// 按名稱排序
usort($products, fn($a, $b) => strcmp($a['name'], $b['name']));
// 價格降序
usort($products, fn($a, $b) => $b['price'] <=> $a['price']);
?>
資料提取
<?php
$users = [
['id' => 1, 'name' => 'Alice', 'email' => 'alice@example.com'],
['id' => 2, 'name' => 'Bob', 'email' => 'bob@example.com'],
['id' => 3, 'name' => 'Charlie', 'email' => 'charlie@example.com'],
];
// 提取 email
$emails = array_map(fn($u) => $u['email'], $users);
// 建立 id => name 對應
$nameById = array_combine(
array_map(fn($u) => $u['id'], $users),
array_map(fn($u) => $u['name'], $users)
);
// [1 => 'Alice', 2 => 'Bob', 3 => 'Charlie']
?>
條件過濾
<?php
$products = [
['name' => 'A', 'price' => 100, 'stock' => 5],
['name' => 'B', 'price' => 50, 'stock' => 0],
['name' => 'C', 'price' => 200, 'stock' => 10],
];
// 過濾有庫存的
$inStock = array_filter($products, fn($p) => $p['stock'] > 0);
// 過濾價格範圍
$minPrice = 60;
$maxPrice = 150;
$filtered = array_filter(
$products,
fn($p) => $p['price'] >= $minPrice && $p['price'] <= $maxPrice
);
?>
驗證
<?php
$validators = [
'email' => fn($v) => filter_var($v, FILTER_VALIDATE_EMAIL) !== false,
'phone' => fn($v) => preg_match('/^09\d{8}$/', $v),
'age' => fn($v) => is_numeric($v) && $v >= 0 && $v <= 150,
];
function validate(array $data, array $validators): array {
$errors = [];
foreach ($validators as $field => $validator) {
if (isset($data[$field]) && !$validator($data[$field])) {
$errors[] = "$field 格式不正確";
}
}
return $errors;
}
$data = ['email' => 'invalid', 'phone' => '0912345678', 'age' => 25];
$errors = validate($data, $validators);
// ['email 格式不正確']
?>
柯里化 (Currying)
<?php
// 柯里化函數
$curry = fn($a) => fn($b) => fn($c) => $a + $b + $c;
echo $curry(1)(2)(3); // 6
// 部分應用
$add1 = $curry(1);
$add1and2 = $add1(2);
echo $add1and2(3); // 6
?>
組合函數
<?php
$compose = fn($f, $g) => fn($x) => $f($g($x));
$addOne = fn($n) => $n + 1;
$double = fn($n) => $n * 2;
$addOneThenDouble = $compose($double, $addOne);
echo $addOneThenDouble(5); // 12 ((5 + 1) * 2)
$doubleThenAddOne = $compose($addOne, $double);
echo $doubleThenAddOne(5); // 11 ((5 * 2) + 1)
?>