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 (IMG:
http://forum.php.pl/style_emoticons/default/biggrin.gif) (IMG:
http://forum.php.pl/style_emoticons/default/biggrin.gif) ):
<?php
class Error extends Exception{}
class DB extends mysqli{
private function __construct($g_config){
@parent::__construct($g_config['host'], $g_config['user'], $g_config['pass'], $g_config['database']);
if(mysqli_connect_error()){
throw new Error('<br /><br />Problem z nawiązaniem połączenia!<br /> '.mysqli_connect_error());
}
}
public static function instance
($g_config){ if(!$obiekt instanceof DB){
$obiekt = new DB($g_config);
}
return $obiekt;
}
public function query($query){
$wynik = parent::query($query);
if(mysqli_error($this)){
throw new Error('<br><br />Problem z wykonaniem zapytania! <br />'.mysqli_error($this));
}
return $wynik;
}
public function __destruct(){
if(!mysqli_connect_error()){
$this->close();
}
}
}
?>
Klasa do obsługi sesji:
<?php
interface sessionHandler{
public function _open($savePath, $sessionName);
public function _close();
public function _read($sessionId);
public function _write($sessionId, $sessionData);
public function _destroy($sessionId);
public function _gc($maxLifetime = NULL);
}
class Session implements sessionHandler{
private $db;
private $sessionId;
private $sessionName;
private $sessionLifetime;
private $sessionTable;
private $isNew = TRUE;
public function __construct(&$db,
$sessionTable = 'my_session_data',
$sessionLifetime = 30,
$sessionProbability = 5,
$sessionDivisor = 100
){
ini_set('session.gc_maxlifetime', $sessionLifetime); ini_set('session.gc_probability', $sessionProbability); ini_set('session.gc_divisor', $sessionDivisor);
$this->db = $db;
$this->sessionLifetime = $sessionLifetime;
$this->sessionTable = $sessionTable;
array(&$this, '_destroy'), );
}
public function _open($savePath, $sessionName){
//echo '<br /><font color=violet>_open()</font><br /><br />';
return(TRUE);
}
public function _close(){
//echo '<br /><font color=violet>_close()</font><br />';
return(TRUE);
}
public function _read($sessionId){
$sql = 'SELECT data FROM '.$this->sessionTable.'
WHERE session_id = "'.$this->db->real_escape_string($this->sessionId).'"
AND(
user_agent="'.$this->db->real_escape_string(USER_AGENT).'"
AND user_ip="'.$this->db->real_escape_string(USER_IP).'"
AND ('.time().' - time ) < '.$this->sessionLifetime.' )';
//echo '<br /><b>_read('.$this->sessionId.')</b><br />'.$sql;
try{
$wynik = $this->db->query($sql);
if($wynik->num_rows == 1){
$this->isNew = FALSE;
}
return('');
}catch(Error $e){
}
}
public function _write($sessionId, $sessionData){
if($this->isNew){
if(isset($_COOKIE[$this->sessionName])){ //echo '<br /><br /><b>isset( $_COOKIE[ '.$this->sessionName.' ] )</b><br />';
$this->_destroy($this->sessionId);
}
$sql = 'INSERT INTO '.$this->sessionTable.' (session_id, data, start, time, user_agent, user_ip)
VALUES
("'.$this->db->real_escape_string($this->sessionId).'",
"'.$this->db->real_escape_string($sessionData).'",
"'.$this->db->real_escape_string(USER_AGENT).'",
"'.$this->db->real_escape_string(USER_IP).'"
)';
//echo '<br /><b>_write(INSERT)('.$this->sessionId.', '.$sessionData.')</b><br />'.$sql;
try{
$this->db->query($sql);
$this->isNew = FALSE;
if($this->db->affected_rows > 0){
return(TRUE);
}
}catch(Error $e){
}
}else{
$sql = 'UPDATE '.$this->sessionTable.' SET
data="'.$this->db->real_escape_string($sessionData).'",
user_agent="'.$this->db->real_escape_string(USER_AGENT).'",
user_ip="'.$this->db->real_escape_string(USER_IP).'"
WHERE session_id="'.$this->db->real_escape_string($this->sessionId).'"';
//echo '<br /><b>_write(UPDATE)('.$this->sessionId.', '.$sessionData.')</b><br />'.$sql;
try{
$this->db->query($sql);
if($this->db->affected_rows){
return(TRUE);
}
}catch(Error $e){
}
}
return(FALSE);
}
public function _destroy($sessionId){
//echo '<br /><b>_destroy('.$this->sessionId.')</b><br />';
$sql = 'DELETE FROM '.$this->sessionTable.'
WHERE session_id="'.$this->db->real_escape_string($this->sessionId).'"';
try{
if($this->db->query($sql)){
return(TRUE);
}else{
return(FALSE);
}
}catch(Error $e){
}
}
public function _gc($maxLifetime = NULL){
//echo '<br /><b>_gc()</b><br />';
$sql = 'DELETE FROM '.$this->sessionTable.'
WHERE ('.time().' - time ) > '.$this->sessionLifetime.''; try{
if($this->db->query($sql)){
return(TRUE);
}else{
return(FALSE);
}
}catch(Error $e){
}
}
}
?>
Struktura bazy danych:
<?php
CREATE TABLE `session` (
`start` varchar(11) NOT NULL,
`
time` varchar
(11
) NOT
NULL, `data` text,
`user_ip` varchar(40) NOT NULL,
`user_agent` varchar(150) NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8_bin;
?>
Plik testowy:
<?php
header('Content-Type: text/html; charset=utf-8'); define('USER_AGENT', $_SERVER['HTTP_USER_AGENT']); define('USER_IP', $_SERVER['REMOTE_ADDR']); $g_config['host'] = 'localhost';
$g_config['user'] = 'user';
$g_config['pass'] = 'pass';
$g_config['database'] = 'testy';
include_once('db.class.php');
include_once('session.class.php');
try{
$db = DB::instance($g_config);
$s = new Session($db);
if(isset($_SESSION['a'])){ echo '<br>Zalogowany <b> :'.$_SESSION['a'].'</b><br />'; }else{
echo '<br /><br />Niezalogowany<br /><br />'; }
$_SESSION['a'] = 'Mietek';
}catch(Error $e){
}
?>
Na początku dodam ze chce ograniczyć do minimum ilość wykonywanych zapytań. (IMG:
http://forum.php.pl/style_emoticons/default/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:
<?php
if(isset($_COOKIE[$this->sessionName])){ echo '<br /><br /><b>isset( $_COOKIE[ '.$this->sessionName.' ] )</b><br />'; $this->_destroy($this->sessionId);
}
?>
Wszystko ładnie pięknie działa tylko tylko mamy już dodatkowe zapytanie (IMG:
http://forum.php.pl/style_emoticons/default/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 (IMG:
http://forum.php.pl/style_emoticons/default/tongue.gif)
Pozdrawiam
Ten post edytował wiechol 3.05.2007, 19:05:40