2011-12-01 14 views
10

Tworzę funkcję otoki wokół mysqli, więc moja aplikacja nie musi być zbyt skomplikowana z kodem obsługi bazy danych. Częścią tego jest trochę kodu do sparametryzowania wywołań SQL za pomocą mysqli :: bind_param(). bind_param(), jak być może wiesz, wymaga referencji. Ponieważ jest to pół-ogólnie wrapper, ja skończyć podejmowania tego połączenia:Moja tablica referencyjna PHP jest "magicznie" stająca się zbiorem wartości ... dlaczego?

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

i pojawia się komunikat o błędzie:

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

Powyższa dyskusja jest Forstall tych, którzy mówią „don” t w twoim przykładzie potrzebują referencji ".

Mój „prawdziwy” Kod jest nieco bardziej skomplikowana, niż ktoś chce czytać, więc ja gotuje kod prowadzące do tego błędu w następującym (miejmy nadzieję) ilustrującego przykładu:

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')); 

Problem wydaje się, że w myfunc(), pomiędzy wywołaniem setArray() a wywołaniem dumpArray(), wszystkie elementy w $ obj-> myarray przestają być referencjami i stają się zamiast nich tylko wartościami. Można to łatwo zauważyć patrząc na wyjściu:

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

Zauważ, że tablica jest w „prawidłowym” państwa w pierwszej połowie wyjściu tutaj. Jeśli miałoby to sens, mógłbym wywołać moje wywołanie bind_param() w tym miejscu i to działałoby. Niestety coś się psuje w drugiej połowie produkcji. Zwróć uwagę na brak "&" dla typów wartości macierzy.

Co stało się z moimi referencjami? Jak mogę temu zapobiec? Nienawidzę nazywać "PHP bug", kiedy naprawdę nie jestem ekspertem od języków, ale czy to może być jeden? Wydaje mi się to bardzo dziwne. Używam PHP 5.3.8 do moich testów w tej chwili.


Edit:

jako więcej niż jedna osoba wskazała, poprawka jest zmiana setArray(), aby zaakceptować swój argument przez odniesienie:

