1.PHP里的匿名函数实质是Closure类的实例
(1)不能自己实例化Closure类型的对象,会触发一个Error
try{ $closure = new Closure(); }catch(Error $e){ var_dump($e); }
(2)匿名函数如何使用父作用域的变量?
答案:使用use()将父作用域的变量加载到匿名函数对象的静态变量表中
$msg2 = 'Closure!'; $greet = function($msg1)use($msg2){ echo $msg1,' ',$msg2,'<br>'; }; $greet('Hello'); echo gettype($greet);//匿名函数是一个Object echo get_class($greet);//匿名函数是类Closure的实例
注:echo中使用','比'.'效率高,原因是echo执行效率比拼接字符串效率高
2.create_function()不是标准的匿名函数
create_function($params,$operation)创建一个函数并返回函数名称。
(1)返回的函数名称是以‘ ’开头的全局唯一函数名称:
$createFuncName = create_function('$arg1,$arg2','echo $arg1," ",$arg2;');//这里用单引号定义第一个参数是为了防止双引号中的变量被解析 echo '创建的函数名称看起来是:',$createFuncName,'<br>';//lambda_1 $createFuncName('Hello','create_function()!'); $myFuncName = trim($createFuncName);//这里不要写死lambda_1随着多运行几次函数返回的名称会有变化 try{ $myFuncName(); }catch(Error $e){ var_dump($e);//Error:Call to undefined function () } echo '实际上生成的函数名称第一个字符是 :','<br>'; var_dump($createFuncName);//string(9) "lambda_1" var_dump($myFuncName);//string(8) "lambda_1" //手动拼一个' '在前面就ok啦 $myFuncName = chr(0).$myFuncName; $myFuncName('Hello','real function name!'); echo gettype($createFuncName),'<br>';//string 不是匿名函数
create_function()返回一个函数名称,所以创建的不是标准的匿名函数,而是有系统生成了一个唯一的函数名。
3.难啃的骨头:Closure::bind()和Closure::bindTo()
(1)bind()
借用官方的示例:
class A { private static $sfoo = 1; private $ifoo = 2; } $cl1 = static function() { return A::$sfoo; }; $cl2 = function() { return $this->ifoo; }; $bcl1 = Closure::bind($cl1, null, 'A'); $bcl2 = Closure::bind($cl2, new A(), 'A'); echo $bcl1(), " ";//1 echo $bcl2(), " ";//2
这里使用一个php扩展zendump来看一下bind()后发生了什么变化:
class A { public static $psa = 3; private static $sfoo = 1; private $ifoo = 2; } //增加一个不进行bind()的匿名函数 $closure = function(){ $c = A::$psa; zendump_opcodes(); return $c; }; $cl1 = static function() { $a = A::$sfoo; zendump_opcodes();//使用扩展提供方法打印当前匿名函数执行内容 return $a; }; $cl2 = function() { $b = $this->ifoo; zendump($this);//使用扩展提供方法打印当前$this变量内容 zendump_opcodes(); return $b; }; $bcl1 = Closure::bind($cl1, null, 'A'); $bcl2 = Closure::bind($cl2, new A(), 'A'); echo $bcl1(), " "; echo $bcl2(), " "; echo $closure(), " "; zendump_class('A');//使用扩展提供方法打印类A的基本信息
1)echo $closure();这一步会打印一个没有被bind()的匿名函数执行情况:
op_array("{closure}") {closure}() refcount(2) addr(0x7f28c4461cb8) vars(1) T(3) filename(/var/www/html/index.php) line(47,51) OPCODE OP1 OP2 RESULT EXTENDED ZEND_FETCH_STATIC_PROP_R "psa" "A" #var0 ZEND_ASSIGN $c #var0 ZEND_INIT_FCALL 80 "zendump_opcodes" 0 ZEND_DO_ICALL ZEND_RETURN $c ZEND_RETURN null 3
这里描述执行的函数是{closure},内部获取了类A的静态变量psa;
2)再看echo $bcl1();这一步:
op_array("A::{closure}") {closure}() refcount(3) addr(0x7f28c4462078) vars(1) T(3) filename(/var/www/html/index.php) line(52,56) OPCODE OP1 OP2 RESULT EXTENDED ZEND_FETCH_STATIC_PROP_R "sfoo" "A" #var0 ZEND_ASSIGN $a #var0 ZEND_INIT_FCALL 80 "zendump_opcodes" 0 ZEND_DO_ICALL ZEND_RETURN $a ZEND_RETURN null 1
与1)中对比,明显区别是执行的函数被描述为:A::{closure},所以在这里可以获取类A的私有静态变量sfoo;
3)最后看看echo $bcl2();这一步:
先看$this的输出情况:
zval(0x7f28c44132f0) -> object(A) addr(0x7f28c4457310) refcount(2) { default_properties(1) { $ifoo => zval(0x7f28c4457338) : long(2) } static_members(2) { $psa => zval(0x7f28c4470900) : long(3) $sfoo => zval(0x7f28c4470910) : long(1) } }
这里描述匿名函数$bcl2中使用的$this是类A的对象,再看看匿名函数$bcl2的情况:
op_array("A::{closure}") {closure}() refcount(3) addr(0x7f28c44621b8) vars(1) T(5) filename(/var/www/html/index.php) line(57,62) OPCODE OP1 OP2 RESULT EXTENDED ZEND_FETCH_OBJ_R "ifoo" #var0 ZEND_ASSIGN $b #var0 ZEND_INIT_FCALL 96 "zendump" 1 ZEND_FETCH_THIS #var2 ZEND_SEND_VAR #var2 1 ZEND_DO_ICALL ZEND_INIT_FCALL 80 "zendump_opcodes" 0 ZEND_DO_ICALL ZEND_RETURN $b ZEND_RETURN null 2
这里描述匿名方法$bcl2为A::{closure},所以这里可以通过指向类A对象实例的$this获取类A的私有变量ifoo。
2)bindTo()
使用同上的方法查看官方示例代码的zendump情况:
class B { function __construct($val) { $this->val = $val; } function getClosure() { return function() { $val = $this->val; zendump($this); zendump_opcodes(); return $val; }; } } $ob1 = new B(1); $ob2 = new B(2); $cl = $ob1->getClosure(); echo $cl(), " "; $cl = $cl->bindTo($ob2); echo $cl(), " ";
1)先看第一次执行匿名函数的情况:
zval(0x7f28c4413290) -> object(B) addr(0x7f28c4456758) refcount(3) { properties(1) { "val" => zval(0x7f28c4461ca0) : long(1) } } op_array("B::{closure}") {closure}() refcount(2) addr(0x7f28c4461f38) vars(1) T(5) filename(/var/www/html/index.php) line(76,81) OPCODE OP1 OP2 RESULT EXTENDED ZEND_FETCH_OBJ_R "val" #var0 ZEND_ASSIGN $val #var0 ZEND_INIT_FCALL 96 "zendump" 1 ZEND_FETCH_THIS #var2 ZEND_SEND_VAR #var2 1 ZEND_DO_ICALL ZEND_INIT_FCALL 80 "zendump_opcodes" 0 ZEND_DO_ICALL ZEND_RETURN $val ZEND_RETURN null 1
这里匿名函数$cl内部的$this是B(1)实例,匿名函数被描述为B::{closure};
2)再看看bindTo()另一个类B实例后的变化:
zval(0x7f28c4413290) -> object(B) addr(0x7f28c44566e0) refcount(3) { properties(1) { "val" => zval(0x7f28c4461de0) : long(2) } } op_array("B::{closure}") {closure}() refcount(2) addr(0x7f28c4462078) vars(1) T(5) filename(/var/www/html/index.php) line(76,81) OPCODE OP1 OP2 RESULT EXTENDED ZEND_FETCH_OBJ_R "val" #var0 ZEND_ASSIGN $val #var0 ZEND_INIT_FCALL 96 "zendump" 1 ZEND_FETCH_THIS #var2 ZEND_SEND_VAR #var2 1 ZEND_DO_ICALL ZEND_INIT_FCALL 80 "zendump_opcodes" 0 ZEND_DO_ICALL ZEND_RETURN $val ZEND_RETURN null 2
这里$this的内容发生了变化,变成了B(2)实例,匿名函数仍然被描述为B::{closure}。
原理还是没有理清楚,不过可以通过A::{closure}这种描述以及$this的实例内容的变化,在一定程度上帮助理解bind()和bindTo()的表现。