Witam.

Mam taką oto klasę, która służy do zarządzania sesjami użytkowników. Kod klasy jest poniżej.
Generalnie idea tej klasy jest taka, żeby zapisywać dane sesji do bazy danych MySQL. Do bazy trafia id sesji wygenerowane przez PHP (private $_session_real_id), a także identyfikator sesji w bazie danych (private $_session_db_id), używany generalnie tylko do szybszego wyszukiwania rekordów w samej bazie.
Do komunikacji z bazą danych używam rozszerzenia mysqli i tu się właśnie zaczynają schody, bo o ile na rozszerzeniu mysql wszystko fajnie działa, tak na mysqli już nie jest tak pięknie.

Poniżej przedstawiam kod klasy (okrojony o metody typu login, logut, getUserId itp.) - generalnie problem tkwi (chyba) w metodzie session_read($id) - jest to callback funkcji session_set_save_handler().

  1. class UserSession
  2. {
  3. private $_session_real_id;
  4. private $_session_db_id;
  5. private $_user_logged_in;
  6. private $_user_id;
  7. private $_user_agent;
  8. private $_session_timeout = 600;
  9. private $_session_lifetime = 3600;
  10.  
  11. public function __construct()
  12. {
  13. global $db; // uchwyt do połączenia bazą danych
  14.  
  15. // inicjuje mechanizm sesji
  16. array(&$this, 'session_open'),
  17. array(&$this, 'session_close'),
  18. array(&$this, 'session_read'),
  19. array(&$this, 'session_write'),
  20. array(&$this, 'session_destroy'),
  21. array(&$this, 'session_gc')
  22. );
  23.  
  24. // sprawdza przesłane cookie o ile je przesłano
  25. // jeśli wygląda podejrzanie zostaje z miejsca anulowane
  26. $this->_user_agent = $_SERVER["HTTP_USER_AGENT"];
  27. if(isset($_COOKIE["PHPSESSID"]))
  28. {
  29. // kontrola bezpieczeństwa i ważności
  30. $this->_session_real_id = $_COOKIE["PHPSESSID"];
  31. $stmt = $db->conn->prepare("SELECT session_id FROM user_sessions
  32. WHERE session_ascii_id = ?
  33. AND ((TIME_TO_SEC(NOW()) - TIME_TO_SEC(session_created)) < ?)
  34. AND user_agent = ?
  35. AND ((TIME_TO_SEC(NOW()) - TIME_TO_SEC(user_last_action)) <= ?
  36. OR user_last_action IS NULL)");
  37.  
  38. $stmt->bind_param('sisi', $this->_session_real_id,
  39. $this->_session_lifetime,
  40. $this->_user_agent,
  41. $this->_session_timeout);
  42. $stmt->execute();
  43. $stmt->bind_result($session_id);
  44.  
  45. if(!$stmt->fetch())
  46. {
  47. // usuwa sesję z bazy danych - w tym czasie usuwane są również
  48. // wszystkie inne przeterminowane sesje
  49. $stmt = $db->conn->prepare("DELETE FROM user_sessions
  50. WHERE ((session_ascii_id = ?)
  51. OR ((TIME_TO_SEC(NOW()) - TIME_TO_SEC(session_created)) > ? ))");
  52. $stmt->bind_param('si', $this->_session_real_id,
  53. $this->_session_lifetime);
  54. $stmt->execute();
  55.  
  56. // usuwa nieprzydatne zmienne sesji
  57. $stmt = $db->conn->prepare("DELETE FROM session_vars
  58. WHERE session_id NOT IN
  59. (SELECT session_id FROM user_sessions)");
  60. $stmt->execute();
  61.  
  62. // pozbywa się identyfikatora wymuszając nadanie nowego
  63. unset($_COOKIE["PHPSESSID"]);
  64. }
  65. }
  66. // inicjuje sesję
  67. }
  68.  
  69. public function session_open($path, $name)
  70. {
  71. return true;
  72. }
  73.  
  74. public function session_close()
  75. {
  76. return true;
  77. }
  78.  
  79. public function session_read($id)
  80. {
  81. global $db; // uchwyt do połączenia z bazą danych
  82.  
  83. // ustala czy sesja w ogóle istnieje
  84. $this->_user_agent = $_SERVER["HTTP_USER_AGENT"];
  85. $this->_session_real_id = $id;
  86.  
  87. $stmt = $db->conn->prepare("SELECT session_id, user_logged_in, user_id
  88. FROM user_sessions
  89. WHERE session_ascii_id = ?");
  90. $stmt->bind_param('s', $this->_session_real_id);
  91. $stmt->execute();
  92. $stmt->bind_result($session_id, $user_logged_in, $user_id);
  93.  
  94. if($stmt->fetch())
  95. {
  96. $this->_session_db_id = $session_id;
  97.  
  98. if($user_logged_in)
  99. {
  100. $this->_user_logged_in = true;
  101. $this->_user_id = $user_id;
  102. }
  103. else
  104. $this->_user_logged_in = false;
  105. }
  106.  
  107. else
  108. {
  109. $this->_user_logged_in = false;
  110. $stmt = $db->conn->prepare("INSERT INTO user_sessions(session_ascii_id, user_logged_in, user_id, session_created, user_agent)
  111. VALUES(?, 0, 0, NOW(), ?)");
  112. $stmt->bind_param('ss', $this->_session_real_id, $this->_user_agent);
  113. $stmt->execute();
  114.  
  115. // teraz pobiera właściwy identyfikator sesji w bazie danych
  116. $stmt = $db->conn->prepare("SELECT session_id FROM user_sessions
  117. WHERE session_ascii_id = ?");
  118. $stmt->bind_param('s', $this->_session_real_id);
  119. $stmt->execute();
  120. $stmt->bind_result($session_id);
  121. $stmt->fetch();
  122. $this->_session_db_id = $session_id;
  123. }
  124.  
  125. return "";
  126. }
  127.  
  128. public function session_write($id, $data)
  129. {
  130. return true;
  131. }
  132.  
  133. public function session_destroy($id)
  134. {
  135. global $db;
  136. /*$stmt = "DELETE FROM user_sessions WHERE session_ascii_id = '$id'";
  137. $result = mysql_query($stmt);*/
  138. $stmt = $db->conn->prepare("DELETE FROM user_sessions WHERE session_ascii_id = ?");
  139. $stmt->bind_param('s', $id);
  140. $result = $stmt->execute();
  141.  
  142. return $result;
  143. }
  144.  
  145. public function session_gc($max_lifetime)
  146. {
  147. return true;
  148. }
  149. }


A problem jest następujący. Jeśli w bazie danych nie ma żadnych wpisów w tabeli user_sessions (czyli w tabeli przechowującej zasadniczą część danych dotyczących sesji) to wszystko jest w porządku, tzn. funckja session_read($id) tworzy w bazie danych wpis z aktualnym identyfikatorem sesji oraz danymi użytkownika. Jednak po ponownym odświeżeniu strony (czyli kiedy istnieje już aktualny wpis w tabeli user_sessions) PHP wywala mi błąd:
Fatal error: Call to a member function bind_param() on a non-object in C:\wamp\www\store\classes\class.userSession.php on line 197
czyli generalnie zapytanie:
  1. $stmt = $db->conn->prepare("SELECT session_id, user_logged_in, user_id
  2. FROM user_sessions
  3. WHERE session_ascii_id = ?");

staje się nagle nieprawidłowe. Żeby było śmieszniej dzieje się tak wtedy kiedy właśnie w tabeli user_sessions istnieje wpis - kiedy tabela jest pusta, wszystko jest w porządku, po prostu tworzony jest nowy wpis.
Dodam jeszcze, że jeśli napisałbym tutaj:

  1. $result = mysql_query("SELECT session_id, user_logged_in, user_id FROM user_sessions WHERE session_ascii_id = '$id'");


zapytanie wykona się bez problemu.
Czy ktoś ma może jakiś pomysł co tu jest nie tak?

Z góry dzięki za pomoc.
Pozdrawiam.


EDIT
Ok, można zamknąc temat.
Problem jednak nie tkwił w funkcji session_read() a w samym konstruktorze - zapomniałem dodać na końcu konstruktora:
  1. $stmt->close()

i stąd całe zamieszanie smile.gif