一、介绍

匿名函数(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

深入了解PHP闭包的使用以及实现的更多相关文章

  1. 《Web 前端面试指南》1、JavaScript 闭包深入浅出

    闭包是什么? 闭包是内部函数可以访问外部函数的变量.它可以访问三个作用域:首先可以访问自己的作用域(也就是定义在大括号内的变量),它也能访问外部函数的变量,和它能访问全局变量. 内部函数不仅可以访问外 ...

  2. 干货分享:让你分分钟学会 JS 闭包

    闭包,是 Javascript 比较重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,很难从定义去理解它.因此,本文不会对闭包的概念进行大篇幅描述 ...

  3. 深入浅出JavaScript之闭包(Closure)

    闭包(closure)是掌握Javascript从人门到深入一个非常重要的门槛,它是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现.下面写下我的学习笔记~ 闭包-无处不 ...

  4. javascript之闭包理解以及应用场景

    半个月没写博文了,最近一直在弄小程序,感觉也没啥好写的. 之前读了js权威指南,也写了篇博文,但是实话实说当初看闭包确实还是一头雾水.现在时隔一个多月(当然这一段时间还是一直有在看闭包的相关知识)理解 ...

  5. js闭包 和 prototype

    function test(){ var p=200; function q(){ return p++; } return q; } var s = test(); alert(s()); aler ...

  6. js闭包for循环总是只执行最后一个值得解决方法

    <style> li{ list-style: none;width:40px;height: 40px;text-align:center;line-height: 40px;curso ...

  7. JavaScript学习笔记(二)——闭包、IIFE、apply、函数与对象

    一.闭包(Closure) 1.1.闭包相关的问题 请在页面中放10个div,每个div中放入字母a-j,当点击每一个div时显示索引号,如第1个div显示0,第10个显示9:方法:找到所有的div, ...

  8. 带你一分钟理解闭包--js面向对象编程

    上一篇<简单粗暴地理解js原型链--js面向对象编程>没想到能攒到这么多赞,实属意外.分享是个好事情,尤其是分享自己的学习感悟.所以网上关于原型链.闭包.作用域等文章多如牛毛,很多文章写得 ...

  9. 如何设计一门语言(七)——闭包、lambda和interface

    人们都很喜欢讨论闭包这个概念.其实这个概念对于写代码来讲一点用都没有,写代码只需要掌握好lambda表达式和class+interface的语义就行了.基本上只有在写编译器和虚拟机的时候才需要管什么是 ...

  10. JavaScript 闭包深入浅出

    闭包是什么? 闭包是内部函数可以访问外部函数的变量.它可以访问三个作用域:首先可以访问自己的作用域(也就是定义在大括号内的变量),它也能访问外部函数的变量,和它能访问全局变量. 内部函数不仅可以访问外 ...

随机推荐

  1. tomcat启动时间修改

    Server Tomcat v6.0 Server at localhost was unable to start within 45 seconds. If the server requires ...

  2. 用于Web开发的8 个最好的跨平台编辑器

    1) Best Cross Platform IDE - Brackets Brackets是一个在前端Web开发和设计人员中最流行的开放源码IDE/代码编辑器之一.它拥有一些实用工具能够将HTML ...

  3. scala 学习笔记七 基于类型的模式匹配

    1.介绍 Scala 提供了强大的模式匹配机制,应用也非常广泛. 一个模式匹配包含了一系列备选项,每个都开始于关键字 case.每个备选项都包含了一个模式及一到多个表达式.箭头符号 => 隔开了 ...

  4. scala里的模式匹配和Case Class

    模式匹配的简介 scala语言里的模式匹配可以看作是java语言中switch语句的改进. 模式匹配的类型 包括:常量模式.变量模式.构造器模式.序列模式.元组模式以及变量绑定模式等. 常量模式匹配 ...

  5. Linq-分页查询

    var list = from s in db.t_address select s; & userid != null) { list = list.Where(v => v.user ...

  6. 我对REST的理解

    1:rest的由来 REST即表述性状态传递(英文:Representational State Transfer,简称REST) 通俗点说:资源在网络中以某种表现形式进行状态转移. 源于REST之父 ...

  7. 【android】模拟点击某个指定坐标作用在View上

    /** * 模拟点击某个指定坐标作用在View上 * @param view * @param x * @param y */ public void clickView(View view,floa ...

  8. Session 共享(Custom模式)By Memcached(原创)

    1.web.config配置: <machineKey decryptionKey="FD69B2EB9A11E3063518F1932E314E4AA1577BF0B824F369& ...

  9. web.xml不同版本的头

    web.xml v2.3 <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web- ...

  10. ios backgroundColor

    loginView.backgroundColor=[UIColorcolorWithHue:0saturation:0brightness:0.9alpha:0.85]; 可随意调