在 PHP 中如何通过引用传递一个函数

38

能否通过引用传递函数?

类似这样:

function call($func){
    $func();
}

function test(){
    echo "hello world!";
}

call(test);
我知道你可以这样做'test',但我不想这样做,因为我需要通过引用传递函数。 那么唯一的方法是使用匿名函数吗? 澄清一下:如果您还记得C ++,您可以通过指针传递函数。
void call(void (*func)(void)){
    func();
}

或者在 Python 中:

def call(func):
    func()

这就是我试图要做到的。


3
“需要通过引用传递函数”是什么意思? - deceze
5
这个问题正在演变成争论,因为您没有澄清“按引用传递”的含义(“按引用传递”已经有一个特定的含义,我认为不适用于此处)。投票关闭。 - deceze
1
你刚才说:“你可以按值传递也可以按引用传递。”然后立即说我都不能做。 - Pwnna
2
在 PHP 中,按引用传递具有不同的含义,这意味着如果您修改通过引用传递的变量,则原始变量将被修改。在这个意义上它类似于指针,但并不完全相同。我说过“这不适用于函数”,因为您无法修改函数。 - deceze
1
@ultimatebuster:也许我们应该从头开始。你想通过引用传递函数来实现什么?你能更新一下你的帖子来解释一下吗? - bob-the-destroyer
显示剩余5条评论
11个回答

35

如果方便的话,不妨试试这样的方法?(是的,我知道这是一个匿名函数,在帖子中提到过,但我对没有提到闭包/函数对象的回复感到不满意,所以这主要是为那些遇到这篇文章的人做个笔记。)

我不使用PHP,但使用闭包似乎在PHP 5.3中可以工作(但在PHP 5.2中不行),在这里演示我不确定有什么限制(如果有的话)。 (说不定闭包会吃掉你的孩子。你已经被警告了。)

function doIt ($fn) {
  echo "doIt\n";
  return $fn();
}

function doMe () {
  echo "doMe\n";
}

// I am using a closure here.
// There may be a more clever way to "get the function-object" representing a given
// named function, but I do not know what it is. Again, I *don't use PHP* :-)
echo doIt(function () { doMe(); });

愉快地编码吧。


2
这与原帖中作者提出的有何不同? - deceze
4
你将一个匿名函数传递给了 doIt,这如何"通过引用传递了doMe"? 很抱歉,这个答案和其他的一样无关紧要,主要是因为问题非常不明确。 - deceze
13
@ultimate 好的,PHP并不优雅。 - deceze
4
不,这正是OP所要求的。它使用一个匿名函数封闭必需调用,因此接收该匿名函数作为参数的函数无需知道它是否接收了类方法或全局函数。这是最具灵活性的做法。 - Izkata
5
为了澄清,PHP 知道不同的 callable 表示方法:'globalFunctionName''ClassName::staticMethodName'array('ClassName', 'staticMethodName')array($object, 'methodName')。所有这些表示方法都可以与 call_user_func()is_callable() 等函数一起使用,但它们实际上并不是引用(除了表示方法 4 中的对象实例),而只是标识符。- 文档 - Lukas
显示剩余8条评论

18

call_user_func() 的问题在于你传递的是被调用函数的返回值,而不是函数本身。

我之前也遇到过这个问题,这是我想出的解决方案。

function funcRef($func){
  return create_function('', "return call_user_func_array('{$func}', func_get_args());");
}

function foo($a, $b, $c){
    return sprintf("A:%s B:%s C:%s", $a, $b, $c);
}

$b = funcRef("foo");

echo $b("hello", "world", 123);

//=> A:hello B:world C:123

ideone.com演示


1
这正是我想要的。谢谢! - Edd Barrett
与@user166390的最高票和被接受的答案相比,这个实际上做到了我想要的,让我可以将不同的C风格“函数指针”传递到PHP函数中,然后使用参数调用该函数指针。谢谢。 - FormerAtariUser
1
create_function() 函数自 PHP 7.2.0 起已被弃用,并在 PHP 8.0.0 中被移除。 - MAChitgarha

4
不,函数在PHP中不是一等值,它们不能通过它们的名称直接传递(这就是你所要求的)。即使是匿名函数或通过create_function创建的函数也是通过对象或字符串引用传递的。
您可以将函数名称作为字符串传递,将对象方法的名称作为(object, string)数组或将匿名函数作为对象传递。这些都不会传递指针或引用,它们只是传递函数的名称。所有这些方法都被称为回调伪类型:http://php.net/callback