function setArray(&$vals) { 

Dodaję tę notatkę do dokument DLACZEGO to wydaje się działać.

PHP ogólnie, a mysqli w szczególności, wydaje się mieć nieco dziwną koncepcję tego, czym jest "referencja". Obserwuj ten przykład:

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

Przede wszystkim, jestem pewien, że zastanawiasz się, dlaczego używam zamiast tablic zmiennych skalarnych - to dlatego var_dump() nie wykazały żadnego wskazania, czy skalara jest referencją, ale dotyczy członków tablicy.

W każdym razie, $ b [0] i $ c [0] oznaczają odniesienia do $ a. Jak na razie dobrze. Teraz rzucamy nasz pierwszy klucz do prac:

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

$ b [0] i $ c [0] są zarówno nadal odwołuje się do tego samego. Jeśli zmienimy jeden, oba będą się zmieniać. Ale do czego one się odnoszą? Pewna nienazwana lokalizacja w pamięci. Oczywiście, zbieranie śmieci zapewnia, że ​​nasze dane są bezpieczne i tak pozostaną, dopóki nie przestaniemy się z nimi zapoznać.

naszej kolejnej sztuczki, możemy to zrobić:

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

Teraz $ c [0] to jedyne odniesienie do naszych danych. I, whoa! Magicznie, to już nie jest "odniesienie". Nie według miary var_dump(), a nie przez miarę mysqli :: bind_param().

Urządzenie PHP manual mówi, że na każdym elemencie danych znajduje się osobna flaga "is_ref". Jednak badanie to wydaje się sugerować, że „is_ref” jest rzeczywiście równoważne „(RefCount> 1)”

Dla zabawy, które można modyfikować ten przykład zabawki następująco:

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

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

Zauważ, że wszystkie trzy Macierze mają znak odniesienia na swoich elementach, który potwierdza, że ​​"is_ref" jest funkcjonalnie równoważne "(refcount> 1)".

To mnie przerasta, dlaczego mysqli :: bind_param() zadba o to rozróżnienie w pierwszej kolejności (lub może to call_user_func_array() ... tak czy inaczej), ale wydaje się, że to, co "naprawdę" musimy zapewnić jest to, że liczba referencyjna wynosi co najmniej dla każdego członka $ this-> bindArgs w naszym wywołaniu call_user_func_array() (patrz sam początek post/pytanie). Najłatwiejszym sposobem na zrobienie tego (w tym przypadku) jest przekazanie polecenia setArray().


Edit:

Dla dodatkowej zabawy i gry, zmodyfikowałem mój oryginalny program (nie pokazaną tutaj), aby opuścić swój odpowiednik setArray() przechodzi przez wartość, a do stworzenia nieuzasadnioną dodatkową tablicę , bindArgsCopy, zawierające dokładnie to samo co bindArgs. Co oznacza, że ​​tak, obie tablice zawierają odniesienia do "tymczasowych" danych, które zostały zdekalokowane do czasu drugiego połączenia. Jak przewidywano w powyższej analizie, zadziałało to. To pokazuje, że powyższa analiza nie jest artefaktem wewnętrznych działań var_dump() (przynajmniej ulga), a także pokazuje, że liczy się licznik referencji, a nie "tymczasowość" oryginału przechowywanie danych.

So. Wykonuję następujące stwierdzenie: W PHP, dla funkcji call_user_func_array() (i prawdopodobnie więcej), powiedzenie, że element danych jest "referencją", to to samo, co powiedzenie, że liczba referencyjna elementu jest większa lub równa 2 (ignorując wewnętrzne optymalizacje pamięci PHP dla równych wartościach skalarnych)


Administrivia uwaga: Chciałbym dać Mario kredytu strony na odpowiedź, jak był pierwszy zaproponować prawidłową odpowiedź, ale ponieważ on napisał w komentarzu, a nie rzeczywiste odpowiedź, nie mogłem zrobić :-(

+1

Wouldn” • Metoda 'setArray()' również musi pobrać tablicę jako parametr odniesienia? Inaczej tworzysz odniesienia do tymczasowej tablicy. – mario

+0

@mario: Pan, sir, ma absolutną rację, ponieważ to rozwiązuje mój problem. Co nasuwa pytanie ... _Dlaczego_ to rozwiązuje problem? Spodziewam się, że odniesienie do tymczasowej tablicy nadal będzie punktem odniesienia, a jedynie odniesieniem do czegoś, co jest przechowywane tylko w pamięci z powodu samego odniesienia. Przypuszczam, że powinienem czytać na temat zarządzania pamięcią PHP, jeśli uda mi się znaleźć taki dokument. –

+0

Tak, put & $ vals w funkcji setArray – malletjo

Odpowiedz

4

Mijamy tablicę jako odniesienie:

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

Moje przypuszczenie (które może być błędne w niektórych szczegółach, ale jest to, miejmy nadzieję, poprawne w przeważającej części), dlaczego powoduje to, że twój kod działa zgodnie z oczekiwaniami, jest taki, że gdy przechodzisz jako wartość, wszystko jest w porządku dla połączenia z dumpArray() wewnątrz z setArray(), ponieważ odwołanie do tablicy $vals wewnątrz setArray() nadal istnieje. Ale gdy kontrola powróci do myfunc(), ta zmienna tymczasowa zniknie, tak jak wszystkie odniesienia do niej. Zatem PHP sumiennie zmienia tablicę na wartości łańcuchów zamiast odniesień przed zwolnieniem pamięci dla niego.Ale jeśli przekażesz to jako odniesienie od myfunc(), to setArray() używa odwołań do tablicy, która żyje, kiedy kontrola powraca do myfunc().

2

Dodanie & w sygnaturze argumentu naprawiło to za mnie. Oznacza to, że funkcja otrzyma adres pamięci oryginalnej tablicy.

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

CodePad.

0

Po prostu napotkałem ten sam problem niemal dokładnie w tej samej sytuacji (piszę opakowanie PDO do własnych celów). Podejrzewałem, że PHP zmienia odniesienie do wartości, gdy żadne inne zmienne nie odwoływały się do tych samych danych, a Rick, twoje komentarze w edycji na twoje pytanie potwierdziły to podejrzenie, więc dziękuję ci za to.

Teraz, dla nikogo innego, kto przychodzi na coś tak, wierzę, mam najprostsze rozwiązanie: prostu ustawić każdy odpowiedni element tablicy do odniesienia do siebie przed przekazaniem tablicę call_user_func_array().

Nie jestem do końca pewien, co dzieje się wewnętrznie, ponieważ wydaje się, że to nie powinno działać, ale element ponownie staje się odniesieniem (które można zobaczyć za pomocą var_dump()), a call_user_func_array() przekazuje argument za pomocą odniesienie zgodnie z oczekiwaniami. Ta poprawka wydaje się działać, nawet jeśli element tablicy jest już referencją, więc nie musisz najpierw sprawdzać.

W kodzie Ricka, byłoby mniej więcej tak (wszystko po pierwszym argumentem dla bind_param jest odniesienie, więc pominąć pierwszy i naprawić wszystkie te, które po tym):

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); 
Powiązane problemy