一、介绍
匿名函数(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