W ciągu kilku godzin debugowania odkryłem, że wymienione artykuły znalezione w wielu wyszukiwaniach Google, a także znaczny podzbiór odpowiedzi Stack Overflow, takich jak tutaj , tutaj i tutaj wszystkie podają nieprawidłowe lub nieaktualne informacje.
Rzeczy, które mogą powodować [krytyczne] problemy z zapisywaniem danych sesji w bazie danych:
-
Podczas gdy wszystkie przykłady online stwierdzają, że możesz „wypełnić”
session_set_save_handler
, żaden z nich nie stwierdza, że należy również ustawićregister_shutdown_function('session_write_close')
też (odniesienie ). -
Kilka (starszych) przewodników odnosi się do przestarzałej struktury bazy danych SQL i nie być użytym. Struktura bazy danych potrzebna do zapisania danych sesji w bazie danych to:
id
/access
/data
. Otóż to. nie ma potrzeby stosowania różnych dodatkowych kolumn sygnatur czasowych, jak widziałem w kilku „przewodnikach” i przykładach.- Kilka starszych przewodników ma również przestarzałą składnię MySQL, taką jak
DELETE * FROM ...
- Kilka starszych przewodników ma również przestarzałą składnię MySQL, taką jak
-
Klasa [wykonana w moim pytaniu] musi zaimplementować
SessionHandlerInterface
. Widziałem przewodniki (wspomniane powyżej), które podają implementacjęsessionHandler
co nie jest odpowiednim interfejsem. Być może poprzednie wersje PHP miały nieco inną metodę (prawdopodobnie <5.4). -
Metody klasy sesji muszą zwrócić wartości określone w podręczniku PHP. Ponownie, prawdopodobnie odziedziczone z PHP sprzed wersji 5.4, ale dwa przewodniki, które przeczytałem, stwierdzały, że
class->open
zwraca wiersz do odczytania, podczas gdy stany podręcznika PHP że musi zwrócićtrue
lubfalse
tylko. -
To jest przyczyna mojego oryginalnego wydania :używałem niestandardowych nazw sesji (w rzeczywistości id jako nazwy sesji i identyfikator sesji to to samo! ) zgodnie z tym bardzo dobrym postem na StackOverflow a to generowało nazwę sesji o długości 128 znaków. Ponieważ nazwa sesji jest jedynym kluczem, który należy złamać, aby złamać sesję i przejąć kontrolę za pomocą przejmowanie sesji wtedy dłuższa nazwa/identyfikator to bardzo dobra rzecz.
- Wywołało to jednak problem, ponieważ MySQL po cichu dzielił identyfikator sesji do zaledwie 32 znaków zamiast 128, więc nigdy nie był w stanie znaleźć danych sesji w bazie danych. To był całkowicie cichy problem (może z powodu mojej klasy połączenia z bazą danych, która nie wyświetla ostrzeżeń o takich rzeczach). Ale na to należy uważać. Jeśli masz jakiekolwiek problemy z pobieraniem sesji z bazy danych, najpierw sprawdź, czy pełny identyfikator sesji może być przechowywany w podanym polu.
Pomijając to wszystko, należy dodać również kilka dodatkowych szczegółów:
Strona podręcznika PHP (link powyżej) pokazuje nieodpowiedni stos wierszy dla obiektu klasy:
Podczas gdy działa to równie dobrze, jeśli umieścisz to w konstruktorze klasy:
class MySessionHandler implements SessionHandlerInterface {
private $database = null;
public function __construct(){
$this->database = new Database(whatever);
// Set handler to overide SESSION
session_set_save_handler(
array($this, "open"),
array($this, "close"),
array($this, "read"),
array($this, "write"),
array($this, "destroy"),
array($this, "gc")
);
register_shutdown_function('session_write_close');
session_start();
}
...
}
To oznacza, że aby następnie rozpocząć sesję na stronie wyjściowej, wystarczy:
<?php
require "path/to/sessionhandler.class.php";
new MySessionHandler();
//Bang session has been setup and started and works
Dla porównania, kompletna klasa komunikacji sesji jest następująca, działa to z PHP 5.6 (i prawdopodobnie 7, ale jeszcze nie testowane na 7)
<?php
/***
* Created by PhpStorm.
***/
class MySessionHandler implements SessionHandlerInterface {
private $database = null;
public function __construct($sessionDBconnectionUrl){
/***
* Just setting up my own database connection. Use yours as you need.
***/
require_once "class.database.include.php";
$this->database = new DatabaseObject($sessionDBconnectionUrl);
// Set handler to overide SESSION
session_set_save_handler(
array($this, "open"),
array($this, "close"),
array($this, "read"),
array($this, "write"),
array($this, "destroy"),
array($this, "gc")
);
register_shutdown_function('session_write_close');
session_start();
}
/**
* Open
*/
public function open($savepath, $id){
// If successful
$this->database->getSelect("SELECT `data` FROM sessions WHERE id = ? LIMIT 1",$id,TRUE);
if($this->database->selectRowsFoundCounter() == 1){
// Return True
return true;
}
// Return False
return false;
}
/**
* Read
*/
public function read($id)
{
// Set query
$readRow = $this->database->getSelect('SELECT `data` FROM sessions WHERE id = ? LIMIT 1', $id,TRUE);
if ($this->database->selectRowsFoundCounter() > 0) {
return $readRow['data'];
} else {
return '';
}
}
/**
* Write
*/
public function write($id, $data)
{
// Create time stamp
$access = time();
// Set query
$dataReplace[0] = $id;
$dataReplace[1] = $access;
$dataReplace[2] = $data;
if ($this->database->noReturnQuery('REPLACE INTO sessions(id,access,`data`) VALUES (?, ?, ?)', $dataReplace)) {
return true;
} else {
return false;
}
}
/**
* Destroy
*/
public function destroy($id)
{
// Set query
if ($this->database->noReturnQuery('DELETE FROM sessions WHERE id = ? LIMIT 1', $id)) {
return true;
} else {
return false;
}
}
/**
* Close
*/
public function close(){
// Close the database connection
if($this->database->dbiLink->close){
// Return True
return true;
}
// Return False
return false;
}
/**
* Garbage Collection
*/
public function gc($max)
{
// Calculate what is to be deemed old
$old = time() - $max;
if ($this->database->noReturnQuery('DELETE FROM sessions WHERE access < ?', $old)) {
return true;
} else {
return false;
}
}
public function __destruct()
{
$this->close();
}
}
Sposób użycia:jak pokazano tuż nad tekstem kodu klasy.