匿名函数(闭包)
概述
匿名函数(Anonymous Functions),也叫闭包(Closures),是没有指定名称的函数。匿名函数自 PHP 5.3 引入,广泛应用于回调、事件处理、高阶函数等场景。匿名函数通过 Closure 类实现,支持通过 use 从父作用域继承变量,也可以通过 bind 和 bindTo 绑定不同的作用域。
PHP 版本说明
- 匿名函数自 PHP 5.3 引入
Closure::bind()和bindTo()自 PHP 5.4 支持- 闭包作为参数时自动垃圾回收机制在 PHP 7.x 中改进
Closure::fromCallable()自 PHP 7.1 引入- 匿名函数支持
...展开参数自 PHP 5.6
基础概念
什么是匿名函数
匿名函数是没有名字的函数,通常作为值被赋给变量或直接作为参数传递给其他函数。在 PHP 中,匿名函数是 Closure 类的实例。
闭包 vs 匿名函数
严格来说,匿名函数是没有名字的函数,而闭包(Closure)是能够捕获外部作用域变量的匿名函数。但在 PHP 社区中,这两个术语通常互换使用。
语法与代码
基本语法
php
<?php
declare(strict_types=1);
// 基本匿名函数
$greet = function (string $name): string {
return "Hello, {$name}!";
};
echo $greet('Alice') . "\n"; // Hello, Alice!
echo $greet('Bob') . "\n"; // Hello, Bob!
// 匿名函数的类型是 Closure
echo get_class($greet) . "\n"; // Closure
// 作为回调传递
$names = ['Alice', 'Bob', 'Charlie'];
$greeted = array_map(function (string $name): string {
return "Hello, {$name}!";
}, $names);
print_r($greeted);
// Array ( [0] => Hello, Alice! [1] => Hello, Bob! [2] => Hello, Charlie! )use 从父作用域继承变量
匿名函数默认不能访问外部变量。需要通过 use 语言结构显式继承。
php
<?php
declare(strict_types=1);
// 不使用 use:无法访问外部变量
$multiplier = 3;
// $double = function (int $n): int {
// return $n * $multiplier; // Undefined variable $multiplier
// };
// 使用 use 继承变量(值传递)
$triple = function (int $n) use ($multiplier): int {
return $n * $multiplier;
};
echo $triple(5) . "\n"; // 15
// use 传值 vs 引用传递
$counter = 0;
// 值传递:捕获的是定义时的值副本
$incrementByValue = function () use ($counter): int {
return ++$counter;
};
// 引用传递:捕获的是变量的引用
$incrementByRef = function () use (&$counter): int {
return ++$counter;
};
echo $incrementByValue() . "\n"; // 1
echo $incrementByValue() . "\n"; // 2(内部计数器)
echo $counter . "\n"; // 0(外部不受影响)
echo $incrementByRef() . "\n"; // 1
echo $incrementByRef() . "\n"; // 2
echo $counter . "\n"; // 2(外部同步变化)
// 继承多个变量
$name = 'Alice';
$age = 30;
$introduce = function () use ($name, $age): string {
return "I am {$name}, {$age} years old.";
};
echo $introduce() . "\n"; // I am Alice, 30 years old.use 与外部变量更新
use 默认按值传递,即捕获变量定义时的值副本。如果外部变量后续发生变化,闭包内部不会感知。使用引用传递 &$var 才能同步变化。
Closure 类方法
Closure 类提供了几个实用方法,可以动态绑定闭包的作用域和 $this 上下文。
php
<?php
declare(strict_types=1);
class App
{
private string $name = 'MyApp';
public function getGreeter(): Closure
{
return function (): string {
return "Hello from {$this->name}";
};
}
public function run(): void
{
// 直接调用:$this 绑定到当前对象
$greeter = $this->getGreeter();
echo $greeter() . "\n"; // Hello from MyApp
}
}
class Framework
{
private string $name = 'Framework';
public function test(Closure $closure): void
{
// 使用 bindTo 将闭包绑定到当前对象
$bound = $closure->bindTo($this);
echo $bound() . "\n"; // Hello from Framework
}
}
$app = new App();
$app->run(); // Hello from MyApp
$fw = new Framework();
$greeter = $app->getGreeter();
$fw->test($greeter); // Hello from Framework
// Closure::bind() 静态方法
$bound = Closure::bind($greeter, new Framework(), Framework::class);
echo $bound() . "\n"; // Hello from Framework闭包作为回调
匿名函数最常见的用途是作为回调传递给各种 PHP 内置函数和自定义函数。
php
<?php
declare(strict_types=1);
// array_map
$numbers = [1, 2, 3, 4, 5];
$doubled = array_map(fn(int $n): int => $n * 2, $numbers);
print_r($doubled); // Array ( [0] => 2 [1] => 4 [2] => 6 [3] => 8 [4] => 10 )
// array_filter
$data = [10, '', 'hello', 0, null, 'world'];
$nonEmpty = array_filter($data, fn($v) => $v !== null && $v !== '' && $v !== 0);
print_r($nonEmpty); // Array ( [0] => 10 [2] => hello [4] => world )
// array_reduce
$sum = array_reduce($numbers, fn(int $carry, int $item): int => $carry + $item, 0);
echo "Sum: {$sum}\n"; // Sum: 15
// usort 自定义排序
$users = [
['name' => 'Charlie', 'age' => 25],
['name' => 'Alice', 'age' => 30],
['name' => 'Bob', 'age' => 20],
];
usort($users, function (array $a, array $b): int {
return $a['age'] <=> $b['age'];
});
print_r($users);
// Array ( [0] => Array ( [name] => Bob [age] => 20 ) [1] => Array ( [name] => Charlie [age] => 25 ) [2] => Array ( [name] => Alice [age] => 30 ) )
// preg_replace_callback
$text = 'Hello 123 World 456';
$result = preg_replace_callback('/\d+/', function (array $match): string {
return '[' . $match[0] . ']';
}, $text);
echo $result . "\n"; // Hello [123] World [456]详细说明
闭包与垃圾回收
PHP 5.2 引入了垃圾回收机制(GC),闭包如果持有对外部对象的引用,可能阻止这些对象被回收。
php
<?php
declare(strict_types=1);
class Resource
{
public function __construct(public readonly string $name) {}
public function __destruct()
{
echo "Resource '{$this->name}' destroyed\n";
}
}
$resource = new Resource('test');
// 闭包持有 $resource 的引用
$callback = function () use ($resource): void {
echo "Using: {$resource->name}\n";
};
$callback(); // Using: test
// 取消引用以允许垃圾回收
unset($resource);
unset($callback); // Resource 'test' destroyed闭包的类型声明
闭包本身可以使用类型声明,也可以声明接受的闭包参数类型。
php
<?php
declare(strict_types=1);
// 闭包参数的类型声明
function applyCallback(callable $callback, mixed $data): mixed
{
return $callback($data);
}
// 使用匿名函数作为回调
$result = applyCallback(
fn(int $n): int => $n * 2,
21
);
echo $result . "\n"; // 42
// Closure 类型提示(比 callable 更严格)
function executeClosure(Closure $closure, mixed ...$args): mixed
{
return $closure(...$args);
}
echo executeClosure(fn(string $s) => strtoupper($s), 'hello') . "\n"; // HELLO递归匿名函数
由于匿名函数没有名称,要实现递归需要将闭包赋给变量并引用它。
php
<?php
declare(strict_types=1);
// 方法 1:将闭包赋给变量,通过 use 引用自身
$factorial = function (int $n) use (&$factorial): int {
if ($n <= 1) {
return 1;
}
return $n * $factorial($n - 1);
};
echo $factorial(5) . "\n"; // 120
// 方法 2:使用 Y Combinator 模式(更函数式)
$Y = function (callable $f) {
return function (mixed $x) use ($f) {
return $f(fn(mixed $y) => ($x($x))($y));
};
};
$factorialGen = $Y(fn(callable $self) => fn(int $n) => $n <= 1 ? 1 : $n * $self($n - 1));
echo $factorialGen(5) . "\n"; // 120实战示例
简单的中间件系统
php
<?php
declare(strict_types=1);
interface MiddlewareInterface
{
public function handle(Closure $next, string $request): string;
}
class AuthMiddleware implements MiddlewareInterface
{
public function handle(Closure $next, string $request): string
{
if (str_contains($request, 'unauthorized')) {
return "[AUTH BLOCKED] {$request}";
}
return $next($request);
}
}
class LogMiddleware implements MiddlewareInterface
{
public function handle(Closure $next, string $request): string
{
echo "LOG: Processing '{$request}'\n";
return $next($request);
}
}
function createPipeline(Closure $handler, MiddlewareInterface ...$middlewares): Closure
{
$pipeline = array_reduce(
array_reverse($middlewares),
fn(Closure $next, MiddlewareInterface $mw) => fn(string $req) => $mw->handle($next, $req),
$handler
);
return $pipeline;
}
// 使用
$finalHandler = fn(string $req): string => "[OK] {$req}";
$auth = new AuthMiddleware();
$log = new LogMiddleware();
$pipeline = createPipeline($finalHandler, $log, $auth);
echo $pipeline('GET /api/users') . "\n";
// LOG: Processing 'GET /api/users'
// [OK] GET /api/users
echo $pipeline('GET /unauthorized') . "\n";
// [AUTH BLOCKED] GET /unauthorized事件发射器
php
<?php
declare(strict_types=1);
class EventEmitter
{
/** @var array<string, Closure[]> */
private array $listeners = [];
public function on(string $event, Closure $listener): void
{
$this->listeners[$event][] = $listener;
}
public function emit(string $event, mixed ...$data): void
{
if (!isset($this->listeners[$event])) {
return;
}
foreach ($this->listeners[$event] as $listener) {
$listener(...$data);
}
}
}
$emitter = new EventEmitter();
$emitter->on('userCreated', function (array $user): void {
echo "User created: {$user['name']} ({$user['email']})\n";
});
$emitter->on('userCreated', function (array $user): void {
echo "Welcome email sent to {$user['email']}\n";
});
$emitter->on('orderPlaced', function (int $orderId, float $total): void {
echo "Order #{$orderId} placed, total: ¥{$total}\n";
});
$emitter->emit('userCreated', ['name' => 'Alice', 'email' => 'alice@example.com']);
$emitter->emit('orderPlaced', 1001, 299.9);注意事项
常见陷阱
- use 捕获的是定义时的值
php
<?php
declare(strict_types=1);
$value = 10;
$closure = function () use ($value): int {
return $value;
};
$value = 20;
echo $closure() . "\n"; // 10(仍然是旧值)
// 如果需要最新值,使用引用传递
$value2 = 10;
$closure2 = function () use (&$value2): int {
return $value2;
};
$value2 = 20;
echo $closure2() . "\n"; // 20- 闭包中的 $this
php
<?php
declare(strict_types=1);
class Example
{
private string $value = 'instance';
public function getClosure(): Closure
{
// PHP 7.1+: 自动绑定 $this
return function (): string {
return $this->value;
};
}
public static function getStaticClosure(): Closure
{
// 静态方法中没有 $this
return function (): string {
// return $this->value; // Fatal error: Using $this when not in object context
return 'no context';
};
}
}
$ex = new Example();
$closure = $ex->getClosure();
echo $closure() . "\n"; // instance最佳实践
1. 短单表达式优先使用箭头函数
php
<?php
declare(strict_types=1);
// 简单操作:使用箭头函数(PHP 7.4+)
$doubled = array_map(fn(int $n): int => $n * 2, [1, 2, 3]);
// 复杂操作:使用完整闭包
$result = array_filter($data, function (array $item): bool {
if ($item['status'] !== 'active') {
return false;
}
if ($item['score'] < 60) {
return false;
}
return true;
});2. 避免在循环中创建闭包时的变量陷阱
php
<?php
declare(strict_types=1);
// 错误:所有闭包共享同一个 $item 的引用
$closures = [];
foreach (['a', 'b', 'c'] as $item) {
$closures[] = function () use ($item): string {
return $item;
};
}
// 所有闭包都返回 'c'
// 正确:使用引用传递并手动管理
$closures = [];
foreach (['a', 'b', 'c'] as $item) {
$closures[] = (function () use (&$item): string {
return $item;
})();
}
// 推荐:直接在闭包参数中传递
$items = ['a', 'b', 'c'];
$closures = array_map(fn(string $item): Closure => fn(): string => $item, $items);
echo $closures[0]() . "\n"; // a
echo $closures[1]() . "\n"; // b
echo $closures[2]() . "\n"; // c3. 使用 Closure::fromCallable 创建闭包
php
<?php
declare(strict_types=1);
function existingFunction(string $s): string
{
return strtoupper($s);
}
// 从已有函数创建闭包
$closure = Closure::fromCallable('existingFunction');
echo $closure('hello') . "\n"; // HELLO
// 从对象方法创建
class StringHelper
{
public static function reverse(string $s): string
{
return strrev($s);
}
}
$reverseClosure = Closure::fromCallable([StringHelper::class, 'reverse']);
echo $reverseClosure('hello') . "\n"; // olleh