Хранение PHP сесcий в базе данных

Сегодня я Вам поведаю как хранить сесcии для PHP в базе данных. По умолчанию PHP использует для хранения сессий дисковые файлы(одна сессия в один файл, где название файла – session_id). Файл создается, как только новая сессия начинает, и удаляется, когда сессия заканчивается (или истекает). Это совершенно нормально для большинства обстоятельств, но имеет следующие недостатки:

  1. Если Вы используете разделенный сервер тогда, другие пользователи того сервера могут быть в состоянии получить доступ к вашим файлам сессии, таким образом ставя под угрозу безопасность вашего сайта.
  2. Каждый сервер будет иметь собственную директорию для хранения сесcий и, если Вы используете балансировшик нагрузок, то нет никакой гарантии, что следующий запрос придет на тот сервер, где была создана сесия.
  3. Было бы трудно для администратора выполнить следующие вопросы: «сколько сессий в настоящее время активно?», «какие пользователи в настоящее время логинятся?»

1. Создание таблицы

Сначала давайте создадим таблицу для хранения сеcсий. Для mysql

  1. CREATE TABLE `php_session` (
  2. `session_id` varchar(32) NOT NULL DEFAULT »,
  3. `date_created` datetime NOT NULL DEFAULT ’0000-00-00 00:00:00′,
  4. `last_updated` datetime NOT NULL DEFAULT ’0000-00-00 00:00:00′,
  5. `session_data` longtext,
  6. PRIMARY KEY  (`session_id`),
  7. KEY `last_updated` (`last_updated`)
  8. ) ENGINE=MyISAM

или для postgresql

  1. CREATE TABLE php_session (
  2. session_id character varying(32) DEFAULT »::character varying NOT NULL,
  3. date_created timestamp WITH time zone NOT NULL,
  4. last_updated timestamp WITH time zone NOT NULL,
  5. session_data text
  6. );
  7.  
  8. ALTER TABLE ONLY php_session
  9. ADD CONSTRAINT php_session_pkey PRIMARY KEY (session_id);

Поля

session_id Идентификатор для сесии, первичный ключ.
session_data Поле должно быть достаточно большим что б сохранить $_SESSION данные
date_created Используется что б узнать когда стартовала сесия.
last_updated Используется, что б узнать когда был последний запрос от пользователя. Также это поле будет использоваться для чистки сесии.

2. Создание класа PHP_Session

Создадим класс для работы с сесcиями. Начнем с конструктора

  1. <?php
  2. class php_Session extends Default_Table
  3. {
  4. // ****************************************************************************
  5. // class constructor
  6. // ****************************************************************************
  7. function __construct ()
  8. {
  9. // save directory name of current script
  10. $this->dirname   = dirname(__file__);
  11.  
  12. $this->dbname    = ‘audit’;
  13. $this->tablename = ‘php_session’;
  14.  
  15. } // php_Session

Коротко говоря в конструкторе я инициализирую подключение к базе данных и таблице php_session

Дальше добавим метод open()

  1. function open ($save_path, $session_name)
  2. // open the session.
  3. {
  4. // do nothing
  5. return TRUE;
  6.  
  7. } // open

В нем мы ничего не делаем :)

  1. function close ()
  2. // close the session.
  3. {
  4. if (!empty($this->fieldarray)) {
  5. // perform garbage collection
  6. $result = $this->gc(ini_get(‘session.gc_maxlifetime’));
  7. return $result;
  8. } // if
  9.  
  10. return FALSE;
  11.  
  12. } // close

Метод close() вызывается, когда сесcия закрывается, в данном случае просто запускаем чистильшика сесий (метод gc).

  1. function read ($session_id)
  2. // read any data for this session.
  3. {
  4. $fieldarray = $this->_dml_getData("session_id=’" .addslashes($session_id) ."’");
  5.  
  6. if (isset($fieldarray[0][‘session_data’])) {
  7. $this->fieldarray = $fieldarray[0];
  8. $this->fieldarray[‘session_data’] = »;
  9. return $fieldarray[0][‘session_data’];
  10. } else {
  11. return »;  // return an empty string
  12. } // if
  13.  
  14. } // read

