• 深入了解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
    ", $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

  • 相关阅读:
    【图文并茂,点赞收藏哦!】重学巩固你的Vuejs知识体系
    RabbitMQ概念及控制台介绍
    RabbitMQ入门介绍及环境搭建
    C# 利用PdfSharp生成Pdf文件
    利用pdf.js在线展示PDF文档
    Python办公自动化之Excel转Word
    C#利用ServiceStack.Redis访问Redis
    Redis基础之事务
    Redis基础之配置文件
    第0天 | 12天搞定Pyhon,前言
  • 原文地址:https://www.cnblogs.com/chenpingzhao/p/4553139.html
Copyright © 2020-2023  润新知