zoukankan      html  css  js  c++  java
  • 深入了解PHP闭包的使用以及实现

    一、介绍
    
    匿名函数(Anonymous functions),也叫闭包函数(closures),允许 临时创建一个没有指定名称的函数。最经常用作回调函数(callback)参数的值。当然,也有其它应用的情况。
    
     
    
    二、使用场景
    
    1、动态调用静态类的时候
    
    
    <?php
    class test
    {
        public static function getinfo()
        {
            var_dump(func_get_args());
        }
    }
     
    call_user_func(array('test', 'getinfo'), 'hello world');
     
    
    2、在callback函数中使用
    
    
    <?php
    //eg array_walk array_map preg_replace_callback etc
     
    echo preg_replace_callback('~-([a-z])~', function ($match) {
        return strtoupper($match[1]);
    }, 'hello-world');
    // 输出 helloWorld
    ?>
     
    
    3、赋值给一个普通的变量
    
    
    <?php
    $greet = function($name)
    {
        printf("Hello %s
    ", $name);
    };
     
    $greet('World');
    $greet('PHP');
    ?>
     4、使用use从父域中继承
    
    
    <?php
    $message = 'hello';
     
    // 继承 $message
    $example = function () use ($message) {
        var_dump($message);
    };
    echo $example();
     
     
    // Inherit by-reference
    $example = function () use (&$message) {
        var_dump($message);
    };
    echo $example();
     
    // The changed value in the parent scope
    // is reflected inside the function call
    $message = 'world';
    echo $example();
     5、传递参数
    
    
    <?php
    $example = function ($arg) use ($message) {
        var_dump($arg . ' ' . $message);
    };
    $example("hello");
     6、OO中的使用
    
    
    <?php
     
    class factory{
        private $_factory;
        public function set($id,$value){
            $this->_factory[$id] = $value;
        }
         
        public function get($id){
            $value = $this->_factory[$id];
            return $value();
        }
    }
    class User{
        private $_username;
        function __construct($username="") {
            $this->_username = $username;
        }
        function getUserName(){
            return $this->_username;
        }
    }
     
    $factory = new factory();
     
    $factory->set("zhangsan",function(){
        return new User('张三');
    });
    $factory->set("lisi",function(){
       return new User("李四");
    });
    echo $factory->get("zhangsan")->getUserName();
    echo $factory->get("lisi")->getUserName();
     
    
    7、函数中的调用
    
    
    <?php
     
    function call($callback){
                $callback();
        }
    call(function() {
        var_dump('hell world');
    });
     
    
    三、分析
    
    第一个例子
    
    
    [root@chenpingzhao www]# php-cgi -dvld.active=1 k1.php 
    Finding entry points
    Branch analysis from position: 0
    Jump found. Position 1 = -2
    filename:       /data/www/k1.php
    function name:  (null)
    number of ops:  11
    compiled vars:  none
    line     #* E I O op                           fetch          ext  return  operands
    -------------------------------------------------------------------------------------
       4     0  E >   EXT_STMT                                                
      11     1        EXT_STMT                                                
             2        EXT_FCALL_BEGIN                                         
             3        INIT_ARRAY                                       ~0      'foo'
             4        ADD_ARRAY_ELEMENT                                ~0      'func'
             5        SEND_VAL                                                 ~0
             6        INIT_ARRAY                                       ~0      'hello+world'
             7        SEND_VAL                                                 ~0
             8        DO_FCALL                                      2          'call_user_func_array'
             9        EXT_FCALL_END                                           
      12    10      > RETURN                                                   1
     
    branch: #  0; line:     4-   12; sop:     0; eop:    10; out1:  -2
    path #1: 0,
    Class foo:
    Function func:
    Finding entry points
    Branch analysis from position: 0
    Jump found. Position 1 = -2
    filename:       /data/www/k1.php
    function name:  func
    number of ops:  11
    compiled vars:  none
    line     #* E I O op                           fetch          ext  return  operands
    -------------------------------------------------------------------------------------
       5     0  E >   EXT_NOP                                                 
       7     1        EXT_STMT                                                
             2        EXT_FCALL_BEGIN                                         
             3        EXT_FCALL_BEGIN                                         
             4        DO_FCALL                                      0  $0      'func_get_args'
             5        EXT_FCALL_END                                           
             6        SEND_VAR_NO_REF                               6          $0
             7        DO_FCALL                                      1          'var_dump'
             8        EXT_FCALL_END                                           
       8     9        EXT_STMT                                                
            10      > RETURN                                                   null
     
    branch: #  0; line:     5-    8; sop:     0; eop:    10; out1:  -2
    path #1: 0,
    End of function func
     
    End of class foo.
     
    X-Powered-By: PHP/5.5.23
    Content-type: text/html
     
    
     没有这个DECLARE_LAMBDA_FUNCTION 这个步骤,说明这个和自己实现的闭包是两码事
    
     
    
    第三个例子比较简单,我们分析一下好了
    
    
    [root@localhost www]# php-cgi -dvld.active=1 k3.php
    Finding entry points
    Branch analysis from position: 0
    Jump found. Position 1 = -2
    filename:       /data/www/k3.php
    function name:  (null)
    number of ops:  17
    compiled vars:  !0 = $greet
    line     #* E I O op                           fetch          ext  return  operands
    -------------------------------------------------------------------------------------
       2     0  E >   EXT_STMT                                                
             1        DECLARE_LAMBDA_FUNCTION                                  '%00%7Bclosure%7D%2Fdata%2Fwww%2Fk3.php0xa67ff017'
       5     2        ASSIGN                                                   !0, ~0
       7     3        EXT_STMT                                                
             4        INIT_FCALL_BY_NAME                                       !0
             5        EXT_FCALL_BEGIN                                         
             6        SEND_VAL                                                 'World'
             7        DO_FCALL_BY_NAME                              1         
             8        EXT_FCALL_END                                           
       8     9        EXT_STMT                                                
            10        INIT_FCALL_BY_NAME                                       !0
            11        EXT_FCALL_BEGIN                                         
            12        SEND_VAL                                                 'PHP'
            13        DO_FCALL_BY_NAME                              1         
            14        EXT_FCALL_END                                           
      10    15        EXT_STMT                                                
            16      > RETURN                                                   1
     
    branch: #  0; line:     2-   10; sop:     0; eop:    16; out1:  -2
    path #1: 0,
    Function %00%7Bclosure%7D%2Fdata%2Fwww%2Fk3.php0xa67ff01:
    Finding entry points
    Branch analysis from position: 0
    Jump found. Position 1 = -2
    filename:       /data/www/k3.php
    function name:  {closure}
    number of ops:  10
    compiled vars:  !0 = $name
    line     #* E I O op                           fetch          ext  return  operands
    -------------------------------------------------------------------------------------
       2     0  E >   EXT_NOP                                                 
             1        RECV                                             !0     
       4     2        EXT_STMT                                                
             3        EXT_FCALL_BEGIN                                         
             4        SEND_VAL                                                 'Hello+%25s%0D%0A'
             5        SEND_VAR                                                 !0
             6        DO_FCALL                                      2          'printf'
             7        EXT_FCALL_END                                           
       5     8        EXT_STMT                                                
             9      > RETURN                                                   null
     
    branch: #  0; line:     2-    5; sop:     0; eop:     9; out1:  -2
    path #1: 0,
    End of function %00%7Bclosure%7D%2Fdata%2Fwww%2Fk3.php0xa67ff01
     
    X-Powered-By: PHP/5.5.23
    Content-type: text/html
     
    Hello World
    Hello PHP
     
    
    让我看一下底层是怎么实现的:Zend/zend_vm_execute.h
    
    其实用的应该是LAMBDA_FUNCTION
    
    
    static int ZEND_FASTCALL  ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
    {
            USE_OPLINE
            zend_function *op_array;
            int closure_is_static, closure_is_being_defined_inside_static_context;
     
            SAVE_OPLINE();
     
            if (UNEXPECTED(zend_hash_quick_find(EG(function_table), Z_STRVAL_P(opline->op1.zv), 
            Z_STRLEN_P(opline->op1.zv), Z_HASH_P(opline->op1.zv), (void *) &op_array) == FAILURE) ||
                UNEXPECTED(op_array->type != ZEND_USER_FUNCTION)) {
                    zend_error_noreturn(E_ERROR, "Base lambda function for closure not found");
            }
     
            closure_is_static = op_array->common.fn_flags & ZEND_ACC_STATIC;
            closure_is_being_defined_inside_static_context = EX(prev_execute_data) &&
            EX(prev_execute_data)->function_state.function->common.fn_flags & ZEND_ACC_STATIC;
            if (closure_is_static || closure_is_being_defined_inside_static_context) {
                    //关键函数在这里
                    zend_create_closure(&EX_T(opline->result.var).tmp_var, (zend_function *) op_array,  EG(called_scope), NULL TSRMLS_CC);
            } else {
                    zend_create_closure(&EX_T(opline->result.var).tmp_var, (zend_function *) op_array,  EG(scope), EG(This) TSRMLS_CC);
            }
     
            CHECK_EXCEPTION();
            ZEND_VM_NEXT_OPCODE();
    }
     我们再看一下zend_create_closure具体是怎么实现的:Zend/zend_closures.c
    
    
    ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_entry *scope, zval 
    *this_ptr TSRMLS_DC) /* {{{ */
    {
        zend_closure *closure;
     
        object_init_ex(res, zend_ce_closure);//初始化
     
        closure = (zend_closure *)zend_object_store_get_object(res TSRMLS_CC);
     
        closure->func = *func;
        closure->func.common.prototype = NULL;
        closure->func.common.fn_flags |= ZEND_ACC_CLOSURE;
     
        if ((scope == NULL) && (this_ptr != NULL)) {
            /* use dummy scope if we're binding an object without specifying a scope */
            /* maybe it would be better to create one for this purpose */
            scope = zend_ce_closure;
        }
     
        if (closure->func.type == ZEND_USER_FUNCTION) {//用户自定函数
            if (closure->func.op_array.static_variables) {
                HashTable *static_variables = closure->func.op_array.static_variables;
                //hash表,申请内存、初始化
                ALLOC_HASHTABLE(closure->func.op_array.static_variables);
                zend_hash_init(closure->func.op_array.static_variables, 
                zend_hash_num_elements(static_variables), NULL, ZVAL_PTR_DTOR, 0);
                //对变量赋值 zval_copy_static_var 这儿是静态变量
                zend_hash_apply_with_arguments(static_variables TSRMLS_CC,
                (apply_func_args_t)zval_copy_static_var, 1, closure->func.op_array.static_variables);
            }
            closure->func.op_array.run_time_cache = NULL;
            (*closure->func.op_array.refcount)++;
        } else {
            //绑定错误
            /* verify that we aren't binding internal function to a wrong scope */
            if(func->common.scope != NULL) {
                if(scope && !instanceof_function(scope, func->common.scope TSRMLS_CC)) {
                    zend_error(E_WARNING, "Cannot bind function %s::%s to scope class %s",
                    func->common.scope->name, func->common.function_name, scope->name);
                    scope = NULL;
                }
                if(scope && this_ptr && (func->common.fn_flags & ZEND_ACC_STATIC) == 0 &&
                        !instanceof_function(Z_OBJCE_P(this_ptr), closure->func.common.scope TSRMLS_CC)) {
                    zend_error(E_WARNING, "Cannot bind function %s::%s to object of class %s",
                    func->common.scope->name, func->common.function_name, Z_OBJCE_P(this_ptr)->name);
                    scope = NULL;
                    this_ptr = NULL;
                }
            } else {
                /* if it's a free function, we won't set scope & this since they're meaningless */
                this_ptr = NULL;
                scope = NULL;
            }
        }
     
        closure->this_ptr = NULL;
        /* Invariants:
         * If the closure is unscoped, it has no bound object.
         * The the closure is scoped, it's either static or it's bound */
        closure->func.common.scope = scope;
        if (scope) {
            closure->func.common.fn_flags |= ZEND_ACC_PUBLIC;
            if (this_ptr && (closure->func.common.fn_flags & ZEND_ACC_STATIC) == 0) {
                closure->this_ptr = this_ptr;
                Z_ADDREF_P(this_ptr);
            } else {
                closure->func.common.fn_flags |= ZEND_ACC_STATIC;
            }
        }
    }
    /* }}} */
    下面我看看变量是如何赋值的:zend/zend_variables.c
    
    
    ZEND_API int zval_copy_static_var(zval **p TSRMLS_DC, int num_args, va_list args, 
    zend_hash_key *key) /* {{{ */
    {
        HashTable *target = va_arg(args, HashTable*);//定一个一个hashtable
        zend_bool is_ref;//是否为引用变量
        zval *tmp;
       
        if (Z_TYPE_PP(p) & (IS_LEXICAL_VAR|IS_LEXICAL_REF)) {//变量作用域 use的时候
            is_ref = Z_TYPE_PP(p) & IS_LEXICAL_REF;
         
            if (!EG(active_symbol_table)) {
                zend_rebuild_symbol_table(TSRMLS_C);
            }
            if (zend_hash_quick_find(EG(active_symbol_table), key->arKey, key->nKeyLength, 
            key->h, (void **) &p) == FAILURE) {
                if (is_ref) {       
                    ALLOC_INIT_ZVAL(tmp);
                    Z_SET_ISREF_P(tmp);
                    zend_hash_quick_add(EG(active_symbol_table), key->arKey, key->nKeyLength, 
                    key->h, &tmp, sizeof(zval*), (void**)&p);
                } else {
                    tmp = EG(uninitialized_zval_ptr);
                    zend_error(E_NOTICE,"Undefined variable: %s", key->arKey);
                }
            } else {
                if (is_ref) {
                    SEPARATE_ZVAL_TO_MAKE_IS_REF(p);
                    tmp = *p;
                } else if (Z_ISREF_PP(p)) {
                    ALLOC_INIT_ZVAL(tmp);
                    ZVAL_COPY_VALUE(tmp, *p);
                    zval_copy_ctor(tmp);
                    Z_SET_REFCOUNT_P(tmp, 0);
                    Z_UNSET_ISREF_P(tmp);
                } else {
                    tmp = *p;
                }
            }
        } else {
            tmp = *p;
        }
        if (zend_hash_quick_add(target, key->arKey, key->nKeyLength, key->h, &tmp, 
        sizeof(zval*), NULL) == SUCCESS) {
            Z_ADDREF_P(tmp);
        }
        return ZEND_HASH_APPLY_KEEP;
    }
    /* }}} */
    QQ:1542385235 (PHP、Java、安卓苹果app制作修改、页面切图、各类模板修改、仿站,数据库修复、WAP制作修改 。我们团队是专门做网站开发的,都是有3年以上工作经验。需要后台系统开发,网页页面制作,app制作,ui设计的请加我qq联系。非诚勿扰!!) 本人qq群也有许多的技术文档,希望可以为你提供一些帮助(非技术的勿加!)。 QQ群: 281442983 (点击链接加入群:http://jq.qq.com/?_wv=1027&k=29LoD19)
  • 相关阅读:
    CentOS升级参考
    CentOS下配置VNC
    CentOS SSH安全和配置无密码登录
    kubernetes使用本地仓库
    IdentityServer4入门五:错误处理
    IdentityServer4入门四:应用Implicit模式保护网站(下)
    IdentityServer4入门四:应用Implicit模式保护网站(上)
    IdentityServer4入门三:授权模式
    浅谈欧几里得算法求最大公约数(GCD)的原理及简单应用
    [CodePlus 2017 11月赛]晨跑 题解(辗转相除法求GCD)
  • 原文地址:https://www.cnblogs.com/piwefei/p/14953056.html
Copyright © 2011-2022 走看看