Skip to content

箭头函数 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

箭头函数的限制

  1. 只能包含单个表达式
  2. 不支持引用传递
  3. 不能包含多条语句
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 )

注意事项

何时不用箭头函数

  1. 需要多行逻辑时
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');
};
  1. 需要引用传递时
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
  1. 需要 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 Found

3. 嵌套时保持可读性

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!

参考链接