深入了解PHP闭包的使用以及实现

时间:2021-12-10 17:34:19

一、介绍

匿名函数(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;
}
/* }}} */

 

参考:http://php.net/manual/zh/function.call-user-func-array.php