2011-12-01 9 views
10

Estoy creando una función de envoltura alrededor de mysqli para que mi aplicación no tenga que ser demasiado complicada con el código de manejo de la base de datos. Parte de eso es un poco de código para parametrizar las llamadas SQL usando mysqli :: bind_param(). bind_param(), como ya sabrá, requiere referencias. Ya que es una envoltura semi general, termino haciendo esta llamada:Mi matriz de referencias de PHP se está "convirtiendo mágicamente" en una serie de valores ... ¿por qué?

call_user_func_array(array($stmt, 'bind_param'), $this->bindArgs); 

y me sale un mensaje de error:

Parameter 2 to mysqli_stmt::bind_param() expected to be a reference, value given 

La discusión anterior es que Forstall quienes digan "Usted don' Necesito referencias en su ejemplo ".

Mi código "real" es un poco más complicado de lo que nadie quiere leer, así que he hierve el código que conduce a este error en el siguiente (con suerte) ejemplo ilustrativo:

class myclass { 
    private $myarray = array(); 

    function setArray($vals) { 
    foreach ($vals as $key => &$value) { 
     $this->myarray[] =& $value; 
    } 
    $this->dumpArray(); 
    } 
    function dumpArray() { 
    var_dump($this->myarray); 
    } 
} 

function myfunc($vals) { 
    $obj = new myclass; 
    $obj->setArray($vals); 
    $obj->dumpArray(); 
} 

myfunc(array('key1' => 'val1', 
      'key2' => 'val2')); 

El problema parece ser que, en myfunc(), entre la llamada a setArray() y la llamada a dumpArray(), todos los elementos en $ obj-> myarray dejan de ser referencias y se convierten en valores. Esto se puede ver fácilmente mirando a la salida:

array(2) { 
    [0]=> 
    &string(4) "val1" 
    [1]=> 
    &string(4) "val2" 
} 
array(2) { 
    [0]=> 
    string(4) "val1" 
    [1]=> 
    string(4) "val2" 
} 

Tenga en cuenta que la matriz está en el estado "correcto" en la primera mitad de la producción aquí. Si tenía sentido hacerlo, podría hacer mi llamada bind_param() en ese punto, y funcionaría. Lamentablemente, algo se rompe en la segunda mitad de la salida. Tenga en cuenta la falta de "&" en los tipos de valor de matriz.

¿Qué pasó con mis referencias? ¿Cómo puedo evitar que esto suceda? Odio llamar "error de PHP" cuando realmente no soy un experto en idiomas, pero ¿podría ser este? Me parece muy extraño. Estoy usando PHP 5.3.8 para mis pruebas en este momento.


Editar:

Como más de una persona señaló, la solución es cambiar setArray() para aceptar su argumento por referencia:

