匿名函数

匿名函数,也称为 闭包,允许创建没有指定名称的函数。它们作为 可调用 参数的值最为有用,但它们还有许多其他用途。

匿名函数使用 Closure 类实现。

示例 #1 匿名函数示例

<?php
echo preg_replace_callback('~-([a-z])~', function ($match) {
return
strtoupper($match[1]);
},
'hello-world');
// 输出 helloWorld
?>

闭包也可以用作变量的值;PHP 会自动将这些表达式转换为 Closure 内部类的实例。将闭包分配给变量使用与任何其他分配相同的语法,包括尾随分号。

示例 #2 匿名函数变量分配示例

<?php
$greet
= function($name) {
printf("Hello %s\r\n", $name);
};

$greet('World');
$greet('PHP');
?>

闭包还可以从父级作用域继承变量。所有此类变量都必须传递给 use 语言结构。从 PHP 7.1 开始,这些变量不能包含 超级全局变量$this 或与参数同名的变量。函数的返回类型声明必须放在 use 子句之后。

示例 #3 从父级作用域继承变量

<?php
$message
= 'hello';

// 没有 "use"
$example = function () {
var_dump($message);
};
$example();

// 继承 $message
$example = function () use ($message) {
var_dump($message);
};
$example();

// 继承的变量值来自函数定义时的值,而不是调用时的值
$message = 'world';
$example();

// 重置 message
$message = 'hello';

// 通过引用继承
$example = function () use (&$message) {
var_dump($message);
};
$example();

// 父级作用域中的更改值
// 会反映在函数调用中
$message = 'world';
$example();

// 闭包也可以接受常规参数
$example = function ($arg) use ($message) {
var_dump($arg . ' ' . $message);
};
$example("hello");

// 返回类型声明放在 use 子句之后
$example = function () use ($message): string {
return
"hello $message";
};
var_dump($example());
?>

上面的示例将输出类似于以下内容

Notice: Undefined variable: message in /example.php on line 6
NULL
string(5) "hello"
string(5) "hello"
string(5) "hello"
string(5) "world"
string(11) "hello world"
string(11) "hello world"

从 PHP 8.0.0 开始,作用域继承变量列表可以包含尾随逗号,它将被忽略。

从父级作用域继承变量与使用全局变量不同。全局变量存在于全局作用域中,无论执行什么函数,它都是一样的。闭包的父级作用域是声明闭包的函数(不一定是被调用的函数)。请看以下示例

示例 #4 闭包和作用域

<?php
// 一个基本的购物车,包含一个已添加商品的列表
// 和每种商品的数量。包含一个方法,使用
// 闭包作为回调来计算购物车中商品的总价格。
class Cart
{
const
PRICE_BUTTER = 1.00;
const
PRICE_MILK = 3.00;
const
PRICE_EGGS = 6.95;

protected
$products = array();

public function
add($product, $quantity)
{
$this->products[$product] = $quantity;
}

public function
getQuantity($product)
{
return isset(
$this->products[$product]) ? $this->products[$product] :
FALSE;
}

public function
getTotal($tax)
{
$total = 0.00;

$callback =
function (
$quantity, $product) use ($tax, &$total)
{
$pricePerItem = constant(__CLASS__ . "::PRICE_" .
strtoupper($product));
$total += ($pricePerItem * $quantity) * ($tax + 1.0);
};

array_walk($this->products, $callback);
return
round($total, 2);
}
}

$my_cart = new Cart;

// 向购物车中添加一些商品
$my_cart->add('butter', 1);
$my_cart->add('milk', 3);
$my_cart->add('eggs', 6);

// 打印总价,包含 5% 的销售税。
print $my_cart->getTotal(0.05) . "\n";
// 结果是 54.29
?>

示例 #5 自动绑定 $this

<?php

class Test
{
public function
testing()
{
return function() {
var_dump($this);
};
}
}

$object = new Test;
$function = $object->testing();
$function();

?>

上面的示例将输出

object(Test)#1 (0) {
}

在类的上下文中声明时,当前类将自动绑定到它,使 $this 在函数的范围内可用。如果不需要自动绑定当前类,则可以使用 静态匿名函数 来代替。

静态匿名函数

