PHP Conference Japan 2024

create_function

(PHP 4 >= 4.0.1, PHP 5, PHP 7)

create_function透過評估程式碼字串來動態建立函式

警告

此函式已於 PHP 7.2.0 版本棄用,並於 PHP 8.0.0 版本移除。強烈不建議依賴此函式。

描述

create_function(string $args, string $code): string

從傳入的參數動態建立函式,並傳回該函式的唯一名稱。

注意

此函式內部會執行 eval(),因此與 eval() 有相同的安全問題。它也具有效能和記憶體使用方面的缺點,因為建立的函式是全域的,而且無法釋放。

應該改用原生的 匿名函式

參數

通常建議將這些參數作為 單引號 字串傳遞。如果使用 雙引號 字串,則程式碼中的變數名稱需要仔細跳脫,例如 \$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
)

參見

新增註解

使用者貢獻的註解 22 則註解

19
tamagochi_man
6 年前
雖然 11 年前 Dan D 的說法是正確的,但現在已不再那麼正確了。匿名函式現在是 Closure 類別的物件,並且可以安全地被垃圾回收器回收。
13
Dan D
18 年前
當在 PHP 中使用匿名函式時,請像在 Python、Ruby、Lisp 或 Javascript 等語言中一樣小心。如先前所述,已配置的記憶體永遠不會被釋放;它們在 PHP 中不是物件,它們只是動態命名的全域函式,因此它們沒有作用域,也不會受到垃圾回收的影響。

所以,如果你正在開發任何具有一定程度可重用性的東西(無論是物件導向或其他),我會像躲避瘟疫一樣避開它們。它們速度慢、效率低,而且無法預測你的實作是否會陷入大型迴圈中。我的實作最終迭代了大約一百萬筆記錄,並迅速耗盡了我每個程序 500MB 的限制。
3
Josh J
18 年前
關於 adaniels dot nl 的 info 提出的遞迴問題

透過在正確的作用域中引用函式變數來進行匿名函式遞迴。
<?php
$fn2
= create_function('$a', 'echo $a; if ($a < 10) call_user_func($GLOBALS["fn2"], ++$a);');
$fn2(1);
?>
3
kak dot serpom dot po dot yaitsam at gmail dot com
12 年前
嘗試使用這個方法來提升你的腳本效能(增加 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;
}
?>
2
kkaiser at revolution-records dot net
17 年前
在將 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,讓你將匿名函式繫結到已實例化的物件。認為這對某些人可能有用。
2
info at adaniels dot nl
18 年前
請注意,在匿名函式中使用 __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 行執行時建立的函式
?>
1
DB on music_ml at yahoo dot com dot ar
21 年前
[由 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);

}

}
?>
1
Dave H
13 年前
以下函式對於建立使用者函式的別名非常有用。
對於內建函式,它不太有用,因為預設值不可用,因此內建函式的函式別名必須提供所有參數,無論是否為選用參數。

<?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;
}
?>
1
lombax85 at gmail dot com
3 年前
對於那些*真的*需要在 php8 上使用 create_function() 的人(因為有無法輕易更改的舊程式碼),可以使用這個:「composer require lombax85/create_function」。
0
CertaiN
11 年前
最佳封裝器

<?php

function create_lambda($args, $code) {
static
$func;
if (!isset(
$func[$args][$code])) {
$func[$args][$code] = create_function($args, $code);
}
return
$func[$args][$code];
}
0
neo at nowhere dot com
16 年前
針對 kkaiser at revolution-records dot net 的筆記,即使 PHP 允許你使用
<?
$myfunc = create_function('$this', $code);
?>
你也不能在匿名函式內使用 "$this" 的參考,因為 PHP 會抱怨你在非物件的上下文中使用了 "$this" 的參考。

目前,我還沒有找到解決方法...
0
Alan FUNG
16 年前
$f = create_function('','echo "function defined by create_function";');
$f();

結果
function defined by create_function

當你使用 create_function 時,你可以在函式主體中不定義任何回傳值。
0
Rene Saarsoo
16 年前
這裡有一些關於 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
?>
0
TSE-WebDesign
17 年前
以下是如何從另一個執行階段建立的函式呼叫執行階段建立的函式
<?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
?>
0
Phlyst
18 年前
回覆 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);
}
}'
);
?>
0
josh at janrain dot com
18 年前
注意!這只是一個方便的函式,它為常規函式產生一個唯一的名稱。它*不是*一個閉包,甚至不是一個匿名函式。它只是一個為你命名的常規函式。
0
Joshua E Cook
18 年前
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;
}
?>
0
boards at gmail dot com
18 年前
如果你要檢查函式是否正確建立,這會是更好的檢查方法

<?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* 引數
?>
0
MagicalTux at FF.ST
20 年前
neo at gothic-chat d0t de 寫道
請注意記憶體洩漏,垃圾收集似乎「忽略」了動態建立的函式!

並非如此...

事實上,PHP 無法「取消指派」函式。因此,如果你建立一個函式,它將不會被刪除,直到腳本結束,即使你取消設定包含其名稱的變數也是如此。

如果你需要每次執行迴圈時變更函式的一部分,請思考如何建立一個更通用的函式,或嘗試使用 eval :) (函式是為了重複使用而建立的。如果你需要執行一次自己的程式碼,eval 會更好)。
-1
edgar at goodforall dot eu
15 年前
我想到了一個小玩具,我想分享。它會建立一個匿名函式,讓你可以將類別當作函式使用。

在 php 5.3 中,支援真正的函子(透過 __invoke)

<?php
function createFunctor($className){
$content = "
-1
neo at gothic-chat d0t de
20 年前
請注意記憶體洩漏,垃圾收集似乎「忽略」了動態建立的函式!

我使用了一個類似這樣的函式來將連結中的特殊字元替換為它們的 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!

在這種情況下,最好將函式儲存在全域變數中。
-1
x-empt[a_t]ispep.cx
23 年前
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;
?>

在許多情況下,讓函式逃脫其定義的範圍可能很有用。
To Top