9. Leden
2013

Proč?

To je asi první věc, které každého napadne. Všechno funguje, tak proč to měnit? Důvodů je několik. Hlavním důvodem je, že je ve výchozím nastavení PHP ukládají na pevný disk. V tom může být několik problémů. Pokud mají sessions dlouhou životnost nebo je web hojně navštěvovaný, vytvoří se obrovské množství souborů. Dostatečné množství souborů, aby dokázalo zahltit i bash. Obojí se samozřejmě dá vyřešit pomocí session-save-path, ale v tu chvíli si musíme začít mazat prošlé soubory sami, což nemusí být , na rozdíl od databáze, zrovna jednoduché.

Ukládat do databáze má tedy smysl především v těchto případech.
  • Potřebuji dlouhou životnost a mám vysokou návštěvnost.
  • Jsem na sdíleném hostingu a chci mít režii ve svých rukou, popřípadě mi parametry sessions nevyhovují.

Jak?

PHP na podobné případy má připravenou funkci session_set_sa­ve_handler, kterou lze nastavit funkce pro správu sessions. Stačí si pouze vytvořit třídu, která bude obsahovat všechny potřebné funkce, kterou pak nasázíme do zmíněné funkce. Zní to dobře, že? Ale i zde je menší problém.

V čem je problém?

Současně totiž nemohou být otevřené dvě nebo více instancí. Důvod je prostý a jednoduše si ho popíšeme na následujícím obrázku.

graf-sess

Sessions se načítají během jejich startování a zapisují se až po jejich ukončení, takže mezitím mohou být na serveru neaktuální informace. Proto je důležité ohlídat, aby neběželi dvě instance jedné session najednou. Ukážeme si příklad.

Pokud paralelně běží dva požadavky, první nejdříve načte data a může je upravit. Druhý je načte také, ale už se nedozví jestli je náhodou první požadavek nezměnil, takže nemusejí být aktuální. Druhý požadavek udělá určité změny a zapíše. První požadavek byl časově náročnější, tak skončil až po druhém požadavku, nyní přepíše session a přepíše výsledek druhého požadavku.  V nejlepším případě se aplikace chová jakoby se druhý požadavek vůbec neprováděl, v tom horším se může i rozhodit nějaká struktura a aplikace se může stát nestabilní.

Naštěstí řešení není složité. Stačí při načítání zkontrolovat, jestli už neběží jiná instance a pokud ano, tak chvilku počkat, než se ukončí.

Implementace

Nyní se už můžeme pustit do samotné implementace. V tomto případě ukládám do databáze MySQL pomocí nativních PHP funkcí, nicméně není problém si upravit model na nějaké jiné řešení, hlavně si vyřešit zamykání tabulek.

Tabulka:

CREATE TABLE session ( id varchar(200) NOT NULL, time datetime NOT NULL, data text NOT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Zde myslím není nic dlouhému k vysvětlování. Snad jen, že sloupec id tu není úplně standardní, ale obsahuje session id.

Interface Modelu:

interface iSessionModel { public function isFree($id);public function lock($id);public function unlock($id);public function write($id, $data);public function read($id);public function remove($id);public function clean($maxlife­time); }
Tento interface implementují všechny modely určené pro naši třídu.  Zajímavé jsou především metody isFree , lock a unlock, které zajišťují zjištění jestli je tabulka zamčená, respektive ji odemknou/zamknou. Metoda clean zajišťuje mazání prošlých záznamů.

Mysql model

class SessionsMySQLModel implements iSessionModel {public function isFree($id) {
$result = mysql_query(„SELECT IS_FREE_LOCK(‚ses­sion_$id‘) AS ‚lock‘“);
$row = mysql_fetch_a­rray($result);
return $row[‚lock‘];
}public function clean($maxlifetime) {
mysql_query(„DELETE FROM sessions WHERE time < NOW() – INTERVAL $maxlifetime SECOND“);
}public function lock($id) {
mysql_query(„SELECT GET_LOCK(‚ses­sion_$id‘, 160)“);
}public function read($id) {
$result = mysql_query(„SELECT data FROM sessions WHERE id=‚$id‘“);
if ($result) {
$data = mysql_fetch_a­rray($result);
$data = $data[‚data‘];
}else
$data = '';
return $data;
}public function remove($id) {
mysql_query(„DELETE FROM sessions WHERE id = ‚$id‘“);
}public function unlock($id) {
mysql_query(„SELECT RELEASE_LOCK(‚ses­sion_$id‘)“);
}public function write($id, $data) {
mysql_query(„INSERT INTO sessions (id,time,data)
VALUES (‚$id‘,NOW(),‚“ . mysql_real_es­cape_string($da­ta) . „‘)
ON DUPLICATE KEY
UPDATE time = NOW() , data = ‚“ . mysql_real_es­cape_string($da­ta) . „‘“);
}}
 

Třída SessionHandler

 
class SessionHandler {private $id;
private $model;public function __construct(i­SessionModel $model) {
$this->model = $model;
session_set_sa­ve_handler(
array($this , ‚open‘),
array($this, ‚close‘),
array($this , ‚read‘),
array($this , ‚write‘),
array($this, ‚remove‘),
array($this, ‚clean‘)
);
}public function open($savePath, $sessionName) {
$id = $this->id = session_id();
$count = 0;
do {
if ($count++)
usleep(100000); //Omezí dotazování na 10× za sekundu
} while (!$this->model->isFree($id));
$this->model->lock($id);
return true;
}public function close() {
$id = $this->id;
$this->model->unlock($id);
return true;
}public function read($id) {
return $this->model->read($id);
}public function write($id, $data) {
$this->model->write($id, $data);
return true;
}public function remove($id) {
$this->model->remove($id);
return true;
}public function clean($maxlifetime) {
$this->model->clean($maxli­fetime);
return true;
}}
Myslím, že není potřeba nic dodávat.

Použití

Po stažení balíku stačí vložit kamkoli do projektu. A přidat tento kód.
include(__DIR__­."/cesta/k/Ses­sionHandler.php); $model = new SessionsMySQLModel; new MySQLSessionSto­rage($model);

Varování

Připojení k databázi je nutné vložit před tento skript a session nastar­tovat až po něm!

 

Stažení

Download168 downloads

Zanechat komentář

„Nejkrásnější ze všech tajemství je být géniem a vědět to jen sám.“ Mark Twain