虽然在 11 年前是正确的,但 Dan D 的说法现在已经不那么正确了。匿名函数现在是 Closure 类的对象,并且可以被垃圾回收器安全地回收。
(PHP 4 >= 4.0.1, PHP 5, PHP 7)
create_function — 通过评估代码字符串动态创建函数
根据传递的参数动态创建一个函数,并返回一个唯一的函数名。
通常建议将这些参数作为 单引号 字符串传递。如果使用 双引号 字符串,则代码中的变量名需要小心转义,例如 \$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 "\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 "some trig: ".(sin($x) + $x*cos($y)); },
function($x,$y) { return "a hypotenuse: ".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 "\nUsing the first array of dynamic functions\n";
echo "parameters: 2.3445, M_PI\n";
process(2.3445, M_PI, $farr);
// 现在创建一堆字符串处理函数
$garr = array(
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)"; },
function($a,$b) { return "CRCs: " . crc32($a) . ", ".crc32($b); },
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);
?>
上面的示例将输出
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 "Original:\n";
print_r($sv);
echo "Sorted:\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 "Original:\n";
print_r($sv);
echo "Sorted:\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 中不是对象——它们只是动态命名的全局函数——因此它们没有作用域,也不受垃圾回收的影响。
因此,如果您正在开发任何可重用的东西(OO 或其他),我会像瘟疫一样避开它们。它们速度慢、效率低,而且无法确定您的实现是否最终会进入一个大的循环。我的实现最终在一个大约 100 万条记录的迭代中,很快耗尽了我的每进程 500 MB 的限制。
关于 info at adaniels dot nl 的递归问题
通过在正确作用域中引用函数变量来实现匿名函数递归。
<?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.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 at gothic-chat d0t 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');
?>
注意内存泄漏,垃圾回收似乎“忽视”了动态创建的函数!
我使用了一个这样的函数来将链接中的特殊字符替换为它们的 htmlentities
<?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;
?>
使函数从其定义的作用域中逃脱在许多情况下都很有用。