Хранение сеансов в базе данных

Задача
Необходимо хранить данные сеанса не в файле, а в базе данных. Если несколько веб-серверов используют одну и ту же базу данных, то данные сеанса доступны на всех этих веб-серверах.

Решение
Установите опцию session.save_handler в значение user в файле php.ini и используйте класс pc_DB_Session, показанный в примере 8.1. Например:

$s = new pc_DB_Session('mysql://user:password@localhost/db');
ini_get('session.auto_start') or session_start();

Обсуждение
Одна из наиболее сильных сторон модуля сеанса состоит в том, что он способен абстрагировать способ сохранения сеансов. Функция session_set_save_handler() указывает PHP, что различные операции с сеансами, такие как запись сеанса и чтение данных сеанса, следует организовывать посредством разных функций. Класс pc_DB_Session хранит информацию о сеансе в базе данных. Если с этой базой данных совместно работают несколько веб-серверов, то пользовательская информация о сеансе становится доступной на всех этих веб-серверах. Поэтому, если есть группа серверов, находящихся за узлом, регулирующим загрузку, то нет необходимости в каких-либо хитрых уловках для обеспечения корректности пользовательских данных о сеансе, независимо от того, какому серверу они были посланы.

Чтобы использовать pc_DB_Session, передайте имя источника данных (DSN, data source name) классу при его реализации.


Данные сеанса сохраняются в таблице с именем php_session, которая имеет следующую структуру:

CREATE TABLE php_session (
id CHAR(32) NOT NULL,
data MEDIUMBLOB,
last_access INT UNSIGNED NOT NULL,
PRIMARY KEY(id)
)

Если требуется имя таблицы, отличное от php_session, установите значение опции session.save_path в файле php.ini в соответствии с новым именем таблицы. Пример 8.1 демонстрирует класс pc_DB_Session.

Пример 8.1. Класс pc_DB_Session
require 'PEAR.php';
require 'DB.php';
class pc_DB_Session extends PEAR {
var $_dbh;
var $_table;
var $_connected = false;
var $_gc_maxlifetime;
var $_prh_read;
var $error = null;
/**
* Конструктор
*/
function pc_DB_Session($dsn = null) {
if (is_null($dsn)) {
$this->error = PEAR::raiseError('No DSN specified');
return;
}
$this->_gc_maxlifetime = ini_get('session.gc_maxlifetime');
// Сеанс продолжается один день, если не определено другое
if (! $this->_gc_maxlifetime) {
$this->_gc_maxlifetime = 86400;
}
$this->_table = ini_get('session.save_path');
if ((! $this->_table) || ('/tmp' == $this->_table)) {
$this->_table = 'php_session';
}
$this->_dbh = DB::connect($dsn);
if (DB::isError($this->_dbh)) {
$this->error = $this->_dbh;
return;
}
$this->_prh_read = $this->_dbh->prepare(
"SELECT data FROM $this->_table WHERE id LIKE ?
AND last_access >= ?");
if (DB::isError($this->_prh_read)) {
$this->error = $this->_prh_read;
return;
}
if (! session_set_save_handler(array(&$this,'_open'),
array(&$this,'_close'),
array(&$this,'_read'),
array(&$this,'_write'),
array(&$this,'_destroy'),
array(&$this,'_gc'))) {
$this->error = PEAR::raiseError('session_set_save_handler()
failed');
return;
}
return $this->_connected = true;
}
function _open() {
return $this->_connected;
}
function _close() {
return $this->_connected;
}
function _read($id) {
if (! $this->_connected) { return false; }
$sth =
$this->_dbh->execute($this->_prh_read,
array($id,time() - $this->_gc_maxlifetime));
if (DB::isError($sth)) {
$this->error = $sth;
return '';
} else {
if (($sth->numRows() == 1) &&
($ar = $sth->fetchRow(DB_FETCHMODE_ORDERED))) {
return $ar[0];
} else {
return '';
}
}
}
function _write($id,$data) {
$sth = $this->_dbh->query(
"REPLACE INTO $this->_table (id,data,last_access) VALUES (?,?,?)",
array($id,$data,time()));
if (DB::isError($sth)) {
$this->error = $sth;
return false;
} else {
return true;
}
}
function _destroy($id) {
$sth = $this->_dbh->query("DELETE FROM $this->_table WHERE id LIKE ?",
array($id));
if (DB::isError($sth)) {
$this->error = $sth;
return false;
} else {
return true;
}
}
function _gc($maxlifetime) {
$sth = $this->_dbh->query("DELETE FROM $this->_table
WHERE last_access < ?",
array(time() - $maxlifetime));
if (DB::isError($sth)) {
$this->error = $sth;
return false;
} else {
return true;
}
}
}

Метод pc_DB_Session::_write() использует MySQL-специфическую команду SQL, REPLACE INTO, которая обновляет существующую запись или вставляет новую, в зависимости от того, есть ли уже в базе данных запись с этим идентификатором поля.


Если вы используете другую базу данных, модифицируйте функцию write() для выполнения той же самой задачи. Например, удаляем существующую строку (если она есть) и вставляем новую, выполняя все это в одной транзакции:

function _write($id,$data) {
$sth = $this->_dbh->query('BEGIN WORK');
if (DB::isError($sth)) {
$this->error = $sth;
return false;
}
$sth = $this->_dbh->query("DELETE FROM $this->_table WHERE id LIKE ?",
array($id));
if (DB::isError($sth)) {
$this->error = $sth;
$this->_dbh->query('ROLLBACK');
return false;
}
$sth = $this->_dbh->query(
"INSERT INTO $this->_table (id,data,last_access) VALUES (?,?,?)",
array($id,$data,time()));
if (DB::isError($sth)) {
$this->error = $sth;
$this->_dbh->query('ROLLBACK');
return false;
}
$sth = $this->_dbh->query('COMMIT');
if (DB::isError($sth)) {
$this->error = $sth;
$this->_dbh->query('ROLLBACK');
return false;
}
return true;
}.



Оцените статью: (0 голосов)
0 5 0

Статьи из раздела PHP на эту тему:
Аутентификация, основанная на cookies
Буферизация вывода в броузер
Взаимодействие в рамках Apache
Идентификация различных броузеров
Настройка обработки ошибок

Вернуться в раздел: PHP / 8. Основы Web