Menu

PHP 匿名函数与闭包:use 关键字

匿名函数(闭包)是保存在变量中的没有名字的函数:$greet = function ($name) { return "Hi $name"; };。学习 use 关键字、按引用捕获、callable、一等可调用语法 strlen(...),以及类中的闭包。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

匿名函数也叫闭包,是一个没有名字的函数。在需要值的地方写 function ($param) { ... }:把它赋给一个变量、传给另一个函数,或者作为返回值。它可以用 use ($var) 带上外围代码中的变量。

每个匿名函数都是内置类 Closure 的对象,所以它可以被保存、传递,之后再用 $greet(...) 调用。

use 关键字按值捕获

闭包自己看不到外部的变量。use ($x) 会在闭包创建的那一刻把 $x 的值复制进闭包,而不是在它运行的时候:

按引用 use:&$var

use (&$x) 给闭包的是变量本身,而不是它的副本。闭包内部的修改在外部可见,外部的修改在内部也能看到。回调函数就是这样累加总数或维持计数器的。

每次调用 makeCounter() 都会创建一个新的 $count,返回的闭包在函数返回之后仍然让它保持存活。这就是“闭包”一词的含义:函数把它需要的变量“封闭”在了自己里面。

把闭包作为回调函数传递

array_map()、array_filter()、usort() 和 array_walk() 这类函数接收一个函数作为参数。用闭包可以直接在原地写出这个函数,需要多少条语句就写多少条:

当函数体只有一个表达式时,像 fn($p) => $p['price'] 这样的箭头函数更短,而且不需要 use。

callable:函数名、方法和 strlen(...)

类型为 callable 的参数接受的不只是闭包。包含函数名的字符串、包含对象和方法名的数组,以及包含类名和静态方法名的数组,都算 callable。从 PHP 8.1 开始,一等可调用语法 strlen(...) 可以把其中任何一种直接变成 Closure。

(...) 形式在这一行运行时就会被检查:拼写错误会当场抛出 Error。而像 'strtoupr' 这样的字符串,在有人尝试调用它之前只是一个字符串。

类中的闭包:$this 和 static

在方法内部创建的闭包会自动绑定 $this,所以它可以使用对象的属性。如果闭包不需要 $this,就把它标记为 static function,这样它也不会让对象一直保持存活。

忘记写 use

最常见的闭包 bug,是使用了外部变量却没有把它列在 use 里。在闭包内部,这个变量根本不存在:

$minAge = 18;
$adults = array_filter($people, function ($p) {
    return $p['age'] >= $minAge;   // $minAge is not in use (...)
});
PHP prints: Warning: Undefined variable $minAge

$minAge 在闭包内部是 null,任何年龄和 >= null 比较都是 true,于是每个人都通过了过滤。在隐藏警告的服务器上,没有任何东西会提醒你。把变量加进 use:

常见问题

PHP 中的闭包是什么?

一个没有名字的函数,用 function (...) { ... } 创建,保存在变量中或作为参数传递。PHP 把它表示为 Closure 类的对象。它可以带上创建它时所在作用域中的变量,这些变量列在 use (...) 里。

PHP 闭包中的 use 关键字有什么作用?

use ($x) 在闭包创建时把外部变量 $x 复制到闭包中。之后在外部对 $x 的修改,闭包里看不到。如果想共享这个变量,写 use (&$x),这样修改会双向生效。

PHP 中的 callable 是什么?

PHP 能调用的任何东西:闭包、字符串形式的函数名('strtoupper')、数组 [$object, 'method'] 或 [ClassName::class, 'staticMethod'],或者带有 __invoke() 的对象。用 callable 类型接收其中任意一种,用 is_callable() 检查。

PHP 中的 strlen(...) 是什么意思?

这是 PHP 8.1 引入的一等可调用语法:strlen(...) 从函数创建一个 Closure,但不调用它。它也适用于方法 $obj->method(...) 和静态方法 Foo::bar(...),并且和字符串形式的函数名不同,它在这一行运行时就会被检查。

匿名函数后面需要加分号吗?

赋值时需要:$f = function () { ... }; 是一条赋值语句,所以要以 ; 结尾。漏掉分号会导致解析错误。直接作为参数传递的闭包本身不需要分号。

Coddy 编程语言插图

和 Coddy 一起学编程

开始