Call_user_func_array передает аргументы конструктору

Я искал много страниц результатов Google, а также здесь, в stackoverflow, но не могу найти решение, которое, похоже, соответствует моей ситуации. Кажется, у меня есть одна последняя загвоздка в функции, которую я пытаюсь построить, которая использует call_user_func_array для динамического создания объектов.

Увлекательная фатальная ошибка, которую я получаю, составляет Object of class Product could not be converted to string. Когда ошибка возникает, в журнале я получаю пять из них (по одному для каждого аргумента): 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 один раз, а затем некоторые, и примеры на этой странице, казалось, показывали людям просто создание массива и вызов его для второго аргумента. Что я могу делать неправильно?

Ответ 1

Вы не можете вызвать конструктор $class следующим образом:

call_user_func_array (new $class, $args);

Это no действительный обратный вызов в качестве первого параметра. Выделите это отдельно:

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 пытается преобразовать объект в строку, который он не позволяет.

Итак, как вы можете видеть, вы не можете легко создать обратный вызов для конструктора, поскольку объект еще не существует. Возможно, именно поэтому вы не смогли легко найти его в руководстве.

Конструкторам нужна специальная работа здесь: если вам нужно передать переменные аргументы конструктору класса объекта not-yet initialize, вы можете использовать ReflectionClass для этого:

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

См. ReflectionClass::newInstanceArgs

Ответ 2

Невозможно использовать call_user_func_array(), потому что (как следует из названия) он вызывает функции/методы, но не предназначен для создания объектов, используйте ReflectionClass

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

Другим (более основанным на дизайне) решением является статический метод factory

class MyClass () {
  public static function create ($args) {
    return new self($args[0],$args[1],$args[2],$args[3],$args[4]);
  }
}

а затем просто

$object = $class::create($args);

В моих глазах это чище, потому что меньше магии и большего контроля

Ответ 3

Я использую это для одноэлементного шаблона factory, потому что ReflectionClass ломает дерево зависимостей, я ненавижу использование eval, но это единственный способ найти упрощение использования шаблона singleton для ввода 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, но для совместной работы его более простой в использовании.