Home  >  Article  >  Backend Development  >  PHP anonymous functions and closures

PHP anonymous functions and closures

WBOY
WBOYOriginal
2016-08-08 09:26:101181browse

[iefreer] reprinted an article that explains the PHP closure syntax in depth, and will also repost an article later on how to apply these new syntaxes skillfully.

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, and PHP did not start to be implemented until 5.3. Supports anonymous functions, and the new C++ standard C++0x has also begun to support it.

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. This free variable is applied Will exist 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 associated references. In some languages, when defining another function within a function, a closure may occur if the inner function references a variable of the outer function. 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 only officially supported in PHP5.3. At this point, careful readers may have opinions, because there is a function that can generate anonymous functions. : create_function function, you can find this function in PHP4.1 and PHP5 in the manual. This function can usually also be used as an anonymous callback function, for example, as follows:

[php] view plaincopy

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


This code just outputs the values ​​in the array in sequence. Of course more can be done. 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

  1. function a() { echo
  2. 'function a'; }
  3. $a
  4. =
  5. 'a'; $a
  6. () ;
  7. We are implementing the callback function You can also use this method, for example:


[php] view plaincopy

  1. function
  2. do_something(
  3. $callback) { ​​​//doing
  4. ​​
  5.         # …                                                       
  6. }
  7. This can be implemented in the function After do_something() is executed, 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.
  8. [php] view plaincopy
    1. $func = create_function('', 'echo "Function created dynamic";');
    2. echo $func; // lambda_1
    3. $func(); //Function created dynamic
    4. $my_func = 'lambda_1';
    5. $my_func(); //This function does not exist
    6. lambda_1(); // This function does not exist


    The front of the above code is easy to understand. This is how create_function is used. However, later calling it through the function name fails. This is a bit difficult to understand. How does PHP work? Ensure that this function is globally unique? lambda_1 seems to be a very common function name. What if we first define a function called lambda_1? The return string of the function here will be lambda_2, which will check whether this is the case when creating the function. Whether the function exists or not, we need to find the appropriate function name, but what if we define a function called lambda_1 after create_function? This will cause the problem of repeated definition of functions. This implementation may not be the best method. In fact, if 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

    1. $func = create_function('', 'echo "Hello";' );
    2. $my_func_name = 'lambda_1'; // string(9) "lambda_1" refcount(2)
    3. debug_zval_dump($my_func_name); // string(8) "lambda_1" refcount(2)
    4. See it, theirs The length is actually different, the length Different means that it is not the same 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
    5. [php] view plaincopy

    1. #define LAMBDA_TEMP_FUNCNAME    "__lambda_func"  
    2.    
    3. ZEND_FUNCTION(create_function)  
    4. {  
    5.     // ... 省去无关代码  
    6.     function_name = (char *) emalloc(sizeof("0lambda_")+MAX_LENGTH_OF_LONG);  
    7.     function_name[0] = '';  // <--- 这里  
    8.     do {  
    9.         function_name_length = 1 + sprintf(function_name + 1, "lambda_%d", ++EG(lambda_count));  
    10.     } while (zend_hash_add(EG(function_table), function_name, function_name_length+1, &new_function, sizeof(zend_function), NULL)==FAILURE);  
    11.     zend_hash_del(EG(function_table), LAMBDA_TEMP_FUNCNAME, sizeof(LAMBDA_TEMP_FUNCNAME));  
    12.     RETURN_STRINGL(function_name, function_name_length, 0);  
    13. }  


    该函数在定义了一个函数之后,给函数起了个名字,它将函数名的第一个字符变为了''也就是空字符,然后在函数表中查找是否已经定义了这个函数,如果已经有了则生成新的函数名, 第一个字符为空字符的定义方式比较特殊, 因为在用户代码中无法定义出这样的函数, 也就不存在命名冲突的问题了,这也算是种取巧(tricky)的做法,在了解到这个特殊的函数之后,我们其实还是可以调用到这个函数的, 只要我们在函数名前加一个空字符就可以了, chr()函数可以帮我们生成这样的字符串, 例如前面创建的函数可以通过如下的方式访问到:

    [php] view plaincopy

    1.    
    2. $my_func = chr(0) . "lambda_1";  
    3. $my_func(); // Hello  


    这种创建"匿名函数"的方式有一些缺点:

    1. 函数的定义是通过字符串动态eval的, 这就无法进行基本的语法检查;
    2. 这类函数和普通函数没有本质区别, 无法实现闭包的效果.

    真正的匿名函数

    在PHP5.3引入的众多功能中, 除了匿名函数还有一个特性值得讲讲: 新引入的__invoke 魔幻方法。

    __invoke魔幻方法

    这个魔幻方法被调用的时机是: 当一个对象当做函数调用的时候, 如果对象定义了__invoke魔幻方法则这个函数会被调用,这和C++中的操作符重载有些类似, 例如可以像下面这样使用:

    [php] view plaincopy

    1. class Callme {  
    2.     public function __invoke($phone_num) {  
    3.         echo "Hello: $phone_num";  
    4.     }  
    5. }  
    6.    
    7. $call = new Callme();  
    8. $call(13810688888); // "Hello: 13810688888  


    匿名函数的实现

    前面介绍了将对象作为函数调用的方法, 聪明的你可能想到在PHP实现匿名函数的方法了,PHP中的匿名函数就的确是通过这种方式实现的。我们先来验证一下:

    [php] view plaincopy

    1. $func = function() {
    2. "Hello, anonymous function";
    3. }
    4. echo gettype($func); // object
    5. echoget_class($ func); //Closure

    It turns out that the anonymous function is just an ordinary class. Students who are familiar with Javascript are very familiar with the use of anonymous functions. PHP also uses a syntax similar to Javascript to define. Anonymous functions can be assigned to a variable. Because the anonymous function is actually a class instance, it is easy to understand that it can be copied. , In Javascript, you can assign an anonymous function to the property of an object, for example:

    [php] view plaincopy

    1. var a = {};
    2. a.call =
    3. function() {alert("called");}
    4. a .call();
    5. // alert called

    This is very common in Javascript, but this is not possible in PHP. Copying the properties of an object cannot be called, so using it will Causes the class to search for methods defined in the class. In PHP, attribute names and defined method names can be repeated. This is determined by PHP's class model. Of course, PHP can be improved in this regard, and may be improved in subsequent versions. Allowing such calls makes 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. When defining anonymous functions, if If you need to use variables outside the scope, you need to use the following syntax:

    [php] view plaincopy

    1. $name = 'TIPI Team';
    2. $func = function() use($name) {
    3. } $ func
    4. (); // Hello TIPI Team
    5. 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 explicit definitions, PHP cannot determine whether the variable is a local variable when mutating. Of course, there may be a way to determine whether local variables are still variables in the upper scope 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:
    6. [php] view plaincopy

    1. function getCounter() {
    2. $i = 0;
    3. return function() use($i) { // If you use a reference to pass in variables: use(&$i)
    4. ++ $i ; $counter
    5. (); // 1
    6. $counter
    7. (); // 1
    8. is different from Javascript. The two function calls here do not increment the $i variable. The default PHP The upper-layer variable is passed into the anonymous function by copying. If the value of the upper-layer variable needs to be changed, it needs to be passed by reference. So the above code does not output 1, 2 but 1, 1. Implementation of closure
    9. I mentioned earlier that anonymous functions are implemented through closures. Now let’s 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?
    10. For example, the following code: [php] view plaincopy

    11. $i=100;

      $counter

      =

      function

      ()
      1. use( $i
      2. ) { debug_zval_dump($i
      3. ); }; $counter();
      4. Use VLD to check what kind of opcode is compiled by this encoding[php] view plaincopy
        1. $ php -dvld.active=1 closure.php  
        2.    
        3. vars:  !0 = $i, !1 = $counter  
        4. # *  op                           fetch          ext  return  operands  
        5. ------------------------------------------------------------------------  
        6. 0  >   ASSIGN                                                   !0, 100  
        7. 1      ZEND_DECLARE_LAMBDA_FUNCTION                             '%00%7Bclosure  
        8. 2      ASSIGN                                                   !1, ~1  
        9. 3      INIT_FCALL_BY_NAME                                       !1  
        10. 4      DO_FCALL_BY_NAME                              0            
        11. 5    > RETURN                                                   1  
        12.    
        13. function name:  {closure}  
        14. number of ops:  5  
        15. compiled vars:  !0 = $i  
        16. line     # *  op                           fetch          ext  return  operands  
        17. --------------------------------------------------------------------------------  
        18.   3     0  >   FETCH_R                      static              $0      'i'  
        19.         1      ASSIGN                                                   !0, $0  
        20.   4     2      SEND_VAR                                                 !0  
        21.         3      DO_FCALL                                      1          'debug_zval_dump'  
        22.   5     4    > RETURN                                                   null  

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

        [php] view plaincopy

        1. static int ZEND_FASTCALL  ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)  
        2. {  
        3.     zend_op *opline = EX(opline);  
        4.     zend_function *op_array;  
        5.    
        6.     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  
        7. y) == FAILURE ||  
        8.         op_array->type != ZEND_USER_FUNCTION) {  
        9.         zend_error_noreturn(E_ERROR, "Base lambda function for closure not found");  
        10.     }  
        11.    
        12.     zend_create_closure(&EX_T(opline->result.u.var).tmp_var, op_array TSRMLS_CC);  
        13.    
        14.     ZEND_VM_NEXT_OPCODE();  
        15. }  

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

        [php] view plaincopy

        1. ZEND_API void zend_create_closure(zval *res, zend_function *func TSRMLS_DC)
        2. {
        3. zend_closure *closure;
        4. object_init_ex(res, zend_ce_closure);
        5. closure = (zend_closure *)zend_object_store_get_object(res TSRMLS_CC);
        6. closure->func = *func;
        7. if
        8. ( closure->func.type == ZEND_USER_FUNCTION) { // If it is a user-defined anonymous function (closure->func.op_array.static_variables) {
        9.                           HashTable *static_variables                                         closure->func.op_array.static_variables                                                                                                     
        10.                             ALLOC_HASHTABLE(closure-> func.op_array.static_variables);                                                                                                                                                                            
        11. // Loop current static variables List, use zval_copy_static_var method to process
        12.                                                                                                                                                                      to array.static_variables);
        13. }
        14. (*closure-> ;func.op_array.refcount)++;
        15. }
        16. closure->func.common.scope = NULL;
        17. }
        18. Like the above code comment As mentioned in, continue to look at the implementation of the zval_copy_static_var() function:
        19. [php] view plaincopy
          1. static int zval_copy_static_var(zval **p TSRMLS_DC, int num_args, va_list args, zend_hash_key *key)
          2. {
          3. HashTable *target = va_arg(args, HashTable*);
          4.  zend_bool is_ref;
          5. //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
          6. if (Z_TYPE_PP(p) & (IS_LEXICAL_VAR|IS_LEXICAL_REF)) {
          7. is_ref = Z_TYPE_PP(p) & IS_LEX ICAL_REF;
          8. if (!EG (active_symbol_table)) {
          9.               zend_rebuild_symbol_table(TSRMLS_C);                                                           If there is no such variable in the current scope
          10. if (zend_hash_quick_find(EG( active_symbol_table), key->arKey, key->nKeyLength, key->h, (void **) &p) == FAILURE) {
          11. (is_ref) {                                                                                                                                                                                                                                                                                                    ​ 
          12.                              ALLOC_INIT_ZVAL(tmp);                                                                                                                                                         >h, &tmp, sizeof(zval*), (void**) &p);
          13. } else
          14. {
          15.       p = &EG(uninitialized_zval_ptr); E_NOTICE,"Undefined variable: %s"
          16. , key->arKey);
          17. }
          18. ​​​​​​​//If this variable exists, then Depending on whether it is a reference, reference or copy the variable
          19.             if (is_ref) {                  _MAKE_IS_REF(p);
          20. }else if (Z_ISREF_PP(p)) {
          21. ​​​​​​​​SEPARATE_ZVAL(p);​​​ }
          22. }
          23. }
          24. if
          25. ( zend_hash_quick_add(target, key->arKey, key->nKeyLength, key->h, p, sizeof(zval*), NULL) == SUCCESS) {                                                                              
          26. }
          27. returnZEND_HASH_APPLY_KEEP;
          28. }
          29. This function is passed to zen as a callback function d_hash_apply_with_arguments()
          30. function, every time the value in the hash table is read It is processed by this function, and this function assigns the variable values ​​​​defined by all use statements to the static variables of this anonymous function, so that the anonymous function can access the use variables.
          Original link:
          http://www.php-internals.com/book/?p=chapt04/04-04-anonymous-function

          Reference reading:http://php.net/manual/zh/functions.anonymous.php The above introduces PHP anonymous functions and closures, including aspects of the content. I hope it will be helpful to friends who are interested in PHP tutorials.

Statement:
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