使用create_function()建立"匿名"函數

#1.使用create_function()建立"匿名"函數php

前面提到PHP5.3中才纔開始正式支持匿名函數,說到這裏可能會有細心讀者有意見了,由於有個函數是能夠生成匿名函數的: create_function函數, 在手冊裏能夠查到這個函數在PHP4.1和PHP5中就有了,這個函數一般也能做爲匿名回調函數使用, 例如以下數組

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

這段代碼只是將數組中的值依次輸出,固然也能作更多的事情。閉包

2.爲何create_function不算真正的匿名函數

那爲何這不算真正的匿名函數呢, 咱們先看看這個函數的返回值,這個函數返回一個字符串, 一般咱們能夠像下面這樣調用一個函數函數

函數的調用ui

<?php
function a() {
    echo 'function a';
}
 
$a = 'a';  //把一個字符串賦值給一個變量
$a(); //用變量來調用函數
?

咱們在實現回調函數的時候也能夠採用這樣的方式,例如debug

回調函數的實現code

<?php
function do_something($callback) {//傳一個變量而後調用變量函數
    // doing
    # ...
 
    // done
    $callback();
}
?>

這樣就能實如今函數do_something()執行完成以後調用$callback指定的函數。字符串

註解:也就是說利用PHP這個變量特性咱們本身也能夠完成匿名函數的調用回調函數


#3. create_function是返回函數名稱的string

回到create_function函數的返回值: 函數返回一個惟一的字符串函數名, 出現錯誤的話則返回FALSE。

這麼說這個函數也只是動態的建立了一個函數,而這個函數是有函數名的,也就是說,其實這並非匿名的。 只是建立了一個全局惟一的函數而已。

<?php
$func = create_function('', 'echo "Function created dynamic";');
echo $func; // lambda_1
 
$func();    // Function created dynamic
 
$my_func = 'lambda_1';
$my_func(); // 不存在這個函數
lambda_1(); // 不存在這個函數
?>

上面這段代碼的前面很好理解,create_function就是這麼用的,後面指定函數名調用卻失敗了,這就有些很差理解了,

php是怎麼保證這個函數是全局惟一的?

lambda_1看起來也是一個很普通的函數明,若是咱們先定義一個叫作lambda_1的函數呢? 這裏函數的返回字符串會是lambda_2,它在建立函數的時候會檢查是否這個函數是否存在直到找到合適的函數名, 但若是咱們在create_function以後定義一個叫作lambda_1的函數會怎麼樣呢?

這樣就出現函數重複定義的問題了, 這樣的實現恐怕不是最好的方法,實際上若是你真的定義了名爲lambda_1的函數也是不會出現我所說的問題的。這到底是怎麼回事呢? 上面代碼的倒數2兩行也說明了這個問題,實際上並無定義名爲lambda_1的函數。

也就是說咱們的lambda_1和create_function返回的lambda_1並非同樣的!? 怎麼會這樣呢? 那隻能說明咱們沒有看到實質, 只看到了表面,表面是咱們在echo的時候輸出了lambda_1,而咱們的lambda_1是咱們本身敲入的. 咱們仍是使用debug_zval_dump函數來看看吧。

<?php
$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)
?>

看出來了吧,他們的長度竟然不同,長度不同,因此咱們調用的函數固然是不存在的, 咱們仍是直接看看create_function函數到底都作了些什麼吧。 該實現見: $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] = '\0';  // <--- 這裏
    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'也就是空字符,而後在函數表中查找是否已經定義了這個函數, 若是已經有了則生成新的函數名, 第一個字符爲空字符的定義方式比較特殊, 這樣在用戶代碼中就沒法定義出這樣的函數了, 這樣也就不存在命名衝突的問題了, 這也算是種取巧的作法了, 在瞭解到這個特殊的函數以後,咱們其實仍是能夠調用到這個函數的, 只要咱們在函數名前加一個空字符就能夠了, chr()函數能夠幫咱們生成這樣的字符串, 例如前面建立的函數能夠經過以下的方式訪問到:

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

這種建立"匿名函數"的方式有一些缺點:

  1. 函數的定義是經過字符串動態eval的, 這就沒法進行基本的語法檢查;

  2. 這類函數和普通函數沒有本質區別, 沒法實現閉包的效果。

相關文章
相關標籤/搜索