一、介绍

匿名函数(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. 一个VLAN配置的实际例子

    背景很简单,和一般的eth-switch通过VLAN做成路由的方式一样.     首先看一种硬件效率较高的方法: Port1~4作为access口,同时在硬件上作为用户模式,即从PC发往这些端口的数据 ...

  2. easyui datatimebox 取值和赋值

    1.取值 var time = $('.easyui-datetimebox').datetimebox('getValue'); 全部代码如下: <script type="text ...

  3. vue设置多入口教程

    官方脚手架搭建vue项目 在src 目录下复制app内容创建test.vue,demo.vue, main.js复制main.js内容demo.js,test.js,修改一下内容 在根目录下创建tes ...

  4. Informatica 常用组件Lookup之二 已连接和未连接的查找

    可以配置一个已连接的查找转换,以从映射管道中直接接收输入:您也可以配置一个未连接的查找转换,以从其它转换的表达式结果中接收输入. 已连接的查找 未连接的查找 直接从管道接收输入值. 从其它转换的 :L ...

  5. Android高效加载大图、多图解决方案,有效避免程序内存溢出现象

    好久没有写博客了,今天就先写一个小的关于在Android中加载大图如何避免内存溢出的问题. 后面会写如何使用缓存技术的核心类,android.support.v4.util.LruCache来加载图片 ...

  6. Open edX 学习、开发、运维相关链接整理

    原文地址:http://edustack.org/ 所需知识: Linux Git Python (Django Mako coffeescript sass) (MongoDB Mysql) Ans ...

  7. Pylons架构网站开发从0到1

    首先说明下这里的从0到1指的是从没有听说过pylons到开发出一个看上去还不错的网站.一个月前,我没有听说过也不知道什么是pylons,HTML只知道一些标签,JavaScript也不怎么懂,由于只倾 ...

  8. HDU 4576 Robot (概率DP)

    暴力DP求解太卡时间了...........写挫一点就跪了 //hdu robot #include <cstdio> #include <iostream> #include ...

  9. 为Ubuntu 安装Transmission 2.90

    Transmission 是 Ubuntu 的默认 BitTorrent 客户端,近期发布了最新的 Transmission 2.90 版本,目前已经可通过 PPA 为 Ubuntu 15.10.Ub ...

  10. 直接修改class文件内容即使是文本会导致App异常,正确方式是修改java再用生成的class替换掉原有的class

    前几天来了个小任务,把某项目中某人的邮件地址改了下. 由于对项目不熟悉,于是采用find方式找出app中所有包含某人邮件地址的文件都找出来了. xml,properties大约三四个,还有两个clas ...