Witam
Postanowiłem napisać własny system obsługi sesji oparty o bazę danych, po przejrzeniu kilku istniejących już rozwiązać stosowanych w skryptach open source doszedłem do wniosku że jest to za skomplikowane na moje potrzeby i napisałem coś takiego :

Najpierw okrojona wersja mojego sterownika do obsługi mysql'a(super improved myql biggrin.gif biggrin.gif ):

  1. <?php
  2. class Error extends Exception{}
  3.  
  4. class DB extends mysqli{
  5.  
  6. private function __construct($g_config){
  7. @parent::__construct($g_config['host'], $g_config['user'], $g_config['pass'], $g_config['database']);
  8.  
  9. if(mysqli_connect_error()){
  10. throw new Error('<br /><br />Problem z nawiązaniem połączenia!<br /> '.mysqli_connect_error());
  11. }
  12. }
  13.  
  14. public static function instance($g_config){
  15. static $obiekt;
  16. if(!$obiekt instanceof DB){
  17. $obiekt = new DB($g_config);
  18. }
  19. return $obiekt;
  20. }
  21.  
  22. public function query($query){
  23. $wynik = parent::query($query);
  24. if(mysqli_error($this)){
  25. throw new Error('<br><br />Problem z wykonaniem zapytania! <br />'.mysqli_error($this));
  26. }
  27. return $wynik;
  28. }
  29.  
  30.  
  31. public function __destruct(){
  32. if(!mysqli_connect_error()){
  33. $this->close();
  34. }
  35. }
  36. }
  37. ?>