匿名函数可以静态声明。这将阻止它们自动绑定到当前类。对象也不能在运行时绑定到它们。

示例 #6 尝试在静态匿名函数中使用 $this

<?php

class Foo
{
function
__construct()
{
$func = static function() {
var_dump($this);
};
$func();
}
};
new
Foo();

?>

上面的示例将输出

Notice: Undefined variable: this in %s on line %d
NULL

示例 #7 尝试将对象绑定到静态匿名函数

<?php

$func
= static function() {
// 函数体
};
$func = $func->bindTo(new stdClass);
$func();

?>

上面的示例将输出

Warning: Cannot bind an instance to a static closure in %s on line %d

变更日志

版本 描述
7.1.0 匿名函数不能闭包 超级全局变量$this 或任何与参数同名的变量。

说明

注意: 可以使用 func_num_args()func_get_arg()func_get_args() 从闭包中调用。

添加备注

用户贡献的备注 20 备注

325
orls
14 年前
当“导入”变量到闭包的范围时要注意——很容易错过/忘记它们实际上是*复制*到闭包的范围中,而不是仅仅是可用。

因此,如果您闭包关心它们的内容,那么您需要明确地以引用方式将它们传递进去

<?php
$result
= 0;

$one = function()
{
var_dump($result); };

$two = function() use ($result)
{
var_dump($result); };

$three = function() use (&$result)
{
var_dump($result); };

$result++;

$one(); // 输出 NULL: $result 不在范围内
$two(); // 输出 int(0): $result 被复制了
$three(); // 输出 int(1)
?>

另一个不那么简单的对象示例(我实际踩到的坑)

<?php
// 预先设置变量
$myInstance = null;

$broken = function() uses ($myInstance)
{
if(!empty(
$myInstance)) $myInstance->doSomething();
};

$working = function() uses (&$myInstance)
{
if(!empty(
$myInstance)) $myInstance->doSomething();
}

//$myInstance 可能已实例化,也可能没有
if(SomeBusinessLogic::worked() == true)
{
$myInstance = new myClass();
}

$broken(); // 将永远不会做任何事情: $myInstance 在这个闭包中将始终为 null。
$working(); // 如果 $myInstance 实例化了,将调用 doSomething

?>
33
erolmon dot kskn at gmail dot com
9 年前
<?php
/*
(字符串) $name 您将在类中添加的函数名称。
用法:$Foo->add(function(){},$name);
这将在 Foo 类中添加一个公有函数。
*/
class Foo
{
public function
add($func,$name)
{
$this->{$name} = $func;
}
public function
__call($func,$arguments){
call_user_func_array($this->{$func}, $arguments);
}
}
$Foo = new Foo();
$Foo->add(function(){
echo
"Hello World";
},
"helloWorldFunction");
$Foo->add(function($parameterone){
echo
$parameterone;
},
"exampleFunction");
$Foo->helloWorldFunction(); /*输出:Hello World*/
$Foo->exampleFunction("Hello PHP"); /*输出:Hello PHP*/
?>
29
cHao
10 years ago
如果你想知道(因为我之前也想知道),匿名函数可以像命名函数一样返回引用。只需像使用命名函数一样使用 & 符号...紧跟在 `function` 关键字之后(以及紧靠在不存在的名称之前)。

<?php
$value
= 0;
$fn = function &() use (&$value) { return $value; };

$x =& $fn();
var_dump($x, $value); // 'int(0)', 'int(0)'
++$x;
var_dump($x, $value); // 'int(1)', 'int(1)'
13
dexen dot devries at gmail dot com
6 years ago
每个 lambda 实例都有自己的静态变量实例。这为事件处理程序、累加器等提供了很好的支持。

使用 `function() { ... };` 表达式创建新的 lambda 会创建其静态变量的新实例。将 lambda 赋值给变量不会创建新实例。lambda 是类 Closure 的对象,将 lambda 赋值给变量与将对象实例赋值给变量具有相同的语义。

示例脚本:$a 和 $b 有独立的静态变量实例,因此产生不同的输出。但是 $b 和 $c 共享它们的静态变量实例 - 因为 $c 指向与 $b 相同的类 Closure 的对象 - 因此产生相同的输出。

#!/usr/bin/env php
<?php

