2011-11-05 25 views
10

He buscado muchas páginas de resultados de Google, así como aquí en stackoverflow pero no puedo encontrar una solución que parezca ajustarse a mi situación. Parece que tengo un último inconveniente en la función que intento construir, que usa call_user_func_array para crear objetos dinámicamente.call_user_func_array pasando argumentos a un constructor

El error fatal capturable que recibo es Object of class Product could not be converted to string. Cuando se produce el error, en el registro obtengo cinco de estos (uno para cada argumento): PHP Warning: Missing argument 1 for Product::__construct(), antes del error fatal capturable.

Este es el código de la función:

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; 
} 

Ahora, si me comente la línea call_user_func_array y sustituirla por la siguiente:

$array[] = new $class($args[0],$args[1],$args[2],$args[3],$args[4]); 

Las cargas de la página como debe ser, y puebla la mesa que estoy construyendo Así que todo está completamente funcional hasta que intente usar realmente mi matriz $args dentro de call_user_func_array.

¿Hay algún detalle sutil acerca de llamar a esa matriz que me falta? Leí el manual de PHP para call_user_func_array una vez, y luego algunos, y los ejemplos en esa página parecían mostrar a la gente simplemente construyendo una matriz y llamándola para el segundo argumento. ¿Qué podría estar haciendo mal?

Respuesta

20

No se puede llamar al constructor de $class así:

call_user_func_array (new $class, $args); 

Eso no es valid callback como primer parámetro. Vamos a escoger este aparte:

call_user_func_array (new $class, $args); 

es la misma que

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

Como se puede ver, el constructor de $class ya ha sido llamado antes call_user_func_array entra en acción. Ya que no tiene parámetros, vea este mensaje de error:

Missing argument 1 for Product::__construct() 

Además, $obj es del tipo de objeto. Una devolución de llamada válida debe ser una cadena o una matriz (o excepcionalmente un objeto muy especial: Closure, pero eso está fuera de discusión aquí, solo lo nombro como completo).

Como $obj es un objeto y no una devolución de llamada válida, por lo que ver el mensaje de error de PHP:

Object of class Product could not be converted to string. 

PHP intenta convertir el objeto en cadena, que no permite.

Como puede ver, no puede crear fácilmente una devolución de llamada para un constructor, ya que el objeto aún no existe. Tal vez es por eso que no fue capaz de buscarlo en el manual fácilmente.

constructores necesitan algún trato especial aquí: Si tiene que pasar argumentos variables a un constructor de la clase de un objeto aún no inicializar, puede utilizar la ReflectionClass para hacer esto:

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

Ver ReflectionClass::newInstanceArgs

+0

Gracias por su respuesta, sin embargo, no obtengo un error de "no devolución de llamada válida", y el error log aparentemente indica que 'call_user_func_array' llama con éxito al constructo, porque me da un' PHP Warning: Falta el argumento 1 para Product :: __ construct() 'para cada uno de los cinco argumentos que el constructor en particular espera, pero por alguna razón no recepción. Por supuesto, puede ser algo que aún no entiendo, pero ese es el conocimiento que poseo hasta ahora. – tuespetre

+0

Primero crea una instancia del objeto con 'new $ class'. Eso significa que el constructor ya ha sido llamado antes de que 'call_user_func_array' entre en acción. ¿Lo ves? Primero viene 'new' porque lo usa como una expresión en el parámetro, luego ocurre la llamada a la función. Pero debido a la nueva ejecución, el constructor de '$ class' ya está llamado (sin argumentos). Y luego PHP intenta convertir el objeto en una cadena porque el primer parámetro espera una cadena o matriz. Debido a que su objeto no admite el envío a cadenas, obtiene el error/advertencia. – hakre

+0

¡Ya veo! Gracias. Buscaré otras formas de pasar dinámicamente argumentos a un constructor. – tuespetre

2

no es posible utilizando call_user_func_array(), porque (como el nombre sugiere) que llama a funciones/métodos, pero no tiene la intención de crear objetos, uso ReflectionClass

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

Otro (más basado en el diseño) solución es un método de fábrica estática

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

y luego simplemente

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

En mis ojos Es más limpio, ya que menos magia y más control

+0

¿Quiere decir que puede llamar, por ejemplo, 'Product :: __ construct ($ args)' donde '$ args' es una matriz de argumentos? ¿Se pueden llamar todas las funciones usando una matriz de argumentos como esa? – tuespetre

+0

¿Dónde dije algo así?!? Por supuesto, puede instanciar objetos con matrices, pero luego recibirá esa matriz como un único argumento en el constructor.Parece que no es lo que quieres – KingCrunch

+0

Lo siento, he leído mal tu publicación. :) – tuespetre

0

I Úselo para el patrón de fábrica singleton, porque ReflectionClass rompe el árbol de dependencia, odio el uso de eval pero es la única manera de encontrar simplificar el uso del patrón singleton para inyectar mockObjects con PHPUnit whi Tout abrir los métodos de clase para esa inyección, ¡TEN CUIDADO CON LOS DATOS LO QUE PASAS PARA EVALUAR LA FUNCIÓN !!!!!!!! ¡¡DEBE ESTAR SEGURO DE QUE ESTÉ LIMPIO Y FILTRADO !!!

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];  
} 


} 

y de utilizar que:

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

a instanciar el objeto:

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

se llama al constructor un poco con los argumentos que pased. Sé que puedo obtener el mismo resultado extendiendo el método estático getInstance, pero trabajar en equipo es más fácil de usar de esta manera

Cuestiones relacionadas