search
HomeBackend DevelopmentPHP TutorialDetailed explanation of anonymous functions and closures in php

Anonymous functions appeared relatively early in Programming languages. They first appeared in Lisp language, and then many programming languages ​​began to have them. This function is available.

Javascript and C# are currently widely used. PHP did not really support anonymous functions until 5.3, and the new C++ standard C++0x also began 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

Speaking of anonymous functions, we have to mention closure. Closure is the abbreviation of Lexical Closure, which refers to free variables. Function, the applied free variable will exist with this function even if it leaves the environment in which it was created, so a closure can also be considered as an entity composed of a function and its related references. In some languages, when defining another function within a function, if the internal function references the variables of the external 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 "anonymous" functions

As mentioned earlier, anonymous functions were only officially supported in PHP5.3. At this point, careful readers may have opinions, because there are A function can generate an anonymous function: 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
 
$array = array(1, 2, 3, 4);
array_walk($array, create_function(&#39;$value&#39;, &#39;echo $value&#39;));

This code just outputs the values ​​​​in the array one by one. Of course, it can also do more things. 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 the following:

<?php
 
function a() {
    echo &#39;function a&#39;;
}
 
$a = &#39;a&#39;;
$a();

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

<?php
 
function do_something($callback) {
    // doing
    # ...
 
    // done
    $callback();
}

In this way, the function specified by $callback can be called after the function do_something() is executed. 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
$func = create_function(&#39;&#39;, &#39;echo "Function created dynamic";&#39;);
echo $func; // lambda_1
 
$func();    // Function created dynamic
 
$my_func = &#39;lambda_1&#39;;
$my_func(); // 不存在这个函数
lambda_1(); // 不存在这个函数

The first part 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 ensure that this function is globally unique? ? lambda_1 also 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. When creating the function, it will check whether the function exists until it finds the appropriate one. 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 is probably not the best method. In fact, if you really define the name The function that is lambda_1 will not have the problem I mentioned. 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, and the surface is when we echo Lambda_1 is output, and our lambda_1 is typed in by ourselves. Let's use the debug_zval_dump function to take a look.

<?php
$func = create_function(&#39;&#39;, &#39;echo "Hello";&#39;);
 
$my_func_name = &#39;lambda_1&#39;;
debug_zval_dump($func);         // string(9) "lambda_1" refcount(2)
debug_zval_dump($my_func_name); // string(8) "lambda_1" refcount(2)

You can see it, their lengths are actually different. Different lengths mean that they are not the same function, so of course the function we call does not exist. Let’s just look at the create_function function. What have you done? See this implementation: $PHP_SRC/Zend/zend_builtin_functions.c

#define LAMBDA_TEMP_FUNCNAME    "lambda_func"
 
ZEND_FUNCTION(create_function)
{
    // ... 省去无关代码
    function_name = (char *) emalloc(sizeof("0lambda_")+MAX_LENGTH_OF_LONG);
    function_name[0] = &#39;\0&#39;;  // <--- 这里
    do {
        function_name_length = 1 + sprintf(function_name + 1, "lambda_%d", ++EG(lambda_count));
    } while (zend_hash_add(EG(function_table), function_name, function_name_length+1, &new_function, sizeof(zend_function), NULL)==FAILURE);
    zend_hash_del(EG(function_table), LAMBDA_TEMP_FUNCNAME, sizeof(LAMBDA_TEMP_FUNCNAME));
    RETURN_STRINGL(function_name, function_name_length, 0);
}

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

<?php
 
$my_func = chr(0) . "lambda_1";
$my_func(); // Hello

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

  1. 函数的定义是通过字符串动态eval的, 这就无法进行基本的语法检查;

  2. 这类函数和普通函数没有本质区别, 无法实现闭包的效果.

真正的匿名函数

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

invoke魔幻方法

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

<?php
class Callme {
    public function invoke($phone_num) {
        echo "Hello: $phone_num";
    }
}
 
$call = new Callme();
$call(13810688888); // "Hello: 13810688888

匿名函数的实现

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

<?php
$func = function() {
    echo "Hello, anonymous function";
}
 
echo gettype($func);    // object
echo get_class($func);  // Closure

原来匿名函数也只是一个普通的类而已。熟悉Javascript的同学对匿名函数的使用方法很熟悉了,PHP也使用和Javascript类似的语法来定义, 匿名函数可以赋值给一个变量, 因为匿名函数其实是一个类实例, 所以能复制也是很容易理解的, 在Javascript中可以将一个匿名函数赋值给一个对象的属性, 例如:

var a = {};
a.call = function() {alert("called");}
a.call(); // alert called

这在Javascript中很常见, 但在PHP中这样并不可以, 给对象的属性复制是不能被调用的, 这样使用将会导致类寻找类中定义的方法,在PHP中属性名和定义的方法名是可以重复的, 这是由PHP的类模型所决定的, 当然PHP在这方面是可以改进的, 后续的版本中可能会允许这样的调用,这样的话就更容易灵活的实现一些功能了。目前想要实现这样的效果也是有方法的: 使用另外一个魔幻方法call(),至于怎么实现就留给各位读者当做习题吧。

闭包的使用

PHP使用闭包(Closure)来实现匿名函数, 匿名函数最强大的功能也就在匿名函数所提供的一些动态特性以及闭包效果,匿名函数在定义的时候如果需要使用作用域外的变量需要使用如下的语法来实现:

<?php
$name = &#39;TIPI Team&#39;;
$func = function() use($name) {
    echo "Hello, $name";
}
 
$func(); // Hello TIPI Team

这个use语句看起来挺别扭的, 尤其是和Javascript比起来, 不过这也应该是PHP-Core综合考虑才使用的语法, 因为和Javascript的作用域不同, PHP在函数内定义的变量默认就是局部变量, 而在Javascript中则相反,除了显式定义的才是局部变量, PHP在变异的时候则无法确定变量是局部变量还是上层作用域内的变量, 当然也可能有办法在编译时确定,不过这样对于语言的效率和复杂性就有很大的影响。

这个语法比较直接,如果需要访问上层作用域内的变量则需要使用use语句来申明, 这样也简单易读,说到这里, 其实可以使用use来实现类似global语句的效果。

匿名函数在每次执行的时候都能访问到上层作用域内的变量, 这些变量在匿名函数被销毁之前始终保存着自己的状态,例如如下的例子:

<?php
function getCounter() {
    $i = 0;
    return function() use($i) { // 这里如果使用引用传入变量: use(&$i)
        echo ++$i;
    };
}
 
$counter = getCounter();
$counter(); // 1
$counter(); // 1

和Javascript中不同,这里两次函数调用并没有使$i变量自增,默认PHP是通过拷贝的方式传入上层变量进入匿名函数,如果需要改变上层变量的值则需要通过引用的方式传递。所以上面得代码没有输出1, 2而是1,1

闭包的实现

前面提到匿名函数是通过闭包来实现的, 现在我们开始看看闭包(类)是怎么实现的。匿名函数和普通函数除了是否有变量名以外并没有区别,闭包的实现代码在$PHP_SRC/Zend/zend_closure.c。匿名函数"对象化"的问题已经通过Closure实现, 而对于匿名是怎么样访问到创建该匿名函数时的变量的呢?

例如如下这段代码:

<?php
$i=100;
$counter = function() use($i) {
    debug_zval_dump($i);
};  
 
$counter();

通过VLD来查看这段编码编译什么样的opcode了

$ php -dvld.active=1 closure.php
 
vars:  !0 = $i, !1 = $counter
# *  op                           fetch          ext  return  operands
------------------------------------------------------------------------
0  >   ASSIGN                                                   !0, 100
1      ZEND_DECLARE_LAMBDA_FUNCTION                             &#39;%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      &#39;i&#39;
        1      ASSIGN                                                   !0, $0
  4     2      SEND_VAR                                                 !0
        3      DO_FCALL                                      1          &#39;debug_zval_dump&#39;
  5     4    > RETURN                                                   null

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

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();
}

该函数调用了zend_create_closure()函数来创建一个闭包对象, 那我们继续看看位于$PHP_SRC/Zend/zend_closures.c的zend_create_closure()函数都做了些什么。

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 (closure->func.op_array.static_variables) {
            HashTable *static_variables = closure->func.op_array.static_variables;
 
            // 为函数申请存储静态变量的哈希表空间
            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.refcount)++;
    }
 
    closure->func.common.scope = NULL;
}

如上段代码注释中所说, 继续看看zval_copy_static_var()函数的实现:

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;
 
    // 只对通过use语句类型的静态变量进行取值操作, 否则匿名函数体内的静态变量也会影响到作用域之外的变量
    if (Z_TYPE_PP(p) & (IS_LEXICAL_VAR|IS_LEXICAL_REF)) {
        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) {
                zval *tmp;
 
                // 如果是引用变量, 则创建一个临时变量一边在匿名函数定义之后对该变量进行操作
                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 {
                // 如果不是引用则表示这个变量不存在
                p = &EG(uninitialized_zval_ptr);
                zend_error(E_NOTICE,"Undefined variable: %s", key->arKey);
            }
        } else {
            // 如果存在这个变量, 则根据是否是引用, 对变量进行引用或者复制
            if (is_ref) {
                SEPARATE_ZVAL_TO_MAKE_IS_REF(p);
            } else if (Z_ISREF_PP(p)) {
                SEPARATE_ZVAL(p);
            }
        }
    }
    if (zend_hash_quick_add(target, key->arKey, key->nKeyLength, key->h, p, sizeof(zval*), NULL) == SUCCESS) {
        Z_ADDREF_PP(p);
    }
    return ZEND_HASH_APPLY_KEEP;
}

