• PHP学习笔记(一):理解匿名函数与Closure


    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()的表现。

  • 相关阅读:
    PHPExcel读取excel03/07版到数组
    firefox 自定义快捷键
    phpstorm 自定义函数配置
    解决FPDF报错:FPDF error: Not a JPEG file / FPDF error: Not a PNG file
    mysql学习笔记
    定时备份服务器数据库(借助windows任务计划以及mysqldump)
    discuz 注册用户用到的几个表
    phpstorm配置取消掉63342
    discuz random函数
    discuz X3.2邮箱非必填
  • 原文地址:https://www.cnblogs.com/ling-diary/p/9121398.html
Copyright © 2020-2023  润新知