使用call_user_func_array向构造函数传递参数

11

我在谷歌搜索和Stack Overflow上搜索了很多页面,但都找不到一个看起来适合我的情况的解决方案。我正在尝试构建一个使用call_user_func_array动态创建对象的函数,但似乎还有最后一个问题。

我得到的可捕获致命错误是Object of class Product could not be converted to string(无法将类Product的对象转换为字符串)。当出现此错误时,在日志中我会得到五个这样的警告(每个参数一个):PHP Warning: Missing argument 1 for Product::__construct(),然后才是可捕获的致命错误。

这是该函数的代码:

public static function SelectAll($class, $table, $sort_field, $sort_order = "ASC")
{  
/* First, the function performs a MySQL query using the provided arguments. */

$query = "SELECT * FROM " .$table. " ORDER BY " .$sort_field. " " .$sort_order;
$result = mysql_query($query);

/* Next, the function dynamically gathers the appropriate number and names of properties. */

$num_fields = mysql_num_fields($result);
for($i=0; $i < ($num_fields); $i++)
{
  $fetch = mysql_fetch_field($result, $i);
  $properties[$i] = $fetch->name;
}

/* Finally, the function produces and returns an array of constructed objects.*/

while($row = mysql_fetch_assoc($result))
{
  for($i=0; $i < ($num_fields); $i++)
  {
    $args[$i] = $row[$properties[$i]];
  }
  $array[] = call_user_func_array (new $class, $args);
}

return $array;
}

现在,如果我注释掉 call_user_func_array 这一行,并将其替换为以下内容:

$array[] = new $class($args[0],$args[1],$args[2],$args[3],$args[4]);
页面按照预期加载,并填充我正在构建的表格,因此在我尝试实际使用$args数组内的call_user_func_array函数之前,一切都是绝对正常的。是否有一些微妙的细节我忽略了调用该数组?我已经阅读了一遍PHP手册中关于call_user_func_array函数的内容,然后再读了一些例子,这些例子似乎只是展示了人们如何构建一个数组并将其用作第二个参数。我可能做错了什么?
3个回答

20

你不能像这样调用$class的构造函数:

call_user_func_array (new $class, $args);

第一个参数不是有效的回调函数。让我们来分析一下:


That's no valid callback as first parameter. Let's pick this apart:
call_user_func_array (new $class, $args);

与...相同

$obj = new $class;
call_user_func_array ($obj, $args);

如你所见,$class 的构造函数在 call_user_func_array 生效之前已经被调用了。由于它没有参数,因此你会看到这个错误信息:

Missing argument 1 for Product::__construct()

除此之外,$obj 是一个对象类型。有效的回调必须是字符串或数组(或者特殊对象 Closure,但那不在这里讨论,我只是为了完整性而提到它)。

由于 $obj 是一个对象而不是有效回调函数,因此您将看到 PHP 错误消息:

Object of class Product could not be converted to string.

PHP试图将对象转换为字符串,但这是不被允许的。

因此,你无法轻松地创建构造函数的回调函数,因为对象尚不存在。也许这就是为什么你没有能够在手册中轻松查找到它的原因。

在这里,构造函数需要进行一些特殊处理:如果你需要向一个未初始化的对象的类构造函数传递可变参数,你可以使用ReflectionClass来实现:

  $ref = new ReflectionClass($class);
  $new = $ref->newInstanceArgs($args);

请参考ReflectionClass::newInstanceArgs