function generate_lambda() : Closure
{
# 创建 lambda 的新实例
return function($v = null) {
static
$stored;
if (
$v !== null)
$stored = $v;
return
$stored;
};
}

$a = generate_lambda(); # 创建 statics 的新实例
$b = generate_lambda(); # 创建 statics 的新实例
$c = $b; # 使用与 $b 相同的 statics 实例

$a('test AAA');
$b('test BBB');
$c('test CCC'); # 这会覆盖 $b 持有的内容,因为它引用了相同的对象

var_dump([ $a(), $b(), $c() ]);
?>

此测试脚本输出
array(3) {
[0]=>
string(8) "test AAA"
[1]=>
string(8) "test CCC"
[2]=>
string(8) "test CCC"
}
15
a dot schaffhirt at sedna-soft dot de
15 years ago
在类中使用匿名函数作为属性时,请注意存在三个命名空间:一个用于常量,一个用于属性,一个用于方法。这意味着,您可以同时对常量、属性和方法使用相同的名称。

由于从 PHP 5.3.0 开始,属性也可以是匿名函数,因此当它们共享相同的名称时会出现一个奇怪现象,但这并不意味着会发生冲突。

考虑以下示例

<?php
class MyClass {
const
member = 1;

public
$member;

public function
member () {
return
"method 'member'";
}

public function
__construct () {
$this->member = function () {
return
"anonymous function 'member'";
};
}
}

header("Content-Type: text/plain");

$myObj = new MyClass();

var_dump(MyClass::member); // int(1)
var_dump($myObj->member); // object(Closure)#2 (0) {}
var_dump($myObj->member()); // string(15) "method 'member'"
$myMember = $myObj->member;
var_dump($myMember()); // string(27) "anonymous function 'member'"
?>

这意味着,常规方法调用按预期工作,与以前一样。匿名函数必须首先检索到一个变量中(就像一个属性一样),然后才能调用。

此致
9
ayon at hyurl dot com
7 years ago
递归调用匿名函数的一种方法是使用 USE 关键字并将函数本身的引用传递给它

<?php
$count
= 1;
$add = function($count) use (&$add){
$count += 1;
if(
$count < 10) $count = $add($count); // 递归调用
return $count;
};
echo
$add($count); // 将输出 10,如预期
?>
11
simon at generalflows dot com
13 years ago
<?php

/*
* 此示例展示了如何使用闭包来实现类似 Python 的装饰器模式。
*
* 我的目标是,你应该能够用任何其他函数来装饰一个函数,然后直接调用被装饰的函数:
*
* 定义函数:$foo = function($a, $b, $c, ...) {...}
* 定义装饰器:$decorator = function($func) {...}
* 装饰它:$foo = $decorator($foo)
* 调用它:$foo($a, $b, $c, ...)
*
* 此示例展示了用于服务的身份验证装饰器,使用简单的
* 模拟会话和模拟服务。
*/

session_start();

/*
* 定义一个示例装饰器。装饰器函数应该采用以下形式:
* $decorator = function($func) {
* return function() use ($func) {
* // 做一些事情,然后在需要时调用被装饰的函数:
* $args = func_get_args($func);
* call_user_func_array($func, $args);
* // 做其他事情。
* };
* };
*/
$authorise = function($func) {
return function() use (
$func) {
if (
$_SESSION['is_authorised'] == true) {
$args = func_get_args($func);
call_user_func_array($func, $args);
}
else {
echo
"访问被拒绝";
}
};
};

/*
* 定义一个要装饰的函数,在此示例中为一个需要
* 授权的模拟服务。
*/
$service = function($foo) {
echo
"服务返回: $foo";
};

/*
* 装饰它。确保用装饰后的函数替换原始函数引用;例如 $authorise($service) 不会起作用,所以应该做 $service = $authorise($service)
*/
$service = $authorise($service);

/*
* 建立模拟授权,调用服务;应该得到
* '服务返回:test 1'。
*/
$_SESSION['is_authorised'] = true;
$service('test 1');

/*
* 删除模拟授权,调用服务;应该得到 '访问被拒绝'。
*/
$_SESSION['is_authorised'] = false;
$service('test 2');

