箭头函数 fn()
概述
箭头函数(Arrow Functions)是 PHP 7.4 引入的一种简洁的匿名函数语法。箭头函数使用 fn() => expression 的形式,自动捕获父作用域中的变量,无需 use 关键字。箭头函数适合简单的单表达式场景,使代码更加简洁可读。
PHP 版本说明
箭头函数自 PHP 7.4 引入。箭头函数始终自动捕获外部变量(按值传递),不支持引用传递 &$var。箭头函数只能包含单个表达式,不支持多行语句块。
基础概念
箭头函数 vs 匿名函数
| 特性 | 箭头函数 fn() => | 匿名函数 function() {} |
|---|---|---|
| 引入版本 | PHP 7.4+ | PHP 5.3+ |
| 变量捕获 | 自动(按值) | 需 use 显式声明 |
| 多行支持 | 不支持 | 支持 |
| 表达式数量 | 仅单个 | 任意 |
| 引用捕获 | 不支持 | 支持 &$var |
| 代码简洁度 | 更简洁 | 较冗长 |
语法与代码
基本语法
箭头函数使用 fn(参数) => 表达式 的语法,自动计算并返回表达式的值。
php
<?php
declare(strict_types=1);
// 基本箭头函数
$double = fn(int $n): int => $n * 2;
echo $double(5) . "\n"; // 10
// 等价的匿名函数
$doubleAnon = function (int $n): int {
return $n * 2;
};
// 无参数的箭头函数
$getTime = fn(): string => date('Y-m-d H:i:s');
echo $getTime() . "\n";
// 多参数的箭头函数
$formatName = fn(string $first, string $last): string => "{$last}, {$first}";
echo $formatName('Alice', 'Smith') . "\n"; // Smith, Alice自动捕获变量
箭头函数最大的特点是可以自动捕获父作用域中的变量,无需 use 关键字。
php
<?php
declare(strict_types=1);
// 箭头函数自动捕获外部变量
$factor = 3;
$multiply = fn(int $n): int => $n * $factor;
echo $multiply(10) . "\n"; // 30
// 对比匿名函数需要 use
// $multiplyAnon = function (int $n) use ($factor): int {
// return $n * $factor;
// };
// 自动捕获多个变量
$prefix = 'Hello';
$suffix = '!';
$greet = fn(string $name): string => "{$prefix}, {$name}{$suffix}";
echo $greet('Alice') . "\n"; // Hello, Alice!按值捕获
箭头函数只能按值捕获变量。如果外部变量在闭包创建后发生变化,箭头函数内部不会感知到变化。
在数组函数中使用
箭头函数最常用的场景是作为数组函数的回调参数,大幅简化代码。
php
<?php
declare(strict_types=1);
$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// array_map:映射
$doubled = array_map(fn(int $n): int => $n * 2, $numbers);
print_r($doubled);
// Array ( [0] => 2 [1] => 4 [2] => 6 ... )
// array_filter:过滤
$evens = array_filter($numbers, fn(int $n): bool => $n % 2 === 0);
print_r($evens);
// Array ( [1] => 2 [3] => 4 [5] => 6 ... )
// array_filter 带键
$users = ['alice' => 30, 'bob' => 25, 'charlie' => 35];
$adults = array_filter($users, fn(int $age): bool => $age >= 30);
print_r($adults);
// Array ( [alice] => 30 [charlie] => 35 )
// array_reduce:归约
$sum = array_reduce($numbers, fn(int $carry, int $n): int => $carry + $n, 0);
echo "Sum: {$sum}\n"; // Sum: 55
// usort:自定义排序
$people = [
['name' => 'Alice', 'age' => 30],
['name' => 'Bob', 'age' => 25],
['name' => 'Charlie', 'age' => 35],
];
usort($people, fn(array $a, array $b): int => $a['age'] <=> $b['age']);
print_r($people);箭头函数与匿名函数的区别
php
<?php
declare(strict_types=1);
// 1. 捕获时机不同
$x = 5;
$arrow = fn(): int => $x;
$anon = function () use ($x): int {
return $x;
};
$x = 10;
echo $arrow() . "\n"; // 10(自动捕获,捕获的是变量的引用快照)
echo $anon() . "\n"; // 5(use 在定义时捕获了值的副本)
// 注意:箭头函数虽然在定义后能感知变化,但实际上是按值捕获
// 箭头函数在调用时才读取外部变量的值(延迟绑定)
// 而 use 在定义时就确定了值
// 2. 不支持引用捕获
$counter = 0;
// 箭头函数不支持引用捕获
// $increment = fn(): int => ++$counter; // 不会改变外部 $counter
// 匿名函数支持引用捕获
$incrementAnon = function () use (&$counter): int {
return ++$counter;
};
echo $incrementAnon() . "\n"; // 1
echo $counter . "\n"; // 1嵌套箭头函数
箭头函数可以嵌套使用,适合构建简洁的数据处理管道。
php
<?php
declare(strict_types=1);
// 嵌套箭头函数:构建高阶函数
$add = fn(int $a): Closure => fn(int $b): int => $a + $b;
$add5 = $add(5);
echo $add5(3) . "\n"; // 8
echo $add5(10) . "\n"; // 15
// 组合箭头函数
$compose = fn(callable $f, callable $g): Closure => fn(mixed $x) => $f($g($x));
$toUpperCase = fn(string $s): string => strtoupper($s);
$addPrefix = fn(string $s): string => "Prefix: {$s}";
$transform = $compose($toUpperCase, $addPrefix);
echo $transform('hello') . "\n"; // PREFIX: HELLO
// 多层嵌套
$curry = fn(callable $fn): Closure => fn(mixed $a) => fn(mixed $b) => $fn($a, $b);
$curriedAdd = $curry(fn(int $a, int $b): int => $a + $b);
echo $curriedAdd(3)(4) . "\n"; // 7详细说明
箭头函数的自动变量捕获原理
箭头函数在调用时才读取外部变量的值,而 use 在定义时就捕获了值的副本。这意味着:
php
<?php
declare(strict_types=1);
// 箭头函数:调用时读取变量值
$outer = 1;
$arrow = fn(): int => $outer;
$outer = 2;
echo $arrow() . "\n"; // 2
// 但箭头函数不能修改变量(没有引用)
$outer = 1;
$arrow = fn(): int => $outer;
// 无法通过箭头函数改变 $outer
// 箭头函数内部的变量屏蔽同名外部变量
$name = 'Alice';
$getLength = fn(): int => strlen($name);
$name = 'Bob';
echo $getLength() . "\n"; // 3(Bob 的长度)类型声明的使用
箭头函数支持完整的参数和返回值类型声明。
php
<?php
declare(strict_types=1);
// 完整类型声明
$safeDivide = fn(int $a, int $b): ?float =>
$b !== 0 ? $a / $b : null;
echo $safeDivide(10, 3) . "\n"; // 3.333...
var_dump($safeDivide(10, 0)); // NULL
// 联合类型(PHP 8.0+)
$parseInput = fn(string $input): int|float|string => match (true) {
is_numeric($input) && str_contains($input, '.') => (float) $input,
is_numeric($input) => (int) $input,
default => $input,
};
echo gettype($parseInput('42')) . "\n"; // integer
echo gettype($parseInput('3.14')) . "\n"; // double
echo gettype($parseInput('hello')) . "\n"; // string箭头函数的限制
- 只能包含单个表达式
- 不支持引用传递
- 不能包含多条语句
php
<?php
declare(strict_types=1);
// 不能使用多行箭头函数
// $multi = fn($x) => {
// $temp = $x * 2;
// return $temp + 1;
// }; // 语法错误
// 解决方案:使用完整闭包或 match 表达式
$process = fn(int $x): int => match (true) {
$x > 0 => $x * 2,
$x < 0 => $x * -1,
default => 0,
};
echo $process(5) . "\n"; // 10
echo $process(-3) . "\n"; // 3
echo $process(0) . "\n"; // 0实战示例
数据转换管道
php
<?php
declare(strict_types=1);
$users = [
['name' => 'alice smith', 'email' => 'ALICE@EXAMPLE.COM', 'age' => '30'],
['name' => 'bob jones', 'email' => 'BOB@EXAMPLE.COM', 'age' => '25'],
['name' => 'charlie brown', 'email' => 'CHARLIE@EXAMPLE.COM', 'age' => '35'],
];
// 使用箭头函数链式处理数据
$normalized = array_map(
fn(array $user): array => [
'name' => ucwords($user['name']),
'email' => strtolower($user['email']),
'age' => (int) $user['age'],
'is_adult' => (int) $user['age'] >= 18,
],
$users
);
$filtered = array_filter(
$normalized,
fn(array $user): bool => $user['is_adult']
);
$sorted = array_values($filtered);
usort($sorted, fn(array $a, array $b): int => $a['age'] <=> $b['age']);
print_r($sorted);配置验证器
php
<?php
declare(strict_types=1);
function validateConfig(array $config): array
{
$rules = [
'host' => fn(mixed $v): bool => is_string($v) && strlen($v) > 0,
'port' => fn(mixed $v): bool => is_int($v) && $v > 0 && $v <= 65535,
'debug' => fn(mixed $v): bool => is_bool($v),
'timeout' => fn(mixed $v): bool => is_int($v) && $v > 0,
];
$errors = [];
foreach ($rules as $field => $rule) {
if (!isset($config[$field])) {
$errors[$field] = "Field '{$field}' is required";
} elseif (!$rule($config[$field])) {
$errors[$field] = "Field '{$field}' has invalid value";
}
}
return $errors;
}
$config = [
'host' => 'localhost',
'port' => 3306,
'debug' => true,
// 缺少 timeout
];
$errors = validateConfig($config);
print_r($errors);
// Array ( [timeout] => Field 'timeout' is required )注意事项
何时不用箭头函数
- 需要多行逻辑时
php
<?php
declare(strict_types=1);
// 复杂逻辑使用完整闭包
$processUser = function (array $user): array {
$name = trim($user['name']);
$email = strtolower($user['email']);
$age = (int) $user['age'];
if ($age < 0 || $age > 150) {
$age = 0;
}
return compact('name', 'email', 'age');
};- 需要引用传递时
php
<?php
declare(strict_types=1);
$sum = 0;
$numbers = [1, 2, 3, 4, 5];
// 箭头函数不能修改外部变量
// array_map(fn($n) => $sum += $n, $numbers); // 错误
// 使用完整闭包
array_map(function (int $n) use (&$sum): void {
$sum += $n;
}, $numbers);
echo $sum . "\n"; // 15- 需要 return 以外的控制流时
箭头函数只能有一个返回表达式,不支持 throw(除非在 match 中)、echo 等操作。
最佳实践
1. 简单转换优先用箭头函数
php
<?php
declare(strict_types=1);
$ids = [101, 102, 103, 104];
// 推荐:简洁明了
$usernames = array_map(
fn(int $id): string => "user_{$id}",
$ids
);
// 不推荐:过度使用完整闭包
// $usernames = array_map(function (int $id): string {
// return "user_{$id}";
// }, $ids);2. 利用 match 增强箭头函数的表达能力
php
<?php
declare(strict_types=1);
$statusMap = fn(int $code): string => match ($code) {
200 => 'OK',
301 => 'Moved Permanently',
404 => 'Not Found',
500 => 'Internal Server Error',
default => "Unknown ({$code})",
};
echo $statusMap(200) . "\n"; // OK
echo $statusMap(404) . "\n"; // Not Found3. 嵌套时保持可读性
php
<?php
declare(strict_types=1);
// 好的嵌套用法:柯里化
$partial = fn(string $prefix): Closure =>
fn(string $name): string =>
fn(string $suffix): string =>
"{$prefix}{$name}{$suffix}";
echo $partial('Dear ')('Alice')(', welcome!') . "\n";
// Dear Alice, welcome!