谢谢您的回答,但是我没有收到“无效回调”错误,并且错误日志似乎表明call_user_func_array确实成功调用了构造函数,因为它给了我一个PHP警告:Product::__construct()缺少参数1,对于特定构造函数期望的五个参数中的每一个,但由于某种原因没有接收到。当然,这可能是我还不理解的东西,但那是我目前所拥有的知识。 - tuespetre
你首先使用 new $class 实例化对象。这意味着在 call_user_func_array 开始执行之前,构造函数已经被调用了。你明白吗?首先是 new,因为你将其作为参数中的表达式使用,然后才是函数调用。但由于 new 已经被执行,所以 $class 的构造函数已经被调用了(没有参数)。然后 PHP 尝试将对象转换为字符串,因为第一个参数需要一个字符串或数组。由于你的对象不支持转换为字符串,所以会出现错误/警告。 - hakre
我明白了!谢谢。我会研究其他动态传递参数给构造函数的方法。 - tuespetre
@tuespetre:请查看答案中的代码示例(末尾的两行代码),那就是你可以这样做的方式。 - hakre
我现在明白ReflectionClass的用法了。非常感谢你的回答,我现在可以做我需要做的事情了! - tuespetre

2

使用call_user_func_array()是不可能的,因为它(顾名思义)调用函数/方法,而不是创建对象,应使用ReflectionClass

$refClass = new ReflectionClass($class);
$object = $refClass->newInstanceArgs($args);

另一个(更注重设计的)解决方案是静态工厂方法。
class MyClass () {
  public static function create ($args) {
    return new self($args[0],$args[1],$args[2],$args[3],$args[4]);
  }
}

然后只需要这样做。
$object = $class::create($args);

在我看来,这样更清晰,因为少了不可思议的东西,更加可掌控。

你的意思是说,比如可以调用 Product::__construct($args),其中 $args 是一个参数数组?所有函数都可以使用这种参数数组来调用吗? - tuespetre
我在哪里说了那样的话?!?当然你可以使用数组实例化对象,但是然后你将会在构造函数中接收到该数组作为单个参数。这看起来不像是你想要的。 - KingCrunch
抱歉,我误读了你的帖子。 :) - tuespetre
ReflectionClass的方式更加简洁,因为你的方法无法扩展到超过4个参数。然而,你的方法在执行时间上会更快。 - demonkoryu
@demonkoryu 可能有人会认为,使用反射本身就是一种hack,但当然你是对的,每次签名更改时都需要特殊方法。然而,这些特殊方法非常常见,甚至有一个名称:工厂 :) 另一方面,答案是来自2011年,现在随着可变参数,创建通用工厂“东西”变得微不足道,如果你认为这是个好主意的话。 - KingCrunch

-1

我使用这个单例工厂模式,因为ReflectionClass破坏了依赖树,我讨厌使用eval,但这是我找到的简化单例模式使用的唯一方法,以注入MockObjects到PHPUnit中,而不必打开类方法进行注入。请注意eval函数传递的数据!!!你必须确保它已经被清理和过滤了!!!

abstract class Singleton{
   private static $instance=array();//collection of singleton objects instances
   protected function __construct(){}//to allow call to extended constructor only from dependence tree
   private function __clone(){}//to disallow duplicate
   private function __wakeup(){}//comment this if you want to mock the object whith php unit jejeje

   //AND HERE WE GO!!!
   public static function getInstance(){        
    $a=get_called_class();
    if(!array_key_exists($a, self::$instance)){ 
        if(func_num_args()){
          /**HERE IS THE CODE **//
            $args=func_get_args();
            $str='self::$instance[$a]=new $a(';
            for($i=0;$i<count($args);$i++){
                $str.=(($i)?",":"").'$args['.$i.']';
            }
            eval($str.");");//DANGER, BE CAREFULLY...we only use this code to inject MockObjects in testing...to another use you will use a normal method to configure the SingletonObject
          /*--------------------------*/
        }else{
            self::$instance[$a]=new $a();
        }

    }
    return self::$instance[$a];     
}


}

然后使用:

class MyClass extends Singleton{
    protected function __construct(MyDependInjection $injection){
      //here i use the args like a normal class but the method IS PROTECTED!!! 
  }
}

实例化对象的方法:

$myVar= MyClass::getInstance($objetFromClassMyDependInjection);

它调用了我传递的参数的构造函数。我知道通过扩展静态方法getInstance可以获得相同的结果,但对于团队合作来说,使用这种方式更容易。


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