?>
8
rob at ubrio dot us
14 年前
你可以使用 __call() 方法始终调用受保护的成员 - 这类似于你在 Ruby 中使用 send 来绕过这种情况。

<?php

class Fun
{
protected function
debug($message)
{
echo
"DEBUG: $message\n";
}

public function
yield_something($callback)
{
return
$callback("Soemthing!!");
}

public function
having_fun()
{
$self =& $this;
return
$this->yield_something(function($data) use (&$self)
{
$self->debug("正在对数据进行处理");
// 对 $data 进行处理
$self->debug("完成对数据的处理。");
});
}

// 妙啊!
public function __call($method, $args = array())
{
if(
is_callable(array($this, $method)))
return
call_user_func_array(array($this, $method), $args);
}
}

$fun = new Fun();
echo
$fun->having_fun();

?>
10
mail at mkharitonov dot net
10 years ago
PHP 和 JavaScript 闭包的一些比较。

=== 示例 1(按值传递) ===
PHP 代码
<?php
$aaa
= 111;
$func = function() use($aaa){ print $aaa; };
$aaa = 222;
$func(); // 输出 "111"
?>

类似的 JavaScript 代码
<script type="text/javascript">
var aaa = 111;
var func = (function(aaa){ return function(){ alert(aaa); } })(aaa);
aaa = 222;
func(); // 输出 "111"
</script>

注意,以下代码与之前的代码不类似
<script type="text/javascript">
var aaa = 111;
var bbb = aaa;
var func = function(){ alert(bbb); };
aaa = 222;
func(); // 输出 "111",但只有在函数声明后 "bbb" 未被更改的情况下

// 而且这种技术在循环中不起作用
var functions = [];
for (var i = 0; i < 2; i++)
{
var i2 = i;
functions.push(function(){ alert(i2); });
}
functions[0](); // 输出 "1",错误!
functions[1](); // 输出 "1",正确
</script>

=== 示例 2(按引用传递) ===
PHP 代码
<?php
$aaa
= 111;
$func = function() use(&$aaa){ print $aaa; };
$aaa = 222;
$func(); // 输出 "222"
?>

类似的 JavaScript 代码
<script type="text/javascript">
var aaa = 111;
var func = function(){ alert(aaa); };
aaa = 222; // 输出 "222"
func();
</script>
13
john at binkmail dot com
7 years ago
性能基准测试 2017!

我决定比较一个保存的闭包与在每次循环迭代时不断创建相同的匿名闭包。我尝试了 1000 万次循环迭代,在 2016 年 12 月的 PHP 7.0.14 中。结果

一个保存在变量中并重复使用的闭包(1000 万次迭代):1.3874590396881 秒

每次都创建一个新的匿名闭包(1000 万次迭代):2.8460240364075 秒

换句话说,在 1000 万次迭代中,每次迭代都重新创建闭包,只在运行时间中增加了 "1.459 秒"。这意味着在我的 7 年老的双核笔记本电脑上,每次创建新的匿名闭包大约需要 146 纳秒。我认为 PHP 会为匿名函数保存一个缓存的 "模板",因此创建闭包的新实例不需要太多时间!

所以你不需要担心在紧凑的循环中反复重新创建匿名闭包!至少在 PHP 7 中是这样的!绝对没有必要将一个实例保存在变量中并重复使用它。不受这种限制是一件好事,因为这意味着你可以随意在需要的地方使用匿名函数,而不是在代码的其他地方定义它们。:-)
8
derkontrollfreak+9hy5l at gmail dot com
10 years ago
注意,从 PHP 5.4 开始,将闭包注册为在同一对象范围中实例化的对象属性会导致循环引用,从而阻止立即销毁对象
<?php

class Test
{
private
$closure;

public function
__construct()
{
$this->closure = function () {
};
}

public function
__destruct()
{
echo
"destructed\n";
}
}

new
Test;
echo
"finished\n";

/*
* PHP 5.3 中的结果:
* ------------------
* destructed
* finished
*
* 从 PHP 5.4 开始的结果:
* ---------------------
* finished
* destructed
*/

?>

为了解决这个问题,你可以在静态方法中实例化闭包
<?php

public function __construct()
{
$this->closure = self::createClosure();
}

public static function
createClosure()
{
return function () {
};
}

