深入了解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\r\n", $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;
}
/* }}} */

 

posted @ 2021-06-30 09:44  piwenfei  阅读(1012)  评论(0编辑  收藏  举报