这个关于匿名函数的链接(http://php.net/manual/en/functions.anonymous.php)认为函数不是一等公民。(当然,“一等公民”是一个很宽泛的概念,可能会引起争议,但无论如何,根据示例,对象可以直接被*调用*--但我不使用PHP,所以主要是在寻找更好的解释。)那么如何将`test`放入函数对象中呢?例如,可以这样做:`$test2 = function () { test() }; call($test2)`吗?或者我错过了什么:p - user166390
1
@pst 当然,但那只是语法糖。匿名函数是使用Closure类实现的,因此您实际上传递的是一个对象,而不是一个函数。请注意,函数名称的语法为“name”,而“匿名函数调用”的语法为“$name()”。您永远无法在“传递”的或匿名函数上执行“name()”。 - deceze
1
无论观察的语义是否相同都没有关系。如果使用“只是一个对象的无糖”这个论点,那么Scala或JavaScript都不会有一流的函数,但它们确实有。 (一个“函数”和一个可以像“函数”一样处理的对象之间的区别是什么?这取决于语言,但...) - user166390
1
@pst 可能“一流”是有争议的,但你无法做到OP想要做的事情。没得商量。 - deceze
1
我不同意。“对我有效”请看我的回复。 - user166390

3
function func1(){
    echo 'echo1 ';
    return 'return1';
}

function func2($func){
    echo 'echo2 ' . $func();
}

func2('func1');

结果:

echo1 echo2 return1

如果你能详细解释一下这个为什么有效,那就太好了 :) @dhbmarcos - Jorge Y. C. Rodriguez
1
这个答案对于大多数用例可能已经足够好了,但对于这个问题来说并不正确。想象一下func1在func2的作用域之外是不可访问的。在这种情况下,这个解决方案将无法工作。 - JepZ

2
在PHP 5.4.4中(未测试更低版本或其他版本),你可以完全按照你建议的那样操作。
以这个为例:
function test ($func) {
    $func('moo');
}

function aFunctionToPass ($str) {
    echo $str;
}

test('aFunctionToPass');

脚本将回显"moo",就像您直接调用"aFunctionToPass"一样。

2
对我来说,它会给出一个通知:PHP通知:使用未定义的常量aFunctionToPass - 假定为'aFunctionToPass'。当我将函数名作为字符串传递时,通知消失了:test('aFunctionToPass'); - user4035
嗯,对,这很有道理。我没有检查警告(我现在已经关闭了它们),所以谢谢你提醒。 - Rob Evans
已更新以修复警告。 - Rob Evans

1

这是一个类似于Javascript一等函数的模式:

function add(first, second, callback){
    console.log(first+second);
    if (callback) callback();
}

function logDone(){
    console.log('done');
}

function logDoneAgain(){
    console.log('done Again');
}
add(2,3, logDone);
add(3,5, logDoneAgain);

可以用PHP完成(在C9 IDE上测试了5.5.9-1ubuntu),方法如下:
// first class function
$add = function($first, $second, $callback) {
  echo "\n\n". $first+$second . "\n\n";
  if ($callback) $callback();
};

function logDone(){
  echo "\n\n done \n\n";
}
call_user_func_array($add, array(2, 3, logDone));
call_user_func_array($add, array(3, 6, function(){
  echo "\n\n done executing an anonymous function!";
}));
结果: 5已完成,9已完成,正在执行匿名函数!
参考: https://github.com/zenithtekla/unitycloud/commit/873659c46c10c1fe5312f5cde55490490191e168

1
你可以在声明函数时将其赋值给一个本地变量来创建引用:
$test = function() {
    echo "hello world!";
};

function call($func){
    $func();
}

call($test);

这是正确的答案,通过匿名函数实现。它不使用“变量函数”结构,而是使用 $test 作为对象。顺便说一句,如果你这样写 $test = function(){...} 然后 $call = test 没有引号,仍然不能得到一个引用。$call() 起作用是因为在 PHP 中允许未加引号的单词作为字符串,所以它是一个“变量函数”,与 $call = 'test' 相同。 - P2000
更正一下,我指的是:如果你编写了一个函数 test(){...},然后用 $call = test(没有引号!)调用它,你仍然得不到一个引用。 - P2000

0

这似乎有点不清楚,为什么你想通过引用传递函数?通常只有在函数需要(可能)修改引用数据时才会通过引用传递。

由于PHP使用数组或字符串来引用函数,因此您可以通过引用传递数组或字符串,从而允许修改函数引用。

例如,您可以这样做:

<?php
$mysort = function($a, b) { return ($a < $b) ? 1 : -1; };
adjust_sort_from_config($mysort); // modifies $mysort
do_something_with_data($mysort);

在哪里

<?php
function load_my_configuration(&$fun)
{
  $sort_memory = new ...;
  ...
  $fun = [$sort_memory, "customSort"];
  // or simply
  $fun = function($a, b) { return (rand(1,10) < 4 ? 1 : -1; };
}

这是因为在PHP中,有三种通过变量引用函数的方式:

  • $name - 字符串$name包含应该调用的全局命名空间中函数的名称
  • array($object, $name) - 引用对象$object的名为$string的方法。
  • array($class, $name) - 引用类$class的静态函数$string。

如果我没记错的话,这些结构指向的方法和静态函数必须是公共的。 "First-class callable syntax" 应该改善这个限制,只要PHP版本足够新,但它似乎只是一些语法糖,围绕着 Closure::fromCallable()

匿名函数在幕后工作方式相同。你只是看不到那些函数的字面随机名称,但对匿名函数的引用也只是一个变量的值而已。


0
你可以说:
$fun = 'test'; 
call($fun); 

这个问题能否通过“获取函数对象”来解决test?比如 call(function () { test() })?(我不知道那是否可行)。代码中的问题在于'test'是一个“未绑定”的字符串,只有在调用时(例如通过call_user_function)才会被解析--此时它可能不同或处于不正确的上下文中。 - user166390

0

使用call_user_func('test');代替call(test);


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接