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

时间:2023-03-09 15:37:07
深入了解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;
}
/* }}} */

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