Skip to content

匿名函数(闭包)

概述

匿名函数(Anonymous Functions),也叫闭包(Closures),是没有指定名称的函数。匿名函数自 PHP 5.3 引入,广泛应用于回调、事件处理、高阶函数等场景。匿名函数通过 Closure 类实现,支持通过 use 从父作用域继承变量,也可以通过 bindbindTo 绑定不同的作用域。

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);

注意事项

常见陷阱

  1. 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
  1. 闭包中的 $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"; // c

3. 使用 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

参考链接