PHP closure function

巴扎黑
Release: 2016-11-11 13:41:15
Original
1523 people have browsed it

Anonymous functions appeared relatively early in programming languages. They first appeared in Lisp language. Later, many programming languages ​​began to have this function.

Javascript and C# are currently widely used. PHP did not really support anonymous functions until 5.3. Functions, C++’s new standard C++0x has also begun to be supported.

Anonymous function is a type of function or subroutine that can be called without specifying an identifier. Anonymous functions can be conveniently passed as parameters to other functions. The most common application is as a callback function.

Closure
When it comes to anonymous functions, we have to mention closures. Closures are short for lexical closures (Lexical Closure). They are functions that reference free variables. The applied free variables will It exists with this function even if it leaves the environment in which it was created, so a closure can also be thought of as an entity composed of a function and its related references. In some languages, when defining another function within a function, if the inner function references a variable of the outer function, a closure may occur. When running an external function, a closure is formed. The word

is easily confused with anonymous functions. In fact, they are two different concepts. This may be because many languages ​​allow closures to be formed when implementing anonymous functions.

Use create_function() to create an "anonymous" function
As mentioned earlier, anonymous functions were officially supported only in PHP5.3. At this point, careful readers may have opinions, because there is a function that can generate anonymous functions: The create_function function can be found in the manual. This function is available in PHP4.1 and PHP5. This function can usually also be used as an anonymous callback function, such as the following:

[php] view plaincopy

$ array = array(1, 2, 3, 4);
array_walk($array, create_function('$value', 'echo $value'));

This code just outputs the values ​​in the array in sequence, of course Can do more. So why is this not a true anonymous function? Let’s first look at the return value of this function. This function returns a string. Usually we can call a function like this:
[php] view plaincopy

function a() {
echo 'function a';
}

$a = 'a';
$a();

We can also use this method when implementing the callback function, for example:

[ php] view plaincopy

function do_something($callback) {
// doing
# ...

// done
$callback();
}

This can be implemented in the function do_something() After execution is completed, the function specified by $callback is called. Returning to the return value of the create_function function: the function returns a unique string function name, or FALSE if an error occurs. So this function only dynamically creates a function, and this function has a function name, which means that it is not actually anonymous. It just creates a globally unique function.

[php] view plaincopy
$func = create_function('', 'echo "Function created dynamic";');
echo $func; // lambda_1

$func(); // Function created dynamic

$my_func = 'lambda_1';
$my_func(); // This function does not exist
lambda_1(); // This function does not exist

The front of the above code is easy to understand, and this is how create_function is used , but the subsequent call through the function name failed, which is a bit difficult to understand. How does PHP ensure that this function is globally unique? lambda_1 seems to be a very common function name. If we first define a function called lambda_1 What? The return string of the function here will be lambda_2. When it creates the function, it will check whether the function exists and find the appropriate function name. But what if we define a function called lambda_1 after create_function? Like this The problem of repeated definition of functions arises. This implementation may not be the best method. In fact, if you really define a function named lambda_1, the problem I mentioned will not occur. What is going on? The last two lines of the above code also illustrate this problem. In fact, there is no function named lambda_1 defined.

That is to say, our lambda_1 and the lambda_1 returned by create_function are not the same!? How could this be the case? That only means that we have not seen the essence, only the surface. The surface is that we output lambda_1 when echoing. And our lambda_1 is typed in by ourselves. Let's use the debug_zval_dump function to take a look.

[php] view plaincopy
$func = create_function('', 'echo "Hello";');

$my_func_name = 'lambda_1';
debug_zval_dump($func); // string(9) " lambda_1" refcount(2)
debug_zval_dump($my_func_name); // string(8) "lambda_1" refcount(2)

See it, their lengths are actually different. Different lengths mean they are not the same one. function, so of course the function we call does not exist, let's just look at what the create_function function does. See the implementation: $PHP_SRC/Zend/zend_builtin_functions.c

[php] view plaincopy
#define LAMBDA_TEMP_FUNCNAME "__lambda_func"

