匿名函数也叫闭包,是一个没有名字的函数。在需要值的地方写 function ($param) { ... }:把它赋给一个变量、传给另一个函数,或者作为返回值。它可以用 use ($var) 带上外围代码中的变量。
<?php
$greet = function (string $name): string {
return "Hello, $name";
}; // the semicolon ends the assignment
echo $greet('Ada'), "\n";
$greeting = 'Welcome';
$welcome = function (string $name) use ($greeting): string {
return "$greeting, $name";
};
echo $welcome('Linus'), "\n";
var_dump($greet instanceof Closure);
每个匿名函数都是内置类 Closure 的对象,所以它可以被保存、传递,之后再用 $greet(...) 调用。
use 关键字按值捕获
闭包自己看不到外部的变量。use ($x) 会在闭包创建的那一刻把 $x 的值复制进闭包,而不是在它运行的时候:
<?php
$discount = 10;
$apply = function (float $price) use ($discount): float {
return $price - $discount;
};
$discount = 50; // changed after the closure was created
echo $apply(100), "\n"; // still uses 10
// Several variables
$currency = 'EUR';
$rate = 1.08;
$convert = function (float $usd) use ($rate, $currency): string {
return number_format($usd / $rate, 2) . " $currency";
};
echo $convert(54), "\n";
按引用 use:&$var
use (&$x) 给闭包的是变量本身,而不是它的副本。闭包内部的修改在外部可见,外部的修改在内部也能看到。回调函数就是这样累加总数或维持计数器的。
<?php
$total = 0;
$addToTotal = function (int $amount) use (&$total): void {
$total += $amount;
};
$addToTotal(10);
$addToTotal(32);
echo $total, "\n";
// A counter that remembers its state between calls
function makeCounter(): Closure
{
$count = 0;
return function () use (&$count): int {
return ++$count;
};
}
$next = makeCounter();
echo $next(), $next(), $next(), "\n";
$other = makeCounter(); // its own $count
echo $other(), "\n";
每次调用 makeCounter() 都会创建一个新的 $count,返回的闭包在函数返回之后仍然让它保持存活。这就是“闭包”一词的含义:函数把它需要的变量“封闭”在了自己里面。
把闭包作为回调函数传递
array_map()、array_filter()、usort() 和 array_walk() 这类函数接收一个函数作为参数。用闭包可以直接在原地写出这个函数,需要多少条语句就写多少条:
<?php
$products = [
['name' => 'Laptop', 'price' => 1200, 'stock' => 3],
['name' => 'Mouse', 'price' => 25, 'stock' => 0],
['name' => 'Monitor', 'price' => 300, 'stock' => 7],
];
$minPrice = 100;
$available = array_filter($products, function ($p) use ($minPrice) {
return $p['stock'] > 0 && $p['price'] >= $minPrice;
});
usort($available, function ($a, $b) {
return $b['price'] <=> $a['price']; // most expensive first
});
$labels = array_map(function ($p) {
$label = strtoupper($p['name']);
return "$label ({$p['stock']} left)";
}, $available);
print_r($labels);
当函数体只有一个表达式时,像 fn($p) => $p['price'] 这样的箭头函数更短,而且不需要 use。
callable:函数名、方法和 strlen(...)
类型为 callable 的参数接受的不只是闭包。包含函数名的字符串、包含对象和方法名的数组,以及包含类名和静态方法名的数组,都算 callable。从 PHP 8.1 开始,一等可调用语法 strlen(...) 可以把其中任何一种直接变成 Closure。
<?php
class Formatter
{
public function shout(string $s): string { return strtoupper($s) . '!'; }
public static function whisper(string $s): string { return strtolower($s) . '...'; }
}
$words = ['Hello', 'World'];
$f = new Formatter();
print_r(array_map('strtoupper', $words)); // function name
print_r(array_map([$f, 'shout'], $words)); // object method
print_r(array_map([Formatter::class, 'whisper'], $words)); // static method
// PHP 8.1 first-class callable syntax
$len = strlen(...);
$shout = $f->shout(...);
$whisper = Formatter::whisper(...);
echo $len('closure'), ' ', $shout('hey'), ' ', $whisper('PSST'), "\n";
function apply(callable $fn, string $value): string
{
return $fn($value);
}
echo apply('ucfirst', 'php'), "\n";
var_dump(is_callable('no_such_function'));
(...) 形式在这一行运行时就会被检查:拼写错误会当场抛出 Error。而像 'strtoupr' 这样的字符串,在有人尝试调用它之前只是一个字符串。
类中的闭包:$this 和 static
在方法内部创建的闭包会自动绑定 $this,所以它可以使用对象的属性。如果闭包不需要 $this,就把它标记为 static function,这样它也不会让对象一直保持存活。
<?php
class Cart
{
private array $items = [];
private float $taxRate = 0.2;
public function add(string $name, float $price): void
{
$this->items[$name] = $price;
}
public function pricesWithTax(): array
{
// $this is available inside the closure
return array_map(function (float $price): float {
return round($price * (1 + $this->taxRate), 2);
}, $this->items);
}
public function names(): array
{
return array_map(static function (string $name): string {
return ucfirst($name);
}, array_keys($this->items));
}
}
$cart = new Cart();
$cart->add('book', 12.5);
$cart->add('pen', 1.99);
print_r($cart->pricesWithTax());
print_r($cart->names());
忘记写 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
$people = [['name' => 'Ada', 'age' => 36], ['name' => 'Tom', 'age' => 12]];
$minAge = 18;
$adults = array_filter($people, function ($p) use ($minAge) {
return $p['age'] >= $minAge;
});
print_r(array_column($adults, 'name'));
// Or an arrow function, which captures $minAge automatically
$adults2 = array_filter($people, fn($p) => $p['age'] >= $minAge);
print_r(array_column($adults2, 'name'));