Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Referencje do obiektu
Forum PHP.pl > Forum > PHP
Skie
Witam,
mam problem z referencjami , z którymi nie mogę sobie poradzć (PHP 5.4).
Napisałem sobie klasę cache'ującą, która zbiera referencje do pewnych obiektów i trzyma je w odpowiedniej strukturze.
W przypadku, gdy pojawiają się w niej referencje do tego samego obiektu zaczynają się schodki, które wydają się być błędem związanym z PHP niż z moją klasą.

Zobrazuję to w poniższym przykładzie.
  1. $man = new StashManager();
  2.  
  3. $obj = new StdClass;
  4. $obj -> id = 1;
  5. $obj -> x = 5;
  6. $obj -> y = 5;
  7. $man -> InsertInto("key1", $obj); // zapisuje w kontenerze pod kluczem key1 referencję do obiektu1
  8. $man -> InsertInto("key2", $obj); // zapisuje w kontenerze pod kluczem key2 referencje do obiektu1
  9. unset($obj); // czyszczę zmienną
  10.  
  11. $obj = &$man -> Select("key2"); // pobieram referencję do obiektu1


I teraz tak , jeżęli zmienię jakaś składową, np:
  1. $obj -> x = 10;


Po czym wyświetlę stan kontenera - obiekt key1 i key2 mają x ustawione na 10 - w końcu to ten sam obiekt.
Teraz chcę zamiast tego usunąć zawartość tej refrencji, robię więc:
  1. $obj = null;


Odczytuje dane i.... Tylko obietk spod key2 jest nullem. Obiekt spod key1 został niezmieniony.


Problemem okazuje się być funkcja pobierająca miejsce, wk tórym ma być wpisany obiekt.
Gdy wpisuję do kontenera ręcznie wartość:
  1. $this -> stash[...] = &$obj;


Wszystko działa w porządku. Jednak, gdy robię to pobierająć dokładne miejsce w kontenerze za pomocą funkcji:
  1.  
  2. public function &GetPlace($path, $breakOnError = false, $offset = 0) {
  3. $arr = explode('/',$path);
  4. $place = &$this -> stash;
  5. $limit = count($arr);
  6.  
  7. $i = 0;
  8. foreach ($arr as $el) {
  9. if ($i-$offset >= $limit) { break; }
  10.  
  11. if (($child = &$this -> GetChild($place, $el)) === null) {
  12. if ($breakOnError) { $empty = null; return $empty; } else { $obj = array(); $this -> SetChild($place, $el, $obj); unset($obj); }
  13. }
  14. $place = &$this -> GetChild($place, $el);
  15.  
  16. $i++;
  17. }
  18.  
  19. return $place;
  20. }
  21.  
  22. public function &GetChild(&$place, $el) {
  23. if (is_array($place) && isset($place[$el])) {
  24. return $place[$el];
  25. } else if (is_object($place) && isset($place -> $el)) {
  26. return $place -> $el;
  27. } else {
  28. $empty = null;
  29. return $empty;
  30. }
  31. }


Co robię źle?

Co robię źle?
rocktech.pl
Witam.

1. Włącz raportowanie błędów



dowiesz się, ze tylko zmienne powinny być przekazywane przez referencje.

2. Pobierasz referencje do obiektu ...

  1. $this -> stash[...] = &$obj;


Cytat
Po czym wyświetlę stan kontenera - obiekt key1 i key2 mają x ustawione na 10 - w końcu to ten sam obiekt.
Teraz chcę zamiast tego usunąć zawartość tej refrencji, robię więc:
[PHP]

$obj = null;
[PHP]


Odczytuje dane i.... Tylko obietk spod key2 jest nullem. Obiekt spod key1 został niezmieniony.


Unsetujesz $obj tym samym odbindowujesz referencje ale $this->statsh[key] zostaje --> http://pl1.php.net/manual/en/language.references.unset.php
Skie
1. Mam ustawione raportowanie błędów E_ALL i nic mi nie wyrzuca w związku z moją klasą. Poza tym, jeżeli nie mógłbym skorzystać z referencji do obiektu to całość traci sens, bo potrzebuję , by klasa trzymała oryginalne obiekty, a nie ich kopie.

2. Tak, dwie zmienne w kontenerze powinny zostać, po wykonaniu operacji $obj = null, ale obie powinny być ustawione na null, co pozwala klasie na pełne funkcjonowanie. unset($obj) od $obj = null w przypadku jeżeli jest to referencja nie działa tak samo jak w przypadku normalnej zmiennej. Pierwsze rzeczywiście odbindowuje referencję, ale drugie już zmienia wartość oryginalnego obiektu na null.

Dodatkowo dodam, że:
  1. $obj1 = new StdClass;
  2. $obj1-> x = 5; // obj1 -> x == 5
  3. $obj2 = &$obj1;
  4. $obj2 -> x = 10; // obj1 -> x == 10
  5. $obj2 = null; // $obj1 == null && $obj2 == null


Działa tak jak powinno

Potrzebuję takiej samej możliwości u siebie.
freemp3
Nie za dużo tych referencji?
  1. $place = &$this -> stash;

Jak rozumiem jest tablicą - elementem klasy, w której trzymane są wszystkie obiekty, więc w jakim celu potrzebujesz tu referencji?

  1. public function &GetChild(&$place, $el);
  2. $place = &$this -> GetChild($place, $el);

Funkcja zwraca referencje, a Ty jeszcze do zmiennej place przypisujesz referencje do referencji smile.gif Możliwe, że gdzieś zamieszałeś z referencjami i dlatego jest taki problem.
Crozin
1. http://php.net/manual/en/language.oop5.references.php - masz złe wyobrażenie co do sposobu działania kodu.
2. Mógłbyś napisać jak chciałbyś by ten kod działał i ewentualnie dlaczego tak?
Skie
freemp3

1. W pierwszym przypadku wpisałem:
  1. $place = &$this -> stash;


Dlatego, że jak potem widzisz, we wnętrzu funkcji Place, co iterację, funckja sprawdza, czy dana składowa istnieje w obecnym zagnieżdżeniu , innymi słowy, jak chcę dodać obiekt pod ścieżkę załóżmy /node1/node2/2, to zadaniem funkcji GetPlace() jest dobrać się do:
$this -> stash -> node1 -> node2 -> 2

I oczywiście zwrócić referencję do tego, by można było oryginalną zmienną z tego adresu zmienić / usunąć / zastąpić etc.
Chętnie bym to zrobił inaczej, ale nie znająć ilości zagłębień, nie wiem jak mógłbym to przeiterować bez referencji. Wszelkie sugestie mile widziane.

2. Co do:
  1. public function &GetChild(&$place, $el);
  2. $place = &$this -> GetChild($place, $el);


Musi tak być bo inaczej nie zadziała. Referencje w PHPie są dość dziwne i w pierwszym przypadku zwrócenie referencji mówi, żeby "zwrócić oryginalną zmienną", która jest w return a nie jej kopii.
Ale mimo to, jeżeli dobiorę się do niej w ten sposób:
  1. $place = $this -> GetChild($place, $el); // bez referencji


To mimo, że funkcja zwraca oryginalny obiekt, to operator = bez referencji skopiuje go do $place i powiązanie zostanie utracone.

Np:
  1. function &Test(&$obj) {
  2. return $obj;
  3. }
  4.  
  5. $obj = new StdClass;
  6. $obj -> val = 5;
  7. $tst = &Test($obj);
  8. $tst -> val = 10; // obj -> val == 10
  9. $tst = null; // obj == null
  10. unset($obj);
  11. unset($tst);
  12.  
  13. /* bez ref */
  14. $obj = new StdClass;
  15. $obj -> val = 5;
  16. $tst = Test($obj);
  17. $tst -> val = 10; // obj -> val == 10
  18. $tst = null; // obj == StdClass ( x => 10 )



Crozin:

Dlaczego mam złe wyobrażenia na temat działania kodu? Linkujesz strony manuala dobrze mi znane, więc nie pomaga mi to w zrozumieniu czego nie rozumiem smile.gif
Jeżeli masz na myśli trochę odbiegający sposób tłumaczenia mojego kodu w postach powyżej to wszyskto pisałem w cudzysłowach , właśnie po to, że nie dokładnei to tak działa , ale "mozna tak powiedzieć" dla uproszczenia sprawy.
Chyba, że masz na myśli jeszcze coś innego?

Klasa ma być używana do cache'owania nabieżąco obiektów z możliwością zagnieżdżania struktur. Innymi słowy mam gdzieś w kodzie obiekt, który chcę cache'ować, więc dodaję go do kontenere pod adres /node1/node2/2

  1. $this -> InsertInto("node1/node2/2", $obj);


Załóżmy, że potrzebuję go potem używać w innym miejscu , więc sprawdzam:

  1. if ($this -> Exists("node1/node2/2")) {
  2. $obj = &$this -> Select("node1/node2/2");
  3. } else {
  4. // obiekt nie istnieje w cache, więc wykonaj kosztowne operacje by go pobrać/utworzyć
  5. }


I teraz tak - jeżeli zmienię zawartość $obj - chcę by uległ on zmianie również w kontenerze. Dlatego korzystam z referencji.
Dopuszczam, możliwość, że ten sam obiekt w kontenerze może istnieć pod różnymi ścieżkami (dla szybszego odczytu).

Zależy mi by było to w pełni "flexible", więc np. poniższy kod:
[php]
$obj = &$this -> Select("node1/node2");
[php]

Też powinien zadziałać i zwrócić:
Array (
[2] => Object() // tutaj znajduje się wcześniej wpisany $obj;
)

Nie tworzę w klasie rozbudowanych struktur do trzymania tego, tylko wszystko wykonuję poprzez referencje na samym $this -> stash właśnie po to, by takie operacje były możliwe, bez komplikowania kodu.

W obecnej formie wszystko działa oprócz usuwania obiektu z kontenera (zostaje usunięty tylko z podanej ścieżki, a pozostałe referencje do niego w kontenerze w magiczny sposób się kopiują, jak podałem w pierwszym poście).
freemp3
Chyba trochę za bardzo kombinujesz. Czy nie prościej było by w przypadku obiektów zastosowanie singletonów i rejestrów?
W przypadku zwykłych zmiennych stworzyć klasę, która będzie po prostu trzymała wartości i w razie usunięcia zmiennej będziesz czyścił również cache? Trzymanie tej samej zmiennej pod różnymi kluczami jest trochę dziwnym pomysłem i pierwszy raz się z czymś takim spotykam. Mógłbyś podać jakiś praktyczny przykład?

Crozin
Link do manuala podałem, ponieważ widzę jak ciągle używasz referencji przy obiektach (co właściwie nie ma sensu) i dziwisz się, że usunięcie obiektu z kontenera nie powoduje jego usunięcia z innych miejsc. Obiekty w PHP przekazywane są przez coś na kształt niejawnych wskaźników, więc ani nie dochodzi do kopiowania obiektów, ani nie ma użytych żadnych referencji. Ty tworzysz jedynie referencje do "wskaźników". Nie mniej jednak być może słabe referencje są tym czego szukasz.
Skie
freemp3
Nie do końca mógłbym tak zrobić, bo obiekty które trzymam w klasie mogą być obiektami, zmiennymi lub tablicami. W każdym razie, jeżeli nie znajdę rozwiązania, będę musiał przemyśleć to zaproponowane przez Ciebie.

Praktyczny przykład trzymania wskaźnika do jednego i tego samego obiektu pod paroma kluczami , będącymi unikalnymi identyfikatorami jest taki, że przyśpiesza to znacząco wyszukiwanie danych. Przy rozbudowanej stukturze może istnieć potrzeba pobierania danych na wiele sposób. Porównując to do bazy danych, brak możliwośc definiowania wielu kluczy do jednej wartości byłoby ak jakby umożliwić wyszukiwanie rekrodów tylko po ID.

Crozin
PHP nie do końca przekazuje obiekty jako referencje. Tzn twierdzenie, że tak robi jest nie do końca prawdziwe, bo zachowanie w ten sposób przekazanych obiektów nieraz różni się od oczekiwanego. Nie będę tutaj się rozwodził na ten temat, bo jest pełno rozmów na zagranicznych forach zagłębiająćych się w mechanikę PHP i czy domyślne referencje, rzeczywiście nimi są.
Na pewno rpzeczytam o weakref i wieczorem lub jutro napiszę co zrobiłem w kwestii tej klasy.

Generalnie mam wrażenie, że za dużo C++ pcham do PHP. Być może ten moduł aplikacji (tj cacheowanie) w ogóle będzie wygodniej napisać w C++ smile.gif
Crozin
Przecież wyraźnie napisałem, że obiekty nie są przekazywane przez referencje.

1. W przypadku obiektów nie ma potrzeby korzystania z referencji. Przy zwykłym przypisaniu dochodzi jedynie do skopiowania "wskaźnika", nie samego obiektu.
2. Jeżeli chcesz by w przypadku usunięcia pierwotnego obiektu skasowane zostały również ten widniejące w cache'u skorzystaj ze wspomnianych przeze mnie wcześniej słabych referencji.
3. Jeżeli chcesz by w przypadku usunięcia obiektu z cache'a skasowany został również "oryginał"... nie rób tego w żadnym wypadku!
4. Sam fakt, że chcesz osiągnąć coś takiego sugeruje, że coś źle sobie zaplanowałeś. Dlaczego nie możesz robić najzwyklejszych przypisań i pozwolić GC-owi zająć się usuwaniem śmieci?
Skie
1. Identyfikator obiektu != referencja obiektu.
3. i 4. Widać, że nie do końca dobrze wytłumaczyłem problem, gdyż nie do końca o to mi chodzi.

Czytałem o Weakref, jest to ciekawa rzecz, ale nie do końca jestem do niej przekonany, gdyż jest to pewna abstrakcja nie pozwalająca na manipulację pamięcią a la C++ cz Assembler, tylko jakby to nazwać... brak oznaczenia, że dany fragment pamięci jest chroniony od strony kodu? Nie wiem jak to lepiej ująć. Boję się, że coś takiego może doprowadzić do zaistnienia dziur w systemie.

W każdym razie, zmieniłem ciut założenia. Select() i InsertInto() będą od tej chwili klonowały wczytany obiekt, a problematykę istnienia wielu kluczy do jednego obiektu rozwiążę dodając mały mechanizm tworzenia w klasie linków symbolicznych, gdzie jeden klucz nie ma wartości, tylko "wskazuje" na inny klucz. Select() , Delete() i Truncate() pozostaną bez zmian.

Minusem tego rozwiązania jest brak kompatybilności wstecznej względem starego systemu, za co pozostali deweloperzy pewnie mnie powieszą, ale... nie da się tego zrobić w PHP tak by działało w 100% po staremu. A myślę, że w parę dni, doprowadzimy system do używalności z tą klasą, bo zmiany są minimalne.

Lecą plusy za owocną konwersacje i propozycje. Dzięki.
Crozin
Szczerze powiedziawszy coraz bardziej gubię się w Twoich oczekiwaniach względem kodu. W przypadku obiektów w ogóle nie powinieneś korzystać tutaj z referencji, jedynie przeszkadzają. Teraz jak dorzuciłeś pomysł klonowania obiektów już kompletnie zgłupiałem.

1. http://ideone.com/sFx9OA - jak widzisz nie potrzeba nigdzie korzystać z żadnych referencji, wszędzie operuje się na dokładnie tym samym obiekcie.
2. Proszę, napisz jeszcze raz - kompletnie od podstaw - jaki problem chcesz rozwiązać przy pomocy tego kodu. Tylko nie pisz o tym, jak to próbujesz zrobić teraz czy jakbyś to zrobił w C++, a jedynie napisz co chcesz osiągnąć. Bo jeżeli ma to być tylko to co podałeś wcześniej, tj.:
Cytat
  1. if ($cache->exists('...')) {
  2. $obj = $cache->get('...');
  3. } else {
  4. // jakieś operacje
  5. $obj = ...;
  6. $cache->set('...', $obj);
  7. }
To wystarczy najzwyczajniej w świecie przypisać utworzony obiekt do jakiejś tablicy, jak w podanym przeze mnie przykładzie.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.