ZEND_FUNCTION(create_function)
{
// ... omit irrelevant code
Function_name = (char * ) emalloc(sizeof("0lambda_")+MAX_LENGTH_OF_LONG);
function_name[0] = 'This is very common in Javascript, but this is not possible in PHP. Copying the properties of an object cannot be called. Such use will cause the class to look for methods defined in the class. In PHP, the property name and the defined method name are It can be repeated. This is determined by PHP's class model. Of course, PHP can be improved in this regard. Subsequent versions may allow such calls, which will make it easier to flexibly implement some functions. At present, there is a way to achieve such an effect: use another magic method __call(). As for how to achieve it, I will leave it as an exercise to the readers.

The use of closures
PHP uses closures (Closure) to implement anonymous functions. The most powerful function of anonymous functions is some of the dynamic features and closure effects provided by anonymous functions. If necessary when defining anonymous functions To use variables outside the scope, you need to use the following syntax:

[php] view plaincopy
$name = 'TIPI Team';
$func = function() use($name) {
echo "Hello , $name";
}

$func(); // Hello TIPI Team

This use statement looks quite awkward, especially compared with Javascript, but this should also be used by PHP-Core after comprehensive consideration. Syntax, because it is different from the scope of Javascript, variables defined in PHP functions are local variables by default, while in Javascript it is the opposite. Except for explicitly defined variables, PHP cannot determine whether the variable is local when mutating. Whether the variable is still a variable in the upper scope, of course, there may be a way to determine it at compile time, but this will have a great impact on the efficiency and complexity of the language.

This syntax is relatively straightforward. If you need to access variables in the upper scope, you need to use the use statement to declare it. This is also simple and easy to read. Speaking of which, you can actually use use to achieve effects similar to the global statement.

The anonymous function can access the variables in the upper scope every time it is executed. These variables always save their own state before the anonymous function is destroyed, such as the following example:

[php] view plaincopy
php function getCounter() {
  $i = 0;
  return function() use($i) { // If you use a reference to pass in the variable here: use(&$i)
          echo ++$i;  
  };
}

$counter = getCounter();
$counter(); // 1
$counter(); // 1

is different from Javascript. The two function calls here do not increment the $i variable. By default, PHP passes upper-level variables into anonymous functions by copying them. If you need to change the value of the upper-level variables, you need to pass them by reference. So the above code does not output 1, 2 but 1, 1.

Implementation of closures
As mentioned earlier, anonymous functions are implemented through closures. Now we start to see how closures (classes) are implemented. There is no difference between anonymous functions and ordinary functions except whether they have variable names. The implementation code of closure is in $PHP_SRC/Zend/zend_closure.c. The problem of "objectification" of anonymous functions has been realized through Closure. How to access the variables when creating the anonymous function?

For example, the following code:

[php] view plaincopy
$i=100;
$counter = function() use($i) {
debug_zval_dump($i);
};

$counter();

Use VLD to see what kind of opcode is compiled by this code

[php] view plaincopy
$ php -dvld.active=1 closure.php

vars: !0 = $i, !1 = $counter
# * op fetch ext return operands
------ -------------------------------------------------- ----------------
0 > ASSIGN                                                              '%00%7Bclosure 
2      ASSIGN                                                   !1, ~1  
3      INIT_FCALL_BY_NAME                                       !1  
4      DO_FCALL_BY_NAME                              0            
5    > RETURN                                                   1  
   
function name:  {closure}  
number of ops:  5  
compiled vars:  !0 = $i  
line     # *  op                           fetch          ext  return  operands  
--------------------------------------------------------------------------------  
  3     0  >   FETCH_R                      static              $0      'i'  
        1      ASSIGN                                                   !0, $0  
  4     2      SEND_VAR                                                 !0  
        3      DO_FCALL                                      1          'debug_zval_dump'  
  5     4    > RETURN                                                   null  

上面根据情况去掉了一些无关的输出, 从上到下, 第1开始将100赋值给!0也就是变量$i, 随后执行ZEND_DECLARE_LAMBDA_FUNCTION,那我们去相关的opcode执行函数中看看这里是怎么执行的, 这个opcode的处理函数位于$PHP_SRC/Zend/zend_vm_execute.h中: 

[php] view plaincopy 
static int ZEND_FASTCALL  ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) 
{  
    zend_op *opline = EX(opline);  
    zend_function *op_array;  
   
    if (zend_hash_quick_find(EG(function_table), Z_STRVAL(opline->op1.u.constant), Z_STRLEN(opline->op1.u.constant), Z_LVAL(opline->op2.u.constant), (void *) &op_arra  
y) == FAILURE ||  
        op_array->type != ZEND_USER_FUNCTION) {  
        zend_error_noreturn(E_ERROR, "Base lambda function for closure not found");  
    }  
   
zend_create_closure(&EX_T(opline->result.u.var).tmp_var, op_array TSRMLS_CC);

ZEND_VM_NEXT_OPCODE();
}


This function calls the zend_create_closure() function to create a closure object, let’s continue to look at it See what the zend_create_closure() function located in $PHP_SRC/Zend/zend_closures.c does.

[php] view plaincopy
ZEND_API void zend_create_closure(zval *res, zend_function *func 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;

if (closure->func.type == ZEND_USER_FUNCTION) { // If it is a user-defined anonymous function
if (closure->func.op_array.static_variables) {
HashTable *static_variables = closure->func.op_array.static_variables; ​​​end_hash_init(closure->func.op_array. Static_variables, ZEND_HASH_NUM_ELEMENTS (Static_variables), NULL, Zval_ptr_DTOR, 0);




Current static variable list, use zval_copy_var method processing

h_Apply_with_arguments (Static_variables TSRMLS_CC, (Apply_func_ARGS_T) Zval_copy_Static_var, Closure-> Func.op_array_variables) ;
                 (*closure->func.op_array.refcount)++; var( )Function implementation:

[php] view plaincopy
static 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*);
 zend_bool is_ref;

// Only perform value operations on static variables of the use statement type, otherwise the static variables in the anonymous function body will also affect variables outside the scope
if (Z_TYPE_PP(p) & (IS_LEXICAL_VAR|IS_LEXICAL_REF) ) {
                                                                                     
                                                                                                                                                                                                                                        // If this variable does not exist in the current scope                                                   , key->arKey, key->nKeyLength, key->h, (void **) &p) == FAILURE) {
                                                                                                                then create A temporary variable operates on the variable after the anonymous function is defined. ALLOC_INIT_ZVAL(tmp); Z_SET_ISREF_P(tmp); table), key->arKey, key->nKeyLength, key->h, &tmp , sizeof(zval*), (void**)&p);
        } else {
// If it is not a reference, it means that the variable does not exist
                                              } else {                                                                                                           Variable, then reference or copy the variable depending on whether it is a reference
                                                                                                                                                                                     SEPARATE_ZVAL(p); if (zend_hash_quick_add(target, key->arKey, key->nKeyLength, key->h, p, sizeof(zval*), NULL) == SUCCESS) {
                                                                                                                                                           

This function is passed to the zend_hash_apply_with_arguments() function as a callback function. Each time the value in the hash table is read, it is processed by this function, and this function assigns the variable values ​​defined by the use statement to the static value of this anonymous function. variable, so that the anonymous function can access the use variable.

Related labels:
php
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template