这个函数作为一个回调函数传递给zend_hash_apply_with_arguments()函数, 每次读取到hash表中的值之后由这个函数进行处理,而这个函数对所有use语句定义的变量值赋值给这个匿名函数的静态变量, 这样匿名函数就能访问到use的变量了。

The above is the detailed content of Detailed explanation of anonymous functions and closures in php. For more information, please follow other related articles on the PHP Chinese website!

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
PHP's Purpose: Building Dynamic WebsitesPHP's Purpose: Building Dynamic WebsitesApr 15, 2025 am 12:18 AM

PHP is used to build dynamic websites, and its core functions include: 1. Generate dynamic content and generate web pages in real time by connecting with the database; 2. Process user interaction and form submissions, verify inputs and respond to operations; 3. Manage sessions and user authentication to provide a personalized experience; 4. Optimize performance and follow best practices to improve website efficiency and security.

PHP: Handling Databases and Server-Side LogicPHP: Handling Databases and Server-Side LogicApr 15, 2025 am 12:15 AM

PHP uses MySQLi and PDO extensions to interact in database operations and server-side logic processing, and processes server-side logic through functions such as session management. 1) Use MySQLi or PDO to connect to the database and execute SQL queries. 2) Handle HTTP requests and user status through session management and other functions. 3) Use transactions to ensure the atomicity of database operations. 4) Prevent SQL injection, use exception handling and closing connections for debugging. 5) Optimize performance through indexing and cache, write highly readable code and perform error handling.