Klasa do obsługi sesji:

  1. <?php
  2. interface sessionHandler{
  3. public function _open($savePath, $sessionName);
  4. public function _close();
  5. public function _read($sessionId);
  6. public function _write($sessionId, $sessionData);
  7. public function _destroy($sessionId);
  8. public function _gc($maxLifetime = NULL); 
  9. }
  10.  
  11. class Session implements sessionHandler{
  12. private $db;
  13. private $sessionId;
  14. private $sessionName;
  15. private $sessionLifetime;
  16. private $sessionTable;
  17. private $isNew = TRUE;
  18.  
  19. public function __construct(&$db, 
  20. $sessionTable = 'my_session_data', 
  21. $sessionLifetime = 30, 
  22. $sessionProbability = 5, 
  23. $sessionDivisor = 100 
  24. ){
  25.  
  26. ini_set('session.gc_maxlifetime', $sessionLifetime);
  27. ini_set('session.gc_probability', $sessionProbability);
  28. ini_set('session.gc_divisor', $sessionDivisor);
  29.  
  30. $this->db = $db;
  31. $this->sessionLifetime = $sessionLifetime;
  32. $this->sessionTable = $sessionTable;
  33. $this->sessionName = session_name();
  34. session_set_save_handler(array(&$this, '_open'),
  35.  array(&$this, '_close'),
  36.  array(&$this, '_read'),
  37.  array(&$this, '_write'),
  38.  array(&$this, '_destroy'),
  39.  array(&$this, '_gc')
  40. );
  41.  
  42. }
  43.  
  44. public function _open($savePath, $sessionName){
  45. $this->sessionId = session_id();
  46. //echo '<br /><font color=violet>_open()</font><br /><br />';
  47. return(TRUE);
  48. }
  49.  
  50. public function _close(){
  51. //echo '<br /><font color=violet>_close()</font><br />';
  52. return(TRUE);
  53. }
  54.  
  55. public function _read($sessionId){
  56. $sql = 'SELECT data FROM '.$this->sessionTable.
  57. WHERE session_id = "'.$this->db->real_escape_string($this->sessionId).'" 
  58. AND(
  59. user_agent="'.$this->db->real_escape_string(USER_AGENT).'"
  60. AND user_ip="'.$this->db->real_escape_string(USER_IP).'"
  61. AND ('.time().' - time ) < '.$this->sessionLifetime.'
  62. )';
  63. //echo '<br /><b>_read('.$this->sessionId.')</b><br />'.$sql;
  64. try{
  65. $wynik = $this->db->query($sql);
  66. if($wynik->num_rows == 1){
  67. $this->isNew = FALSE;
  68. return (array_pop($wynik->fetch_row()));
  69. }
  70. return('');
  71. }catch(Error $e){
  72. echo $e->getMessage();
  73. }
  74. }
  75.  
  76. public function _write($sessionId, $sessionData){
  77. if($this->isNew){
  78.  
  79. if(isset($_COOKIE[$this->sessionName])){
  80. //echo '<br /><br /><b>isset( $_COOKIE[ '.$this->sessionName.' ] )</b><br />';
  81. $this->_destroy($this->sessionId);
  82. }
  83.  
  84. $sql = 'INSERT INTO '.$this->sessionTable.' (session_id, data, start, time, user_agent, user_ip)
  85. VALUES
  86. ("'.$this->db->real_escape_string($this->sessionId).'",
  87.  "'.$this->db->real_escape_string($sessionData).'",
  88.  "'.time().'", 
  89.  "'.time().'", 
  90.  "'.$this->db->real_escape_string(USER_AGENT).'", 
  91.  "'.$this->db->real_escape_string(USER_IP).'"
  92. )';
  93.  
  94. //echo '<br /><b>_write(INSERT)('.$this->sessionId.', '.$sessionData.')</b><br />'.$sql;
  95.  
  96. try{
  97. $this->db->query($sql);
  98. $this->isNew = FALSE;
  99. if($this->db->affected_rows > 0){
  100. return(TRUE);
  101. }
  102. }catch(Error $e){
  103. echo $e->getMessage();
  104. }
  105.  
  106. }else{
  107. $sql = 'UPDATE '.$this->sessionTable.' SET
  108. data="'.$this->db->real_escape_string($sessionData).'", 
  109. time="'.time().'", 
  110. user_agent="'.$this->db->real_escape_string(USER_AGENT).'", 
  111. user_ip="'.$this->db->real_escape_string(USER_IP).'"
  112. WHERE session_id="'.$this->db->real_escape_string($this->sessionId).'"';
  113.  
  114. //echo '<br /><b>_write(UPDATE)('.$this->sessionId.', '.$sessionData.')</b><br />'.$sql;
  115. try{
  116. $this->db->query($sql);
  117. if($this->db->affected_rows){
  118. return(TRUE);
  119. }
  120. }catch(Error $e){
  121. echo $e->getMessage();
  122. }
  123. }
  124. return(FALSE);
  125.  
  126. }
  127.  
  128. public function _destroy($sessionId){
  129. //echo '<br /><b>_destroy('.$this->sessionId.')</b><br />';
  130. $sql = 'DELETE FROM '.$this->sessionTable.
  131. WHERE session_id="'.$this->db->real_escape_string($this->sessionId).'"';
  132. try{
  133. if($this->db->query($sql)){
  134. return(TRUE);
  135. }else{
  136. return(FALSE);
  137. }
  138. }catch(Error $e){
  139. echo $e->getMessage();
  140. }
  141. }
  142.  
  143. public function _gc($maxLifetime = NULL){
  144. //echo '<br /><b>_gc()</b><br />';
  145. $sql = 'DELETE FROM '.$this->sessionTable.
  146. WHERE ('.time().' - time ) > '.$this->sessionLifetime.'';
  147. try{
  148. if($this->db->query($sql)){
  149. return(TRUE);
  150. }else{
  151. return(FALSE);
  152. }
  153. }catch(Error $e){
  154. echo $e->getMessage();
  155. }
  156. }
  157. }
  158. ?>