Метод используется для чтения данных из сесcии. В нашем случае выбираем поле по $session_id из таблицы и возращаем все, что есть в session_data.

  1. function write ($session_id, $session_data)
  2. // write session data to the database.
  3. {
  4. if (!empty($this->fieldarray)) {
  5. if ($this->fieldarray[‘session_id’] != $session_id) {
  6. // user is starting a new session with previous data
  7. $this->fieldarray = array();
  8. } // if
  9. } // if
  10.  
  11. if (empty($this->fieldarray)) {
  12. // create new record
  13. $array[‘session_id’]   = $session_id;
  14. $array[‘date_created’] = getTimeStamp();
  15. $array[‘last_updated’] = getTimeStamp();
  16. $array[‘session_data’] = addslashes($session_data);
  17. $this->_dml_insertRecord($array);
  18. } else {
  19. // update existing record
  20. $array[‘last_updated’] = getTimeStamp();
  21. $array[‘session_data’] = addslashes($session_data);
  22. $this->_dml_updateRecord($array, $this->fieldarray);
  23. } // if
  24.  
  25. return TRUE;
  26.  
  27. } // write

Метод write() обновляет или же (если ещё данных в сеcсию не поступало) создает ячейку данных в таблице для данных в сесии по $session_id.

  1. function destroy ($session_id)
  2. // destroy the specified session.
  3. {
  4. $fieldarray[‘session_id’] = $session_id;
  5. $this->_dml_deleteRecord($fieldarray);
  6.  
  7. return TRUE;
  8.  
  9. } // destroy

Метод destroy() используется для уничтожения сеcсии (в нашем случае удаляем ячейку в таблице по $session_id).

  1. function __destruct ()
  2. // ensure session data is written out before classes are destroyed
  3. // (see http://bugs.php.net/bug.php?id=33772 for details)
  4. {
  5.  
  6. } // __destruct

На всяк случай деструктор (по ссылочке со старым PHP данный метод не будет работать без этого).

  1. function gc ($max_lifetime)
  2. // perform garbage collection.
  3. {
  4. $real_now = date(‘Y-m-d H:i:s’);
  5. $dt1 = strtotime("$real_now -$max_lifetime seconds");
  6. $dt2 = date(‘Y-m-d H:i:s’, $dt1);
  7.  
  8. $count = $this->_dml_deleteSelection("last_updated < ‘$dt2‘");
  9.  
  10. return TRUE;
  11.  
  12. } // gc

Метод gc используется для чистки сесий (у которых время жизни прошло). Параметр $max_lifetime указывает сколько может существовать сесия (в секундах). В нашем случае мы удаляем запросом по last_updated < текушее время – $max_lifetime сесии, у которых TTL уже прошло.

Все, наш класс готов. Теперь можем его попытаться использовать

  1. require_once ‘classes/php_session.class.php’;
  2. $session_class = new php_Session;
  3. session_set_save_handler(array(&amp;$session_class, ‘open’),
  4. array(&$session_class, ‘close’),
  5. array(&$session_class, ‘read’),
  6. array(&$session_class, ‘write’),
  7. array(&$session_class, ‘destroy’),
  8. array(&$session_class, ‘gc’));

Вот, теперь можем дальше оперировать с сесиями как не в чем не бывало. Но теперь в таблице появятся приблизительно такие строчки

Вот и все.

В заключение

Вы увидели, что это – относительно простой процесс, чтобы переключить регистрацию данных сессии от обычных дисковых файлов до базы данных. Это преодолевает недостатки, созданые с обычными дисковыми файлами:

  1. Данные сессии более безопасны, поскольку потенциальный хакер должен быть подключен к  базе данных прежде, чем он может получить доступ к чему – нибудь.
  2. Использование многократных серверов не создает для нас проблему, поскольку все данные сессии теперь хранятся в единственном центральном месте и доступны всем серверам.
  3. Намного легче выбрать из базы данных информацию о текущих сессиях или текущих пользователях.

15. сентября 2008 by Alexey Vasiliev
Categories: PHP | Tags: | 5 комментариев

Comments (5)

  1. А где учет того факта, что id-сессии может меняться в течение работы?

  2. И как вы себе это представляете?

  3. Хотелось бы что-то наваять в комментах креативного, но мысль не складывается, так что просто “зачОт”

  4. class php_Session extends Default_Table

    Выложите Default_Table если не сложно

  5. Default_Table – это класс для работы с базой данных, основные методы
    _dml_getData – берет данные
    _dml_insertRecord – вставляет данные в таблицу
    _dml_deleteSelection – удаляет из таблицы данные

    Это все, что вам надо.
    ЗЫ
    Выковырять будет его тяжеловато, я его привязывал к одному проекту.