虽然 11 年前它是正确的,但 Dan D 的说法现在并不那么准确了。匿名函数现在是 Closure 类的一个对象,并由垃圾收集器安全地回收。
(PHP 4 >= 4.0.1, PHP 5, PHP 7)
create_function — 通过评估代码字符串动态创建函数
此函数自 PHP 7.2.0 起已弃用,自 PHP 8.0.0 起移除。强烈建议不要依赖此函数。
根据传递的参数动态创建一个函数,并返回其唯一名称。
返回一个唯一的函数名作为字符串,或者在失败时返回 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 "\nUsing the first array of dynamic functions\n";
echo "parameters: 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 "\nUsing the second array of dynamic functions\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 中不是对象——它们只是动态命名的全局函数——因此它们没有作用域,也不受垃圾回收的影响。
因此,如果您正在开发任何可重用的东西(面向对象或其他),我建议您尽量避免使用它们。它们速度慢、效率低,而且无法确定您的实现是否最终会进入一个大的循环。我的实现最终迭代了大约 100 万条记录,并迅速耗尽了我每个进程 500MB 的限制。
关于 adaniels.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 中将匿名函数绑定到对象完全相同。
请注意,必须手动重新定义“$this”关键字才能使 $code 块正常工作。
但是,在 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@revolution-records.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@adaniels.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.net/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('Could not create function $fnc.');
}
# 但是,下面的代码不会工作
if (empty(create_function('$arg', 'return $arg;'))) {
die('Could not create anonymous function.');
}
# 你会收到关于无法在可写上下文中使用返回值的错误(即,返回值在 C 中是一个常量,而 empty() 函数不使用 const void* 参数
?>
neo@gothic-chat.de 写道
注意内存泄漏,垃圾回收似乎“忽略”了动态创建的函数!
并非如此……
实际上,PHP 无法“取消分配”函数。因此,如果你创建了一个函数,它在脚本结束前不会被删除,即使你取消设置包含其名称的变量。
如果你需要每次运行循环时更改函数的一部分,请考虑一种创建更通用函数的方法,或者尝试使用 `eval()` :)(函数是为了重复使用而设计的。如果你只需要运行你自己的代码片段一次,`eval()` 会更好)。
我只是想出一个小的玩具,我想分享一下。创建一个匿名函数,让你可以使用类作为函数。
在 PHP 5.3 中,支持真正的函子(通过 `__invoke`)。
<?php
function createFunctor($className){
$content = "
static \$class;
if(!\$class){
\$class = new $className;
}
return \$class->run(\$args);
";
$f = create_function('$args', $content);
return $f;
}
class test {
public function run($args){
print $args;
}
}
$test = createFunctor('test');
$test('hello world');
?>
注意内存泄漏,垃圾回收似乎“忽略”了动态创建的函数!
我使用类似这样的函数将链接中的特殊字符替换为它们的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;
?>
使函数跳出其定义的作用域在许多情况下都很有用。