?>
6
jake dot tunaley at berkeleyit dot com
5 年前
注意,在分配给静态变量的匿名函数中使用 $this。

<?php
class Foo {
public function
bar() {
static
$anonymous = null;
if (
$anonymous === null) {
// 表达式不允许作为静态初始化器,因此使用变通方法
$anonymous = function () {
return
$this;
};
}
return
$anonymous();
}
}

$a = new Foo();
$b = new Foo();
var_dump($a->bar() === $a); // True
var_dump($b->bar() === $a); // 也是 True
?>

在静态匿名函数中,$this 将是第一次调用该方法的任何对象实例的值。

要获得你可能预期的行为,你需要将 $this 上下文传递给函数。

<?php
Foo {
public function
bar() {
static
$anonymous = null;
if (
$anonymous === null) {
// 表达式不能作为静态初始化器解决方法
$anonymous = function (self $thisObj) {
return
$thisObj;
};
}
return
$anonymous($this);
}
}

$a = new Foo();
$b = new Foo();
var_dump($a->bar() === $a); // True
var_dump($b->bar() === $a); // False
?>
11
toonitw at gmail dot com
6 years ago
从 PHP 7.0 开始,您可以通过用 () 括起匿名函数来使用 IIFE(立即执行函数表达式)。

<?php
$type
= 'number';
var_dump( ...( function() use ($type) {
if (
$type=='number') return [1,2,3];
else if (
$type=='alphabet') return ['a','b','c'];
} )() );
?>
1
Hayley Watson
10 个月前
"如果不需要自动绑定当前类,则可以使用静态匿名函数代替。"

您可能不希望自动绑定的主要原因是,只要为匿名函数创建的 Closure 对象存在,它就会保留对生成它的对象的引用,从而阻止该对象被销毁,即使该对象在程序中的其他任何地方都不再存在,即使函数本身不使用 $this。

<?php

Foo
{
public function
__construct(private string $id)
{
echo
"创建 Foo " . $this->id, "\n";
}
public function
gimme_function()
{
return function(){};
}
public function
gimme_static_function()
{
return static function(){};
}
public function
__destruct()
{
echo
"销毁 Foo " . $this->id, "\n";
}
}

echo
"对象在最后一个引用被移除后立即被销毁。\n";
$t = new Foo('Alice');
$t = new Foo('Bob'); // 导致 Alice 被销毁。
// 现在销毁 Bob。
unset($t);
echo
"---\n";

echo
"非静态匿名函数保留对创建它的对象的引用。\n";
$u = new Foo('Carol');
$ufn = $u->gimme_function();
$u = new Foo('Daisy'); // 不会导致 Carol 被销毁,
// 因为 $ufn 持有的函数仍然对
// 它有引用。
unset($u); // 导致 Daisy 被销毁。
echo "---\n"; // 注意 Carol 还没有被销毁。

echo "静态匿名函数不会保留对创建它的对象的引用。\n";
$v = new Foo('Eve');
$vfn = $v->gimme_static_function();
$v = new Foo('Farid'); // $vfn 持有的函数
// 不保留对 Eve 的引用,因此 Eve 在这里会被销毁。
unset($v); // 销毁 Farid
echo "---\n";
// 然后程序结束,丢弃对任何仍在活动中的对象的引用
// (具体来说,是 Carol)。
?>

由于 $ufn 在程序结束时仍然存在,因此 Carol 也得以幸存。$vfn 也在程序结束时仍然存在,但它包含的函数被声明为静态的,因此没有保留对 Eve 的引用。

保留对已死对象的引用的匿名函数因此可能是内存泄漏的潜在来源。如果该函数对生成它的对象没有用,那么将其声明为静态可以防止它导致对象超出其使用期限。
7
kdelux at gmail dot com
14 年前
这是一个定义变量($this)并在 Closure 函数中使用的示例。下面的代码探索了所有用法,并展示了限制。

此代码段中最有用的工具是 requesting_class() 函数,它将告诉您哪个类负责执行当前 Closure()。

概述
-----------------------
成功找到调用对象的引用。
成功调用 $this(__invoke);
成功引用 $$this->name;
成功调用 call_user_func(array($this, 'method'))