Struktura bazy danych:

  1. <?php
  2. CREATE TABLE `session` (
  3. `session_id` char(32) NOT NULL,
  4. `start` varchar(11) NOT NULL,
  5. `time` varchar(11) NOT NULL,
  6. `data` text,
  7. `user_ip` varchar(40) NOT NULL,
  8. `user_agent` varchar(150) NOT NULL,
  9. PRIMARY KEY (`session_id`),
  10. KEY `time(`time`)
  11. ) ENGINE=InnoDB DEFAULT CHARSET=utf8_bin;
  12. ?>


Plik testowy:
  1. <?php
  2. header('Content-Type: text/html; charset=utf-8');
  3. define('USER_AGENT', $_SERVER['HTTP_USER_AGENT']);
  4. define('USER_IP', $_SERVER['REMOTE_ADDR']);
  5. $g_config['host'] = 'localhost';
  6. $g_config['user'] = 'user';
  7. $g_config['pass'] = 'pass';
  8. $g_config['database'] = 'testy';
  9. include_once('db.class.php');
  10. include_once('session.class.php');
  11.  
  12. try{
  13. $db = DB::instance($g_config); 
  14. $s = new Session($db);
  15.  
  16. if(isset($_SESSION['a'])){
  17. echo '<br>Zalogowany <b> :'.$_SESSION['a'].'</b><br />';
  18. }else{
  19. echo '<br /><br />Niezalogowany<br /><br />';
  20. }
  21. $_SESSION['a'] = 'Mietek';
  22. }catch(Error $e){
  23. echo $e->getMessage();
  24. }
  25. ?>


Na początku dodam ze chce ograniczyć do minimum ilość wykonywanych zapytań. smile.gif
Aby zobaczyć cokolwiek w oknie przeglądarki trzeba odkomentować niektóre linie w kodzie.
W rozwiązaniach które przeglądałem sprawdzanie istnienia/aktywności sesji jest wykonywane zarówno w metodzie _read() jak i _write() w ten sposób "tracimy" dodatkowe zapytanie(choć ma to i plus rozwiązuje mój problem :-)) ponieważ i tak jeśli rozpoczniemy sesje wykonywane są metody _read() i _write().

Ja w metodzie _read() sprawdzam czy istnieje sesja i nie wygasła, jeśli jest to ustawiam zmienną isNew na FALSE(domyślnie TRUE) dzięki temu metoda _write() wie że ma wykonać UPDATE a nie INSERT INTO(jeśli isNew ustawiona na TRUE) i odwrotnie jeśli zmienna isNew ma wartość TRUE. W tym momencie zaczynają się schody, jeśli sesja wygasła(wygasła w bazie z powodu nieaktywności usera ale przeglądarka jest otwarta) to metoda _write() będzie próbowała dodać nowy rekord do bazy o id takim samym jak ten wygasły w bazie (to samo ciacho) i wyniknie błąd w zapytaniu ponieważ kolumna session_id ma primary_key w celu zwiększenia wydajności bazy a jak wiadomo to pole musi być unikalne. Jak widać ja to rozwiązuje tym kawałkiem kodu:
  1. <?php
  2. if(isset($_COOKIE[$this->sessionName])){
  3. echo '<br /><br /><b>isset( $_COOKIE[ '.$this->sessionName.' ] )</b><br />';
  4. $this->_destroy($this->sessionId);
  5. }
  6. ?>


Wszystko ładnie pięknie działa tylko tylko mamy już dodatkowe zapytanie smile.gif a tego nie chce i tu moje pytanie do was czy macie jakiś sposób by zmienić obecne id sesji?
Kombinowałem z session_regenerate_id" title="Zobacz w manualu PHP" target="_manual ale najwyraźniej wewnątrz mechanizmu obsługi sesji to nie działa - i dobrze, pozostaje chyba tylko session_set_cookie_params" title="Zobacz w manualu PHP" target="_manual i nadpisywanie cookie albo dodatkowe zapytanie.
No i narzuca się pytanie czy nie lepiej wykonać jedno zapytanie DELETE, SELECT więcej czy kombinować z zmianą id?

P.S IMO jeśli sesja wygaśnie i tak powinno zostać zmienione id sessji tongue.gif

Pozdrawiam