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().
class UserSession { private $_session_real_id; private $_session_db_id; private $_user_logged_in; private $_user_id; private $_user_agent; private $_session_timeout = 600; private $_session_lifetime = 3600; public function __construct() { // inicjuje mechanizm sesji ); // sprawdza przesłane cookie o ile je przesłano // jeśli wygląda podejrzanie zostaje z miejsca anulowane $this->_user_agent = $_SERVER["HTTP_USER_AGENT"]; { // kontrola bezpieczeństwa i ważności $this->_session_real_id = $_COOKIE["PHPSESSID"]; $stmt = $db->conn->prepare("SELECT session_id FROM user_sessions WHERE session_ascii_id = ? AND ((TIME_TO_SEC(NOW()) - TIME_TO_SEC(session_created)) < ?) AND user_agent = ? AND ((TIME_TO_SEC(NOW()) - TIME_TO_SEC(user_last_action)) <= ? OR user_last_action IS NULL)"); $stmt->bind_param('sisi', $this->_session_real_id, $this->_session_lifetime, $this->_user_agent, $this->_session_timeout); $stmt->execute(); $stmt->bind_result($session_id); if(!$stmt->fetch()) { // usuwa sesję z bazy danych - w tym czasie usuwane są również // wszystkie inne przeterminowane sesje $stmt = $db->conn->prepare("DELETE FROM user_sessions WHERE ((session_ascii_id = ?) OR ((TIME_TO_SEC(NOW()) - TIME_TO_SEC(session_created)) > ? ))"); $stmt->bind_param('si', $this->_session_real_id, $this->_session_lifetime); $stmt->execute(); // usuwa nieprzydatne zmienne sesji $stmt = $db->conn->prepare("DELETE FROM session_vars WHERE session_id NOT IN (SELECT session_id FROM user_sessions)"); $stmt->execute(); // pozbywa się identyfikatora wymuszając nadanie nowego } } // inicjuje sesję } public function session_open($path, $name) { return true; } public function session_close() { return true; } public function session_read($id) { // ustala czy sesja w ogóle istnieje $this->_user_agent = $_SERVER["HTTP_USER_AGENT"]; $this->_session_real_id = $id; $stmt = $db->conn->prepare("SELECT session_id, user_logged_in, user_id FROM user_sessions WHERE session_ascii_id = ?"); $stmt->bind_param('s', $this->_session_real_id); $stmt->execute(); $stmt->bind_result($session_id, $user_logged_in, $user_id); if($stmt->fetch()) { $this->_session_db_id = $session_id; if($user_logged_in) { $this->_user_logged_in = true; $this->_user_id = $user_id; } else $this->_user_logged_in = false; } else { $this->_user_logged_in = false; $stmt = $db->conn->prepare("INSERT INTO user_sessions(session_ascii_id, user_logged_in, user_id, session_created, user_agent) VALUES(?, 0, 0, NOW(), ?)"); $stmt->bind_param('ss', $this->_session_real_id, $this->_user_agent); $stmt->execute(); // teraz pobiera właściwy identyfikator sesji w bazie danych $stmt = $db->conn->prepare("SELECT session_id FROM user_sessions WHERE session_ascii_id = ?"); $stmt->bind_param('s', $this->_session_real_id); $stmt->execute(); $stmt->bind_result($session_id); $stmt->fetch(); $this->_session_db_id = $session_id; } return ""; } public function session_write($id, $data) { return true; } { /*$stmt = "DELETE FROM user_sessions WHERE session_ascii_id = '$id'"; $result = mysql_query($stmt);*/ $stmt = $db->conn->prepare("DELETE FROM user_sessions WHERE session_ascii_id = ?"); $stmt->bind_param('s', $id); $result = $stmt->execute(); return $result; } public function session_gc($max_lifetime) { return true; } }
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:
$stmt = $db->conn->prepare("SELECT session_id, user_logged_in, user_id FROM user_sessions 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:
$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:
$stmt->close()
i stąd całe zamieszanie
