通过call_user_func_array传递命名参数给PHP函数

16
当尝试使用任意一组参数在子类中调用函数时,我遇到了以下问题:
class Base{

    function callDerived($method,$params){
        call_user_func_array(array($this,$method),$params);
    }
}

class Derived extends Base{
    function test($foo,$bar){
        print "foo=$foo, bar=$bar\n";
    }
}

$d = new Derived();
$d->callDerived('test',array('bar'=>'2','foo'=>1));

输出:

foo=2, bar=1

我想知道是否有一种方法可以实现我的需求,而不必重新组合带有func_get_args索引顺序的数组。当然,我可以直接传递整个数组并在函数中处理它...但那不是我想要做的事情。

谢谢


2
我认为你在这里没什么好运气... - Felix Kling
不行。而且你不知道自己方法参数的顺序感觉很不舒服 :X 如果你想确保特定的顺序,也许应该考虑使用“接口”。 - KingCrunch
@KingCrunch 实际上,我不确定我得到的参数数组的顺序 - 但是是的,这是一个笨拙的情况。 - Wagemage
2
@Wagemage:也许你根本不需要使用call_user_func_array()函数:可以直接使用call_user_func('func', $array['foo'], $array['bar']); - KingCrunch
@KingCrunch,是的,我明白你的意思 - 但对于不同的派生类,调用将具有不同数量和顺序的参数...这是一个我试图去除低效的修补程序,而不是全新的代码。 - Wagemage
显示剩余3条评论
8个回答

24

不,PHP不支持命名参数。只有参数的顺序被考虑在内。您可能可以使用ReflectionClass来拆解代码以检查函数参数名称,但最终您仍需要使用它来重新排序数组。


是的,我就担心会出现这种情况。唉... 美学已经不重要了,我可以通过“func_get_args”的方式来解决它。 - Wagemage
现在 PHP 8 支持命名参数 :) - a55

8

股票PHP类 ReflectionMethod 是你的朋友。

例如:

class MyClass { 
    function myFunc($param1, $param2, $param3='myDefault') { 
        print "test"; 
    } 
}

$refm = new ReflectionMethod('MyClass', 'myFunc');

foreach ($refm->getParameters() as $p) 
    print "$p\n";

结果如下:
Parameter #0 [ <required> $param1 ]
Parameter #1 [ <required> $param2 ]
Parameter #2 [ <optional> $param3 = 'myDefault' ]

此时,您已经知道目标函数的参数名称。有了这些信息,您可以修改方法“callDerived”,并根据参数名称重新排序数组以便于使用call_user_func_array

5

好消息,我有同样的疑虑(我在寻找PHP中类似Python的命名参数),并找到了这个有用的工具:https://github.com/PHP-DI/Invoker

该工具使用反射API从数组中提取一些参数来调用可调用对象,并且还使用可选参数默认值来代替数组中未定义的其他参数。

$invoker = new Invoker\Invoker;
$result = $invoker->call(array($object, 'method'), array(
    "strName"  => "Lorem",
    "strValue" => "ipsum",
    "readOnly" => true,
    "size"     => 55,
));

玩得愉快


DownVote: 这不叫做“命名参数”。这只是将一个映射传递给函数。命名参数将支持代码完成、反射等功能... - Behnam

5

更新: PHP 8现在支持命名参数,如果您传递一个关联数组,它可以与call_user_func_array一起使用。因此,您可以简单地执行以下操作:

<?php

function myFunc($foo, $bar) {
    echo "foo=$foo, bar=$bar\n";
}

call_user_func_array('myFunc', ['bar' => 2, 'foo' => 1]);
// Outputs:  foo=1, bar=2

在你的代码中,你会高兴地知道,你不需要改变任何东西。只需升级到PHP 8,它将按照你的预期工作。


3
您可以简单地传递一个数组并提取它:
function add($arr){
    extract($arr, EXTR_REFS);
    return $one+$two;
}
$one = 1;
$two = 2;
echo add(compact('one', 'two')); // 3

这将作为引用提取,因此几乎没有开销。

3

我使用位掩码(bitmask)代替布尔参数:

// Ingredients
define ('TOMATO',    0b0000001);
define ('CHEESE',    0b0000010);
define ('OREGANO',   0b0000100);
define ('MUSHROOMS', 0b0001000);
define ('SALAMI',    0b0010000);
define ('PEPERONI',  0b0100000);
define ('ONIONS',    0b1000000);

function pizza ($ingredients) {
  $serving = 'Pizza with';
  $serving .= ($ingredients&TOMATO)?' Tomato':''; 
  $serving .= ($ingredients&CHEESE)?' Cheese':''; 
  $serving .= ($ingredients&OREGANO)?' Oregano':''; 
  $serving .= ($ingredients&MUSHROOMS)?' Mushrooms':''; 
  $serving .= ($ingredients&SALAMI)?' Salami':''; 
  $serving .= ($ingredients&ONIONS)?' Onions':''; 
  return trim($serving)."\n" ;
}

// Now order your pizzas!
echo pizza(TOMATO | CHEESE | SALAMI); 
echo pizza(ONIONS | TOMATO | MUSHROOMS | CHEESE); // "Params" are not positional

1

对于那些可能仍会遇到这个问题的人(就像我一样),这是我的方法:

自从PHP 5.6,你可以使用...,如此处所述:

在这种情况下,你可以使用类似这样的东西:

class Base{

    function callDerived($method,...$params){
            call_user_func_array(array($this,$method),$params);
        }
}

class Derived extends Base{
    function test(...$params){
        foreach ($params as $arr) {
            extract($arr);
        }
        print "foo=$foo, bar=$bar\n";
    }
}

$d = new Derived();
$d->callDerived('test',array('bar'=>'2'),array('foo'=>1)); 

//print: foo=1, bar=2

0

有一种方法可以做到,那就是使用数组(最简单的方法):

class Test{
    public $a  = false;
    private $b = false;
    public $c  = false;
    public $d  = false;
    public $e  = false;
    public function _factory(){
        $args    = func_get_args();
        $args    = $args[0];
        $this->a = array_key_exists("a",$args) ? $args["a"] : 0;
        $this->b = array_key_exists("b",$args) ? $args["b"] : 0;
        $this->c = array_key_exists("c",$args) ? $args["c"] : 0;
        $this->d = array_key_exists("d",$args) ? $args["d"] : 0;
        $this->e = array_key_exists("e",$args) ? $args["e"] : 0;
    }
    public function show(){
        var_dump($this);
    }
}

$test = new Test();
$args["c"]=999;
$test->_factory($args);
$test->show();

完整的解释可以在我的博客中找到: http://www.tbogard.com/2013/03/07/passing-named-arguments-to-a-function-in-php/


1
真的吗?希望你永远不会弄错像 $this->e = array_key_exists("d",$args) ? $args["e"] : 0; 这样的东西,调试起来太费时间了... 顺便说一下,你用了7行代码只是为了在调用函数时保存几个参数吗?显然不值得。 - Julien Palard

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