function setArray(&$vals) { 

estoy añadiendo a esta nota documente POR QUÉ esto parece funcionar.

PHP en general, y mysqli en particular, parecen tener un concepto un tanto extraño de lo que es una "referencia". Observe este ejemplo:

$a = "foo"; 
$b = array(&$a); 
$c = array(&$a); 
var_dump($b); 
var_dump($c); 

En primer lugar, estoy seguro de que se esté preguntando por qué estoy usando matrices en lugar de variables escalares - es porque var_dump() no muestra ninguna indicación de si un escalar es una referencia, pero lo hace para los miembros de la matriz.

De todos modos, en este punto, $ b [0] y $ c [0] son ​​ambas referencias a $ a. Hasta aquí todo bien. Ahora tiramos nuestra primera llave en las obras:

unset($a); 
var_dump($b); 
var_dump($c); 

$ b [0] y $ c [0] se sigue manteniendo referencias a la misma cosa. Si cambiamos uno, ambos seguirán cambiando. Pero, ¿a qué se refieren? Alguna ubicación sin nombre en la memoria. Por supuesto, la recolección de basura asegura que nuestros datos estén seguros, y lo seguirá siendo, hasta que dejemos de referirnos a ellos.

Para nuestro siguiente truco, hacemos esto:

unset($b); 
var_dump($c); 

Ahora $ c [0] es la única referencia que queda a nuestros datos. Y, whoa! Mágicamente, ya no es una "referencia". No por la medida de var_dump(), y tampoco por la medida de mysqli :: bind_param().

El PHP manual dice que hay una marca separada, 'is_ref' en cada dato. Sin embargo, esta prueba parece sugerir que 'is_ref' es en realidad equivalente a '(refcount> 1)'

Para divertirse, puede modificar este ejemplo juguete de la siguiente manera:

$a = array("foo"); 
$b = array(&$a[0]); 
$c = array(&$a[0]); 

var_dump($a); 
var_dump($b); 
var_dump($c); 

Tenga en cuenta que los tres las matrices tienen la marca de referencia en sus miembros, lo que respalda la idea de que 'is_ref' es funcionalmente equivalente a '(refcount> 1)'.

Me supera el hecho de que mysqli :: bind_param() se preocupe por esta distinción en primer lugar (o quizás sea call_user_func_array() ... de cualquier forma), pero parece que lo que "realmente" necesitamos asegurar es que el recuento de referencias es al menos para cada miembro de $ this-> bindArgs en nuestra llamada a call_user_func_array() (vea el comienzo de la publicación/pregunta). Y la forma más fácil de hacerlo (en este caso) es hacer que setArray() pase por referencia.


Editar:

Para la diversión y juegos extra, he modificado mi programa original (no se muestra aquí) para dejar su equivalente a setArray() pasan por valor, y para crear una matriz adicional gratuita , bindArgsCopy, que contiene exactamente lo mismo que bindArgs. Lo que significa que, sí, ambas matrices contenían referencias a datos "temporales" que fueron desasignados por el momento de la segunda llamada. Según lo predicho por el análisis anterior, esto funcionó. Esto demuestra que el análisis anterior no es un artefacto del funcionamiento interno de var_dump(), al menos, un alivio para mí, y también demuestra que es el recuento de referencias lo que importa, no la "temporalidad" del original. almacenamiento de datos.

So. Hago la siguiente afirmación: en PHP, para call_user_func_array() (y probablemente más), decir que un elemento de datos es una "referencia" es lo mismo que decir que el recuento de referencias del elemento es mayor o igual que 2 (ignorando las optimizaciones de memoria interna de PHP para escalares de igual valorada)


nota Administrivialidades: me encantaría darle Mario el crédito sitio para la respuesta, ya que fue el primero en sugerir la respuesta correcta, pero ya que él lo escribió en un comentario, no una respuesta real, no podría hacerlo :-(

+1

Wouldn' t el método 'setArray()' también necesita obtener la matriz como parámetro de referencia? De lo contrario, solo está creando referencias a una matriz temporal. – mario

+0

@mario: Usted, señor, tiene toda la razón, ya que esto soluciona mi problema. Lo cual plantea la pregunta ... _¿Por qué soluciona esto el problema? Esperaría que una referencia a una matriz temporal siga siendo una referencia, solo una referencia a algo que solo se mantiene en la memoria debido a la referencia misma. Supongo que debería leer sobre la administración de la memoria PHP, si puedo encontrar un documento de este tipo. –

+0

Sí, put & $ vals en la función setArray – malletjo

Respuesta

4

pasar la matriz como referencia:

function setArray(&$vals) { 
    foreach ($vals as $key => &$value) { 
     $this->myarray[] =& $value; 
    } 
    $this->dumpArray(); 
    } 

Mi suposición (que podría ser incorrecta en algunos detalles, pero afortunadamente es correcta en su mayor parte) en cuanto a por qué esto hace que su código funcione como se espera es que cuando pasa como un valor, todo está bien para la llamada al dumpArray() de setArray() porque todavía existe la referencia a la matriz $vals dentro de setArray(). Pero cuando el control vuelve a myfunc(), esa variable temporal ya no está, como todas las referencias a ella. Así que PHP obedientemente cambia la matriz a valores de cadena en lugar de referencias antes de desasignar la memoria para ello.Pero si lo pasa como referencia desde myfunc(), entonces setArray() está utilizando referencias a una matriz que permanece activa cuando el control vuelve al myfunc().

2

Añadiendo el & en la firma del argumento lo solucioné para mí. Esto significa que la función recibirá la dirección de memoria de la matriz original.

function setArray(&$vals) { 
// ... 
} 

CodePad.

0

Acabo de encontrar el mismo problema en casi exactamente la misma situación (estoy escribiendo un contenedor PDO para mis propios fines). Sospeché que PHP estaba cambiando la referencia a un valor una vez que ninguna otra variable hacía referencia a los mismos datos, y Rick, sus comentarios en la edición de su pregunta confirmaron esa sospecha, así que gracias por eso.

Ahora, para cualquier otra persona que viene a ser algo parecido a esto, creo que tengo la solución más simple: simplemente establecer cada elemento de la matriz correspondiente a una referencia a sí mismo antes de pasar a la matriz call_user_func_array().

No estoy seguro de qué sucede internamente porque parece que no debería funcionar, pero el elemento vuelve a ser una referencia (que se puede ver con var_dump()), y call_user_func_array() pasa el argumento por referencia como se esperaba Esta solución parece funcionar incluso si el elemento de la matriz todavía es una referencia, por lo que no es necesario que primero verifique.

En el código de Rick, sería algo como esto (todo después de que el primer argumento para bind_param es por referencia, así que no tome la primera y solucionar todos los queridos después de que):

for ($i = 1, $count = count($this->bindArgs); $i < $count; $i++) { 
    $this->bindArgs[$i] = &$this->bindArgs[$i]; 
} 

call_user_func_array(array($stmt, 'bind_param'), $this->bindArgs); 
Cuestiones relacionadas