失败:通过 $this-> 引用任何内容
失败:$this->name = '';
失败:$this->delfect();

<?php



function requesting_class()
{
foreach(
debug_backtrace(true) as $stack){
if(isset(
$stack['object'])){
return
$stack['object'];
}
}

}






class
Person
{
public
$name = '';
public
$head = true;
public
$feet = true;
public
$deflected = false;

function
__invoke($p){ return $this->$p; }
function
__toString(){ return 'this'; } // 测试引用

function __construct($name){ $this->name = $name; }
function
deflect(){ $this->deflected = true; }

public function
shoot()
{
// 如果定义了 customAttack,则使用它作为射击结果。否则射击脚部
if(is_callable($this->customAttack)){
return
call_user_func($this->customAttack);
}

$this->feet = false;
}
}

$p = new Person('Bob');


$p->customAttack =
function(){

echo
$this; // 注意:未定义变量:this

#$this = new Class() // 致命错误

// 用于分配变量 '$this' 的技巧
extract(array('this' => requesting_class())); // 确定哪个类负责调用 Closure

var_dump( $this ); // 被动引用有效
var_dump( $$this ); // 添加到类:function __toString(){ return 'this'; }

$name = $this('name'); // 成功
echo $name; // 输出:Bob
echo '<br />';
echo $
$this->name;

call_user_func_array(array($this, 'deflect'), array()); // 成功调用

#$this->head = 0; //** 致命错误:在非对象上下文中使用 $this
$$this->head = 0; // 成功设置值

};

print_r($p);

$p->shoot();

print_r($p);


die();

?>
4
mike at borft dot student dot utwente dot nl
12 年前
由于可以将闭包分配给类变量,因此不能直接调用它们实在可惜。例如,以下代码无效
<?php
class foo {

public
test;

public function
__construct(){
$this->test = function($a) {
print
"$a\n";
};
}
}

$f = new foo();

$f->test();
?>

然而,使用魔术方法 `__call` 可以实现。
<?php
class foo {

public
test;

public function
__construct(){
$this->test = function($a) {
print
"$a\n";
};
}

public function
__call($method, $args){
if (
$this->{$method} instanceof Closure ) {
return
call_user_func_array($this->{$method},$args);
} else {
return
parent::__call($method, $args);
}
}
}
$f = new foo();
$f->test();
?>
it
希望对大家有所帮助 ;)
3
Anonymous
15 years ago
如果你想检查你是否处理的是一个闭包,而不是一个字符串或数组回调,你可以这样做

<?php
$isAClosure
= is_callable($thing) && is_object($thing);
?>
1
petersenc at gmail dot com
14 天前
从 PHP 8.3.9 开始,PHP 不允许在 `use` 语句中使用类型提示。考虑以下 Laravel 路由

Route::get('/tags/{tag}', function (string $tag) use ($posts): View {
$tagPosts = $posts->filter(
function (Post $post) use ($tag): bool {
return in_array($tag, $post->tags);
}
);

return view('tags.show', [
'posts' => $tagPosts,
'tag' => $tag
]);
});

正如你所见,我可以在闭包中通过类型提示参数和返回值类型来使代码更简洁。但是 `use` 不允许类型提示。
0
Hayley Watson
2 个月前
如果你在对象属性中存储了一个闭包(或其他可调用对象),并且你想调用它,你可以使用圆括号来区分它与方法调用。

<?php
class Test
{
public
$callable;

function
__construct()
{
$this->callable = function($a) { return $a + 2; };
}
}

$t = new Test;

echo (
$t->callable)(40);
?>
-2
gabriel dot totoliciu at ddsec dot net
14 年前
如果你想创建一个递归闭包,你需要这样写

$some_var1="1";
$some_var2="2";

function($param1, $param2) use ($some_var1, $some_var2)
{

// 这里添加一些代码

call_user_func(__FUNCTION__, $other_param1, $other_param2);

// 这里添加一些代码

}

如果你需要按引用传递值,你应该查看

https://php.net/manual/en/function.call-user-func.php
https://php.net/manual/en/function.call-user-func-array.php

如果你想知道 `$some_var1` 和 `$some_var2` 是否仍然可以通过 `call_user_func` 使用,是的,它们是可用的。
To Top