How do you prevent SQL Injection in PHP? (Prepared statements, PDO)How do you prevent SQL Injection in PHP? (Prepared statements, PDO)Apr 15, 2025 am 12:15 AM

Using preprocessing statements and PDO in PHP can effectively prevent SQL injection attacks. 1) Use PDO to connect to the database and set the error mode. 2) Create preprocessing statements through the prepare method and pass data using placeholders and execute methods. 3) Process query results and ensure the security and performance of the code.

PHP and Python: Code Examples and ComparisonPHP and Python: Code Examples and ComparisonApr 15, 2025 am 12:07 AM

PHP and Python have their own advantages and disadvantages, and the choice depends on project needs and personal preferences. 1.PHP is suitable for rapid development and maintenance of large-scale web applications. 2. Python dominates the field of data science and machine learning.

PHP in Action: Real-World Examples and ApplicationsPHP in Action: Real-World Examples and ApplicationsApr 14, 2025 am 12:19 AM

PHP is widely used in e-commerce, content management systems and API development. 1) E-commerce: used for shopping cart function and payment processing. 2) Content management system: used for dynamic content generation and user management. 3) API development: used for RESTful API development and API security. Through performance optimization and best practices, the efficiency and maintainability of PHP applications are improved.

PHP: Creating Interactive Web Content with EasePHP: Creating Interactive Web Content with EaseApr 14, 2025 am 12:15 AM

PHP makes it easy to create interactive web content. 1) Dynamically generate content by embedding HTML and display it in real time based on user input or database data. 2) Process form submission and generate dynamic output to ensure that htmlspecialchars is used to prevent XSS. 3) Use MySQL to create a user registration system, and use password_hash and preprocessing statements to enhance security. Mastering these techniques will improve the efficiency of web development.

PHP and Python: Comparing Two Popular Programming LanguagesPHP and Python: Comparing Two Popular Programming LanguagesApr 14, 2025 am 12:13 AM

PHP and Python each have their own advantages, and choose according to project requirements. 1.PHP is suitable for web development, especially for rapid development and maintenance of websites. 2. Python is suitable for data science, machine learning and artificial intelligence, with concise syntax and suitable for beginners.

The Enduring Relevance of PHP: Is It Still Alive?The Enduring Relevance of PHP: Is It Still Alive?Apr 14, 2025 am 12:12 AM

PHP is still dynamic and still occupies an important position in the field of modern programming. 1) PHP's simplicity and powerful community support make it widely used in web development; 2) Its flexibility and stability make it outstanding in handling web forms, database operations and file processing; 3) PHP is constantly evolving and optimizing, suitable for beginners and experienced developers.

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

AI Hentai Generator

AI Hentai Generator

Generate AI Hentai for free.

Hot Article

R.E.P.O. Energy Crystals Explained and What They Do (Yellow Crystal)
4 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Best Graphic Settings
4 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. How to Fix Audio if You Can't Hear Anyone
4 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
WWE 2K25: How To Unlock Everything In MyRise
1 months agoBy尊渡假赌尊渡假赌尊渡假赌

Hot Tools

Dreamweaver Mac version

Dreamweaver Mac version

Visual web development tools

SublimeText3 English version

SublimeText3 English version

Recommended: Win version, supports code prompts!

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

Atom editor mac version download

Atom editor mac version download

The most popular open source editor

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

Integrate Eclipse with SAP NetWeaver application server.