Categories
PHP

Storing Sessions in Database

By default, sessions are read from files, you can use the session_set_save_handler() function to define custom session handlers to store session data in a database. In this tutorial, we’ll create two code snippets, Procedural and OOP, to manage session data.

There are some good reasons not to store session data in files. Performance might be one issue, with security as another issue. An alternative way to store session information is to use databases. For this, you just need a database table (we named it data_table) with three columns:

  • Column id (primary key) is of type VARCHAR(32) and contains the session ID.
  • Column data is of type TEXT and contains the session data.
  • Column access is of type VARCHAR(14) and contains the time stamp of the most recent access to the session information.

SQL code for creating the session table:

CREATE TABLE `data_table` (
  `id` VARCHAR(32) NOT NULL ,
  `data` TEXT NOT NULL ,
  `access` VARCHAR(14) NOT NULL ,
  PRIMARY KEY (`id`(32)),
  INDEX (`access`(14))
 );

Table structure preview in PhpMyAdmin:

Creating a table in PHPMyAdmin

It doesn’t matter which database is used. Our code uses PDO which supports MySQL, MariaDB, CUBRID, MS SQL Server, Oracle, Firebird, PostgreSQL, SQLite, and some other RDBMS.

The PHP function session_set_save_handler() is used to set the user-level session storage functions for storing and retrieving data associated with a session. This function has two prototypes.

  1. Using callable functions
  2. Using a session handler (i.e. SessionHandlerInterface)

Whatever prototype you use, you need to create custom functions/methods for all relevant six session operations PHP uses internally:

  1. Opening a session
  2. Closing a session
  3. Reading a session variable
  4. Writing a session variable
  5. Destroying a session
  6. Cleaning up (garbage collection, for example removing old session data from the database)

session_set_save_handler(): callable prototype syntax

<?php
 //Syntax # 1
 session_set_save_handler(
  callable $open,
  callable $close,
  callable $read,
  callable $write,
  callable $destroy,
  callable $gc,
  callable $create_sid = ?,
  callable $validate_sid = ?,
  callable $update_timestamp = ?
 ): bool

session_set_save_handler(): sessionhandler prototype syntax

<?php
 //Syntax # 2
 session_set_save_handler(
        object $sessionhandler,
        bool $register_shutdown = true
     ): bool

For all these six operations, the following listings contain code that reads and writes session data from and to a MySQL/MariaDB data source. You need PHP and must set the connection parameters (server, username, password, etc.) appropriately. Then, you include the code from this listing using require_once and use sessions as usual. In the background, PHP saves all session information in the database, not in the file system.

1. Using callable functions and PDO to store session data

Creating sessions.php:

<?php
 //sessions.php
 $dbh = null;
 $db_table = '';

 function open($savePath, $sessionName){
  global $dbh, $db_table;

  $db_name = 'sessions';
  $db_user = 'root';
  $db_pass = '';
  $db_host = 'localhost';
  $db_port = 3306;
  $db_table = 'data_table';
   
  $dsn = "mysql:host=$db_host;port=$db_port;dbname=$db_name";
   
  try {
   $dbh = new PDO($dsn, $db_user, $db_pass);
  } catch (PDOException $e) {
   echo "Error: $e->getMessage()";
   exit;
  }
   return true;
 }

 function close(){
  global $dbh;
  $dbh = null;
  return true;
 }

 function read($id){
  global $dbh, $db_table;
  $q = $dbh->prepare("SELECT data FROM $db_table WHERE id=?");
  $q->bindParam(1, $id);
  if ( $q->execute() ) {
   if ($q->rowCount() == 0)
    return '';
   return $q->fetchColumn();
  }
  return false;
 }

 function write($id, $data){
  global $dbh, $db_table;
  $ts = date('YmdHis');
  $q = $dbh->prepare("REPLACE INTO $db_table VALUES(?, ?, ?)");
  $q->bindParam(1, $id);
  $q->bindParam(2, $data);
  $q->bindParam(3, $ts);

  return $q->execute();
 }

 function destroy($id){
  global $dbh, $db_table;
  $q = $dbh->prepare("DELETE FROM $db_table WHERE id=?");
  $q->bindParam(1, $id);
  return $q->execute();
 }

 function gc($maxlifetime){
  global $dbh, $db_table;
  $ts = date('YmdHis', time() - $maxlifetime);
  $q = $dbh->prepare("DELETE FROM $db_table WHERE access < ?");
  $q->bindParam(1, $ts);
  $q->execute();
  if ( $q->execute() )
   return $q->rowCount();
  return false;
 }
 
 session_set_save_handler('open','close','read','write','destroy','gc');

Including the code from this listing using require_once and use sessions as usual:

<?php
 //example.php

 require_once 'sessions.php';
 session_start();
 $_SESSION['username'] = 'Admin';
 $_SESSION['location'] = 'US';

2. Implementing SessionHandlerInterface to store session data

Creating sessions_class.php:

<?php
 //sessions_class.php
 class DbSessionClass implements SessionHandlerInterface {
  private $dbh;
  private $db_host = 'localhost';
  private $db_port = 3306;
  private $db_name = 'sessions';
  private $db_user = 'root';
  private $db_pass = '';
  private $db_table = 'data_table';

  public function open($savePath, $sessionName): bool {
   $dsn = "mysql:host=$this->db_host;port=$this->db_port;dbname=$this->db_name";

   try {
    $this->dbh = new PDO($dsn, $this->db_user, $this->db_pass);
   } catch (PDOException $e) {
    echo 'Error!: ' . $e->getMessage();
    exit;
   }
   return true;
  }

  public function close(): bool {
   $this->dbh = null;
   return true;
  }

  public function read($id):string|false {
   $q = $this->dbh->prepare("SELECT data FROM $this->db_table WHERE id=?");
   $q->bindParam(1, $id);

   if ( $q->execute() ) {
    if ($q->rowCount() == 0)
     return '';
    return $q->fetchColumn();
   }
   return false;
  }

  public function write($id, $data): bool {
   $ts = date('YmdHis');

   $q = $this->dbh->prepare("REPLACE INTO $this->db_table VALUES(?, ?, ?)");
   $q->bindParam(1, $id);
   $q->bindParam(2, $data);
   $q->bindParam(3, $ts);
   return $q->execute();
  }

  public function destroy($id): bool {
   $q = $this->dbh->prepare("DELETE FROM $this->db_table WHERE id=?");
   $q->bindParam(1, $id);
   return $q->execute();
  }

  public function gc($maxlifetime): int|false {
   $ts = date('YmdHis', time() - $maxlifetime);
   $q = $this->dbh->prepare("DELETE FROM $this->db_table WHERE access < ?");
   $q->bindParam(1, $ts);
   if ( $q->execute() )
    return $q->rowCount();
   return false;
  }
 }
 
 $handler = new DbSessionClass();
 session_set_save_handler($handler, true);

The following figure shows the contents of the database table after data has been written into it:

<?php
 //example.php

 require_once 'sessions_class.php';
 session_start();
 $_SESSION['username'] = 'Admin';
 $_SESSION['location'] = 'US';
PhpMyAdmin: previewing session stored in the table

Cookies and Sessions: