Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [PHP] Dziwny sposób obejścia metody __SET. Proszę o objaśnienie.
Forum PHP.pl > Forum > Przedszkole
Ziuk
Witam

Zakupiłem książkę PHP i MySQL. Projekty do wykorzystania:
http://helion.pl/ksiazki/php-i-mysql-proje...inas,phmspr.htm

Znajduje się w niej kod odpowiadający za mechanizm rejestracji i logowania użytkowników, który w kolejnych rozdziałach jest bazą do tworzenia różnych przykładowych aplikacji. Jego zasadnicza częścią jest klasa User, której kod przedstawiam poniżej:


  1. <?php
  2. class User
  3. {
  4. private $uid; // identyfikator użytkownika
  5. private $fields; // inne pola rekordu
  6.  
  7. // inicjalizacja obiektu User
  8. public function __construct()
  9. {
  10. $this->uid = null;
  11. $this->fields = array('username' => '',
  12. 'password' => '',
  13. 'emailAddr' => '',
  14. 'isActive' => false);
  15. }
  16.  
  17. // nadpisanie metody odczytującej właściwości
  18. public function __get($field)
  19. {
  20. if ($field == 'userId')
  21. {
  22. return $this->uid;
  23. }
  24. else
  25. {
  26. return $this->fields[$field];
  27. }
  28. }
  29.  
  30. // nadpisanie metody ustawiającej właściwości
  31. public function __set($field, $value)
  32. {
  33. if (array_key_exists($field, $this->fields))
  34. {
  35. $this->fields[$field] = $value;
  36. }
  37. }
  38.  
  39. // sprawdzenie, czy nazwa użytkownika ma właściwy format
  40. public static function validateUsername($username)
  41. {
  42. return preg_match('/^[A-Z0-9]{2,20}$/i', $username);
  43. }
  44.  
  45. // sprawdzenie, czy adres email ma właściwy format
  46. public static function validateEmailAddr($email)
  47. {
  48. return filter_var($email, FILTER_VALIDATE_EMAIL);
  49. }
  50.  
  51. // zwrócenie obiektu wypełnionego na podstawie identyfikatora użytkownika
  52. public static function getById($uid)
  53. {
  54. $u = new User();
  55.  
  56. $query = sprintf('SELECT USERNAME, PASSWORD, EMAIL_ADDR, IS_ACTIVE ' .
  57. 'FROM %sUSER WHERE USER_ID = %d',
  58. DB_TBL_PREFIX,
  59. $uid);
  60. $result = mysql_query($query, $GLOBALS['DB']);
  61.  
  62. if (mysql_num_rows($result))
  63. {
  64. $row = mysql_fetch_assoc($result);
  65. $u->username = $row['USERNAME'];
  66. $u->password = $row['PASSWORD'];
  67. $u->emailAddr = $row['EMAIL_ADDR'];
  68. $u->isActive = $row['IS_ACTIVE'];
  69. $u->uid = $uid;
  70. }
  71.  
  72. return $u;
  73. }
  74.  
  75. // zwrócenie obiektu wypełnionego na podstawie nazwy użytkownika
  76. public static function getByUsername($username)
  77. {
  78. $u = new User();
  79.  
  80. $query = sprintf('SELECT USER_ID, PASSWORD, EMAIL_ADDR, IS_ACTIVE ' .
  81. 'FROM %sUSER WHERE USERNAME = "%s"',
  82. DB_TBL_PREFIX,
  83. mysql_real_escape_string($username, $GLOBALS['DB']));
  84. $result = mysql_query($query, $GLOBALS['DB']);
  85.  
  86. if (mysql_num_rows($result))
  87. {
  88. $row = mysql_fetch_assoc($result);
  89. $u->username = $username;
  90. $u->password = $row['PASSWORD'];
  91. $u->emailAddr = $row['EMAIL_ADDR'];
  92. $u->isActive = $row['IS_ACTIVE'];
  93. $u->uid = $row['USER_ID'];
  94. }
  95.  
  96. return $u;
  97. }
  98.  
  99. // zapisanie rekordu w bazie danych
  100. public function save()
  101. {
  102. if ($this->uid)
  103. {
  104. $query = sprintf('UPDATE %sUSER SET USERNAME = "%s", ' .
  105. 'PASSWORD = "%s", EMAIL_ADDR = "%s", IS_ACTIVE = %d ' .
  106. 'WHERE USER_ID = %d',
  107. DB_TBL_PREFIX,
  108. mysql_real_escape_string($this->username, $GLOBALS['DB']),
  109. mysql_real_escape_string($this->password, $GLOBALS['DB']),
  110. mysql_real_escape_string($this->emailAddr, $GLOBALS['DB']),
  111. $this->isActive,
  112. $this->userId);
  113. mysql_query($query, $GLOBALS['DB']);
  114. }
  115. else
  116. {
  117. $query = sprintf('INSERT INTO %sUSER (USERNAME, PASSWORD, ' .
  118. 'EMAIL_ADDR, IS_ACTIVE) VALUES ("%s", "%s", "%s", %d)',
  119. DB_TBL_PREFIX,
  120. mysql_real_escape_string($this->username, $GLOBALS['DB']),
  121. mysql_real_escape_string($this->password, $GLOBALS['DB']),
  122. mysql_real_escape_string($this->emailAddr, $GLOBALS['DB']),
  123. $this->isActive);
  124. mysql_query($query, $GLOBALS['DB']);
  125.  
  126. $this->uid = mysql_insert_id($GLOBALS['DB']);
  127. }
  128. }
  129.  
  130. // oznaczenie rekordu jako nieaktywnego i zwrócenie znacznika aktywacji
  131. public function setInactive()
  132. {
  133. $this->isActive = false;
  134. $this->save(); // zapewnienie, że rekord jest zapisany
  135.  
  136. $token = random_text(5);
  137. $query = sprintf('INSERT INTO %sPENDING (USER_ID, TOKEN) ' .
  138. 'VALUES (%d, "%s")',
  139. DB_TBL_PREFIX,
  140. $this->uid,
  141. $token);
  142. mysql_query($query, $GLOBALS['DB']);
  143.  
  144. return $token;
  145. }
  146.  
  147. // wyczyszczenie tymczasowego statusu użytkownika i oznaczenie rekordu jako aktywnego
  148. public function setActive($token)
  149. {
  150. $query = sprintf('SELECT TOKEN FROM %sPENDING WHERE USER_ID = %d ' .
  151. 'AND TOKEN = "%s"',
  152. DB_TBL_PREFIX,
  153. $this->uid,
  154. mysql_real_escape_string($token, $GLOBALS['DB']));
  155. $result = mysql_query($query, $GLOBALS['DB']);
  156.  
  157. if (!mysql_num_rows($result))
  158. {
  159. return false;
  160. }
  161. else
  162. {
  163. $query = sprintf('DELETE FROM %sPENDING WHERE USER_ID = %d ' .
  164. 'AND TOKEN = "%s"', DB_TBL_PREFIX,
  165. $this->uid,
  166. mysql_real_escape_string($token, $GLOBALS['DB']));
  167. mysql_query($query, $GLOBALS['DB']);
  168.  
  169. $this->isActive = true;
  170. $this->save();
  171. return true;
  172. }
  173. }
  174. }
  175. ?>


Zdeklarowana w klasie metoda dostępowa __SET:

Kod
    
    public function __set($field, $value)
    {
        if (array_key_exists($field, $this->fields))
        {
            $this->fields[$field] = $value;
        }
    }


przewiduje jedynie zapis do pola fields, które za sprawą konstruktora rozbudowywane jest do rozmiarów tablicy. Jak zatem możliwy jest zapis do pola $uid występujący np. w metodzie public static function getById($uid)?

Czy ktoś mógłby to wyjaśnić?

Wiem, że mógłbym uniknąć całego problemu zmieniając pola z private na public lub deklarując w __SET obsługę także pola $uid ale mnie interesuje zrozumiane tego jak to się dzieje, że zapis do pola $uid jest w ogóle możliwy mimo braku obsługi tegoż pola w metodzie __SET.

Za pomoc z góry dziękuję.

Cały rozdział(1) w którym umieszczona została przytoczona powyżej klasa jest dostępny na stronie:
http://pdf.helion.pl/phmspr/phmspr-1.pdf
Dostępny jest także kod z tego rozdziału:
ftp://ftp.helion.pl/przyklady/phmspr.zip


thek
Pamiętaj, że zmienne choć prywatne, są widoczne dla metod tejże klasy. Co z tego, że $obiekt->uid = 5 wywali błąd, skoro masz metodę, która wewnątrz widzi $this->uid i może z nią robić co chce smile.gif Poczytaj ciutkę o obiektówce i zasięgu widoczności zmiennych. __set i __get owszem... mają taką definicję, że działają z private $fields, ale jakakolwiek metoda tej klasy, niezależne o jakim atrybucie dostępu, widzi jej składowe już jako publiczne. Na zasadzie "Co jest moje to zezwalam, a tyś obcy i nie pozwolę."
mortus
Z manuala PHP:
Cytat
__set() is run when writing data to inaccessible properties.

Oznacza to, że operator -> za pomocą domyślnej metody __set() w pierwszej kolejności próbuje uzyskać dostęp do zadeklarowanych właściwości (property) klasy o określonym zasięgu, a w przypadku, gdy danej właściwości nie ma lub nie ma do niej dostępu następuje przeciążenie wyżej wspomnianej metody __set(), które to realizuje Twoja funkcja.
Ziuk
Jest więc tak, że metody klasy mogą uzyskiwać dostęp i możliwość zapisu do dowolnego pola klasy (w tym pola prywatnego) o ile tego typu operacja rozgrywa się w ramach tejże klasy nie wychodząc poza jej obręb. Natomiast jeżeli spoza klasy chciałbym ustawić wartość pola $uid to taka operacja nie jest możliwa gdyż metoda dostępowa __set w tym wypadku nie obsługuje zmiany $uid z pozycji (zewnętrznego wobec klasy User) obiektu klasy User.
W kodzie o którym mowa metoda __set właściwe kieruje jedynie tym gdzie mają być zapisywane wartości tzn. wskazuje zapis do konkretnych pozycji tablicy fields, która jest jednocześnie polem. Bez "nawigacji" prowadzonej przez __set dane zapisywane byłyby wprost do pól klasy czyli do pól takich jak $uid i dlatego też $uid, który nie jest ujęty w "nawigacji" realizowanej za pośrednictwem __set jest zapisywalny.

Czy teraz dobrze rozumuje?

Cytat
przeciążenie wyżej wspomnianej metody __set(), które to realizuje Twoja funkcja.


Jeszcze nie wiem co to jest przeciążenie metody smile.gif
thek
Dobrze rozumiesz Ziuk. Wewnątrz klasy każda metoda ma dostęp do pól na zasadzie specyfikatora public, bo jak wspomniałem: "To co moje może swobodnie sie ze soba porozumiewać". Tutaj co byś nie robił, to i tak każdy ma dostęp do wszystkiego. Stąd wewnątrz metod odwołania $this->pole są jak najbardziej poprawne. Dopiero próba odwołania zewnetrznego $obiekt_klasy->pole, jeśli byłoby ono private lub protected, jest naruszeniem. Własnie dlatego są metody magiczne __set i __get, które zwyczajowo są tworzone jako:
  1. public function __set($field, $value) {
  2. $this->$field = $value;
  3. }
co sprawia, że jeśli nie istnieje własność o nazwie $field to tworzona jest wtedy niejawnie i wartość mu nadawana jest ustawiana na $value. W Twoim wypadku jest ona przeciążona/nadpisana w taki sposób, że odbywało się to nie wprost do obiektu ale do własności fields i to tylko, jeśli dany index istnieje w tej tablicy, na co zapewne nie zwróciłeś uwagi. Tak więc zapis $obiekt->password = 'test' da radę zrobić, ale juz $obiekt->losowe_pole = 'próba' nie powiedzie się, bo w tablicy fields brak indeksu o nazwie losowe_pole.

Przeciążenie to deklaracja wielu wersji tej samej metody, przy czym kompilator/parser z kontekstu wychwytuje o która z nich chodzi. W PHP nie jest to tak widoczne, ponieważ jawne przeciążenie tu ciężko zrobić i odbywa się z kontekstu obiektu. Jest to lepiej widoczne w językach ze stałym typowaniem, gdzie choćby metoda( int zmienna) i metoda( float zmienna ) są dla komplilatora dwiema zupełnie innymi, choć o tej samej nazwie. Musiałbym sprawdzić jak PHP reaguje na metoda( array $zmienna ) i metoda( object $zmienna ) oraz metoda( $zmienna ), ale z tego co kojarzę to powinien się zbuntować coś o redeklaracji funkcji krzycząc wink.gif
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.