雖然 11 年前 Dan D 的說法是正確的,但現在已不再那麼正確了。匿名函式現在是 Closure 類別的物件,並且可以安全地被垃圾回收器回收。
(PHP 4 >= 4.0.1, PHP 5, PHP 7)
create_function — 透過評估程式碼字串來動態建立函式
此函式已於 PHP 7.2.0 版本棄用,並於 PHP 8.0.0 版本移除。強烈不建議依賴此函式。
從傳入的參數動態建立函式,並傳回該函式的唯一名稱。
通常建議將這些參數作為 單引號 字串傳遞。如果使用 雙引號 字串,則程式碼中的變數名稱需要仔細跳脫,例如 \$somevar
。
args
函式的參數,以單一逗號分隔的字串表示。
code
函式的程式碼。
傳回一個唯一的函式名稱作為字串,若失敗則傳回 false
。請注意,該名稱包含一個不可列印的字元 ("\0"
),因此在列印名稱或將其併入任何其他字串時應小心。
範例 #1 使用 create_function() 或匿名函式動態建立函式
您可以使用動態建立的函式,例如,從執行時收集的資訊建立函式。首先,使用 create_function()
<?php
$newfunc = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
echo $newfunc(2, M_E) . "\n";
?>
現在是相同的程式碼,使用 匿名函式;請注意,程式碼和參數不再包含在字串中
<?php
$newfunc = function($a,$b) { return "ln($a) + ln($b) = " . log($a * $b); };
echo $newfunc(2, M_E) . "\n";
?>
以上範例將輸出
ln(2) + ln(2.718281828459) = 1.6931471805599
範例 #2 使用 create_function() 或匿名函式建立通用的處理函式
另一種用法是建立通用的處理函式,可以對一組參數套用一組運算
<?php
function process($var1, $var2, $farr)
{
foreach ($farr as $f) {
echo $f($var1, $var2) . "\n";
}
}
// 建立一堆數學函式
$farr = array(
create_function('$x,$y', 'return "some trig: ".(sin($x) + $x*cos($y));'),
create_function('$x,$y', 'return "a hypotenuse: ".sqrt($x*$x + $y*$y);'),
create_function('$a,$b', 'if ($a >=0) {return "b*a^2 = ".$b*sqrt($a);} else {return false;}'),
create_function('$a,$b', "return \"min(b^2+a, a^2,b) = \".min(\$a*\$a+\$b,\$b*\$b+\$a);"),
create_function('$a,$b', 'if ($a > 0 && $b != 0) {return "ln(a)/b = ".log($a)/$b; } else { return false; }')
);
echo "\n使用第一個動態函式陣列\n";
echo "參數:2.3445, M_PI\n";
process(2.3445, M_PI, $farr);
// 現在建立一堆字串處理函式
$garr = array(
create_function('$b,$a', 'if (strncmp($a, $b, 3) == 0) return "** \"$a\" '.
'and \"$b\"\n** Look the same to me! (looking at the first 3 chars)";'),
create_function('$a,$b', 'return "CRCs: " . crc32($a) . ", ".crc32($b);'),
create_function('$a,$b', 'return "similar(a,b) = " . similar_text($a, $b, $p) . "($p%)";')
);
echo "\n使用第二個動態函式陣列\n";
process("Twas brilling and the slithy toves", "Twas the night", $garr);
?>
再次,以下是使用 匿名函式 的相同程式碼。請注意,程式碼中的變數名稱不再需要跳脫,因為它們並未包含在字串中。
<?php
function process($var1, $var2, $farr)
{
foreach ($farr as $f) {
echo $f($var1, $var2) . "\n";
}
}
// 建立一組數學函式
$farr = array(
function($x,$y) { return "三角函數運算:".(sin($x) + $x*cos($y)); },
function($x,$y) { return "斜邊長: ".sqrt($x*$x + $y*$y); },
function($a,$b) { if ($a >=0) {return "b*a^2 = ".$b*sqrt($a);} else {return false;} },
function($a,$b) { return "min(b^2+a, a^2,b) = " . min($a*$a+$b, $b*$b+$a); },
function($a,$b) { if ($a > 0 && $b != 0) {return "ln(a)/b = ".log($a)/$b; } else { return false; } }
);
echo "\n使用第一個動態函式陣列\n";
echo "參數:2.3445, M_PI\n";
process(2.3445, M_PI, $farr);
// 現在建立一組字串處理函式
$garr = array(
function($b,$a) { if (strncmp($a, $b, 3) == 0) return "** \"$a\" " .
"和 \"$b\"\n** 看起來很像! (檢查前 3 個字元)"; },
function($a,$b) { return "CRC 碼: " . crc32($a) . ", ".crc32($b); },
function($a,$b) { return "相似度(a,b) = " . similar_text($a, $b, $p) . "($p%)"; }
);
echo "\n使用第二個動態函式陣列\n";
process("Twas brilling and the slithy toves", "Twas the night", $garr);
?>
以上範例將輸出
Using the first array of dynamic functions parameters: 2.3445, M_PI some trig: -1.6291725057799 a hypotenuse: 3.9199852871011 b*a^2 = 4.8103313314525 min(b^2+a, a^2,b) = 8.6382729035898 ln(a)/b = 0.27122299212594 Using the second array of dynamic functions ** "Twas the night" and "Twas brilling and the slithy toves" ** Look the same to me! (looking at the first 3 chars) CRCs: 3569586014, 342550513 similar(a,b) = 11(45.833333333333%)
範例 #3 使用動態函式作為回呼函式
動態函式最常見的用途可能是將它們作為回呼傳遞,例如在使用 array_walk() 或 usort() 時。
<?php
$av = array("the ", "a ", "that ", "this ");
array_walk($av, create_function('&$v,$k', '$v = $v . "mango";'));
print_r($av);
?>
轉換為匿名函式
<?php
$av = array("the ", "a ", "that ", "this ");
array_walk($av, function(&$v,$k) { $v = $v . "mango"; });
print_r($av);
?>
以上範例將輸出
Array ( [0] => the mango [1] => a mango [2] => that mango [3] => this mango )
使用 create_function() 將字串從最長排序到最短
<?php
$sv = array("small", "a big string", "larger", "it is a string thing");
echo "原始:\n";
print_r($sv);
echo "已排序:\n";
usort($sv, create_function('$a,$b','return strlen($b) - strlen($a);'));
print_r($sv);
?>
轉換為匿名函式
<?php
$sv = array("small", "a big string", "larger", "it is a string thing");
echo "原始:\n";
print_r($sv);
echo "已排序:\n";
usort($sv, function($a,$b) { return strlen($b) - strlen($a); });
print_r($sv);
?>
以上範例將輸出
Original: Array ( [0] => small [1] => a big string [2] => larger [3] => it is a string thing ) Sorted: Array ( [0] => it is a string thing [1] => a big string [2] => larger [3] => small )
當在 PHP 中使用匿名函式時,請像在 Python、Ruby、Lisp 或 Javascript 等語言中一樣小心。如先前所述,已配置的記憶體永遠不會被釋放;它們在 PHP 中不是物件,它們只是動態命名的全域函式,因此它們沒有作用域,也不會受到垃圾回收的影響。
所以,如果你正在開發任何具有一定程度可重用性的東西(無論是物件導向或其他),我會像躲避瘟疫一樣避開它們。它們速度慢、效率低,而且無法預測你的實作是否會陷入大型迴圈中。我的實作最終迭代了大約一百萬筆記錄,並迅速耗盡了我每個程序 500MB 的限制。
關於 adaniels dot nl 的 info 提出的遞迴問題
透過在正確的作用域中引用函式變數來進行匿名函式遞迴。
<?php
$fn2 = create_function('$a', 'echo $a; if ($a < 10) call_user_func($GLOBALS["fn2"], ++$a);');
$fn2(1);
?>
嘗試使用這個方法來提升你的腳本效能(增加 maxCacheSize)
<?php
runkit_function_copy('create_function', 'create_function_native');
runkit_function_redefine('create_function', '$arg,$body', 'return __create_function($arg,$body);');
function __create_function($arg, $body) {
static $cache = array();
static $maxCacheSize = 64;
static $sorter;
if ($sorter === NULL) {
$sorter = function($a, $b) {
if ($a->hits == $b->hits) {
return 0;
}
return ($a->hits < $b->hits) ? 1 : -1;
};
}
$crc = crc32($arg . "\\x00" . $body);
if (isset($cache[$crc])) {
++$cache[$crc][1];
return $cache[$crc][0];
}
if (sizeof($cache) >= $maxCacheSize) {
uasort($cache, $sorter);
array_pop($cache);
}
$cache[$crc] = array($cb = eval('return function('.$arg.'){'.$body.'};'), 0);
return $cb;
}
?>
在將 PHP4 的程式碼庫遷移到 PHP5 的過程中,我遇到了一個特殊的問題。在函式庫中,每個類別都繼承自一個名為 'class_container' 的通用類別。 'class_container' 包含一個名為 runtime_functions 的陣列和一個名為 class_function 的方法,如下所示:
<?php
function class_function($name,$params,$code) {
$this->runtime_functions[$name] = create_function($params,$code);
}
?>
在 class_container 的子類別中,有一個函式使用 class_function() 來儲存一些自參考的自訂 lambda 函式
<?php
function myfunc($name,$code) {
$this->class_function($name,'$theobj','$this=&$theobj;'.$code);
}
?>
在 PHP4 中,這個方法運作良好。其想法是在子類別層級編寫程式碼區塊,例如「echo $this->id;」,然後簡單地使用 $MYOBJ->myfunc("go","echo $this->id;");,稍後像 $MYOBJ->runtime_functions["go"](); 這樣呼叫它。
它基本上就像在 Javascript 中將匿名函式繫結到物件一樣運作。
請注意,$code 區塊需要手動重新定義 "$this" 關鍵字才能運作。
然而,在 PHP5 中,你不能重新宣告 $this 而不會出現嚴重錯誤,因此程式碼必須更新為:
<?php
function myfunc($name,$code) {
$this->class_function($name,'$this',$code);
}
?>
顯然 create_function() 允許你透過函式參數設定 $this,讓你將匿名函式繫結到已實例化的物件。認為這對某些人可能有用。
請注意,在匿名函式中使用 __FUNCTION__,將始終產生 '__lambda_func'。
<?php
$fn = create_function('', 'echo __FUNCTION__;');
$fn();
// 結果:__lambda_func
echo $fn;
// 結果:ºlambda_2 (實際的第一個字元無法顯示)
?>
這表示匿名函式無法遞迴使用。以下程式碼(遞迴計數到 10)會導致錯誤
<?php
$fn2 = create_function('$a', 'echo $a; if ($a < 10) call_user_func(__FUNCTION__, $a++);');
$fn2(1);
// 警告:call_user_func(__lambda_func) [function.call-user-func]:第一個引數預期是 T:/test/test.php(21) 中一個有效的回呼:在第 1 行執行時建立的函式
?>
[由 danbrown AT php DOT net 編輯:將使用者更正的文章與之前(不正確)的文章合併。]
你無法從類別方法內的匿名函式中使用 $this 來引用類別變數。匿名函式不會繼承方法的作用域。你必須這樣做:
<?php
class AnyClass {
var $classVar = 'some regular expression pattern';
function classMethod() {
$_anonymFunc = create_function( '$arg1, $arg2', 'if ( eregi($arg2, $arg1) ) { return true; } else { return false; } ' );
$willWork = $_anonymFunc('some string', $classVar);
}
}
?>
以下函式對於建立使用者函式的別名非常有用。
對於內建函式,它不太有用,因為預設值不可用,因此內建函式的函式別名必須提供所有參數,無論是否為選用參數。
<?php
function create_function_alias($function_name, $alias_name)
{
if(function_exists($alias_name))
return false;
$rf = new ReflectionFunction($function_name);
$fproto = $alias_name.'(';
$fcall = $function_name.'(';
$need_comma = false;
foreach($rf->getParameters() as $param)
{
if($need_comma)
{
$fproto .= ',';
$fcall .= ',';
}
$fproto .= '$'.$param->getName();
$fcall .= '$'.$param->getName();
if($param->isOptional() && $param->isDefaultValueAvailable())
{
$val = $param->getDefaultValue();
if(is_string($val))
$val = "'$val'";
$fproto .= ' = '.$val;
}
$need_comma = true;
}
$fproto .= ')';
$fcall .= ')';
$f = "function $fproto".PHP_EOL;
$f .= '{return '.$fcall.';}';
eval($f);
return true;
}
?>
對於那些*真的*需要在 php8 上使用 create_function() 的人(因為有無法輕易更改的舊程式碼),可以使用這個:「composer require lombax85/create_function」。
最佳封裝器
<?php
function create_lambda($args, $code) {
static $func;
if (!isset($func[$args][$code])) {
$func[$args][$code] = create_function($args, $code);
}
return $func[$args][$code];
}
針對 kkaiser at revolution-records dot net 的筆記,即使 PHP 允許你使用
<?
$myfunc = create_function('$this', $code);
?>
你也不能在匿名函式內使用 "$this" 的參考,因為 PHP 會抱怨你在非物件的上下文中使用了 "$this" 的參考。
目前,我還沒有找到解決方法...
$f = create_function('','echo "function defined by create_function";');
$f();
結果
function defined by create_function
當你使用 create_function 時,你可以在函式主體中不定義任何回傳值。
這裡有一些關於 create_function() 可能會產生「記憶體洩漏」的討論。
create_function() 實際上做的是建立一個名稱為 chr(0).lambda_n 的普通函式,其中 n 是一個數字
<?php
$f = create_function('', 'return 1;');
function lambda_1() { return 2; }
$g = "lambda_1";
echo $g(); // 輸出:2
$h = chr(0)."lambda_1";
echo $h(); // 輸出:1
?>
以下是如何從另一個執行階段建立的函式呼叫執行階段建立的函式
<?php
$get_func = create_function('$func', 'return substr($func,1);');
$get_value = create_function('$index','return pow($index,$index);');
$another_func = create_function('$a', '$func="\x00"."'.$get_func($get_value).'";return $func($a);');
echo $another_func(2); # 結果是 4
?>
回覆 info at adaniels dot nl
你可能無法在 lambda 中使用 __FUNCTION__(感謝你指出這一點;我剛剛遇到這個問題),但如果你將函式指派給變數,你可以使用 $GLOBALS 來解決。我用 PHP4 重新實作了 array_walk_recursive(),如下所示
<?php
$array_walk_recursive = create_function('&$array, $callback',
'foreach($array as $element) {
if(is_array($element)) {
$funky = $GLOBALS["array_walk_recursive"];
$funky($element, $callback);
}
else {
$callback($element);
}
}');
?>
create_function() 建立的函式無法透過參考傳回值。下面的函式建立一個可以這樣做的函式。引數與 create_function() 相同。請注意,這些引數會以未修改的方式傳遞給 eval(),因此請務必對傳入的資料進行消毒。
<?php
/**
* create_ref_function
* 建立一個匿名(lambda 風格)函式
* 它會傳回一個參考
* 請參閱 https://php.dev.org.tw/create_function
*/
function
create_ref_function( $args, $code )
{
static $n = 0;
$functionName = sprintf('ref_lambda_%d',++$n);
$declaration = sprintf('function &%s(%s) {%s}',$functionName,$args,$body);
eval($declaration);
return $functionName;
}
?>
如果你要檢查函式是否正確建立,這會是更好的檢查方法
<?php
$fnc = @create_function('$arg1,$arg2,$arg3', 'return true;');
# 將該函式設為你想要的任何內容
if (empty($fnc)) {
die('無法建立函式 $fnc。');
}
# 雖然,以下程式碼會*無法*運作
if (empty(create_function('$arg', 'return $arg;'))) {
die('無法建立匿名函式。');
}
# 你會收到一個錯誤,指出無法在可寫入的上下文中使用
# 回傳值(亦即,回傳值在 C 語言中是一個 const,而函式 empty() 並未使用
# const void* 引數
?>
neo at gothic-chat d0t de 寫道
請注意記憶體洩漏,垃圾收集似乎「忽略」了動態建立的函式!
並非如此...
事實上,PHP 無法「取消指派」函式。因此,如果你建立一個函式,它將不會被刪除,直到腳本結束,即使你取消設定包含其名稱的變數也是如此。
如果你需要每次執行迴圈時變更函式的一部分,請思考如何建立一個更通用的函式,或嘗試使用 eval :) (函式是為了重複使用而建立的。如果你需要執行一次自己的程式碼,eval 會更好)。
我想到了一個小玩具,我想分享。它會建立一個匿名函式,讓你可以將類別當作函式使用。
在 php 5.3 中,支援真正的函子(透過 __invoke)
<?php
function createFunctor($className){
$content = "
請注意記憶體洩漏,垃圾收集似乎「忽略」了動態建立的函式!
我使用了一個類似這樣的函式來將連結中的特殊字元替換為它們的 HTML 實體。
<?php
$text = preg_replace_callback (
"/(<(frame src|a href|form action)=\")([^\"]+)(\"[^>]*>)/i",
create_function (
'$matches',
'return $matches[1] . htmlentities ($matches[3]) . $matches[4];'
),
$text);
?>
經過 1000 次呼叫後,該進程使用的記憶體比之前多了約 5MB。在我的情況下,這將一個 PHP 進程的記憶體大小提高到超過 100MB!
在這種情況下,最好將函式儲存在全域變數中。
Create_function 允許改變函式的範圍。您可能有一個類別需要定義一個全域函式。這是可行的,例如:
<?php
class blah {
function blah() {
$z=create_function('$arg1string','return "function-z-".$arg1string;');
$GLOBALS['z']=$z;
}
}
$blah_object=new blah;
$result=$GLOBALS['z']('Argument 1 String');
echo $result;
?>
在許多情況下,讓函式逃脫其定義的範圍可能很有用。