session_set_save_handler

(PHP 4, PHP 5, PHP 7, PHP 8)

session_set_save_handlerEstablece funciones de almacenamiento de sesiones a nivel de usuario

Descripción

session_set_save_handler(
    callable $open,
    callable $close,
    callable $read,
    callable $write,
    callable $destroy,
    callable $gc,
    callable $create_sid = ?
): bool

Desde PHP 5.4 es posible registrar el siguiente prototipo:

session_set_save_handler(SessionHandlerInterface $sessionhandler, bool $register_shutdown = true): bool

session_set_save_handler() establece las funciones de almacenamiento de sesiones a nivel de usuario que se usan para almacenar y recuperar información asociada con una sesión. Es más útil cuando se prefiere un método de almacenamiento distinto de aquellos proporcionados por las sesiones de PHP. Esto es, almacenar la información de sesión en una base de datos local.

Parámetros

Esta función tiene dos prototipos.

sessionhandler

Una instancia de una clase que implemente SessionHandlerInterface, tal como SessionHandler, para registrarla como el gestor de sesión. Sólo desde PHP 5.4.

register_shutdown

Registrar la función session_write_close() como una función register_shutdown_function().

o
open(string $savePath, string $sessionName)

La llamada de retorno open funciona como un constructor en las clases y es ejecutada cuando la sesión está siendo abierta. Es la primera función de llamada de retorno ejecutada cuando la sesión se inicia automáticamente o manualmente con session_start(). El valor devuelto es true en caso de éxito, false en caso de error.

close()

La llamada de retorno close funciona como un destructor en las clases y es ejecutada después de haber llamado a la llamada de retorno write de la sesión. También se invoca cuando se llama a session_write_close(). El valor devuelto debería ser true en caso de éxito, false en caso de error.

read(string $sessionId)

La llamada de retorno read debe devolver siempre una cadena de sesión (serializada), o una cadena vacía si no existe información que leer.

Esta llamada de retorno es llamda internamente por PHP cuando se inicia la sesión o cuando se llama a session_start(). Antes de invocar a esta llamada de sesión PHP invocará a la llamda de retorno open.

El valor que devuelve esta llamada de retorno debe estar exactamente en el mismo formato de serialización que fue originalmente pasado para el almacenamiento de la llamada de retorno write. El valor devuelto será deserializado automáticamente por PHP y srá usado para rellenar la variable superglobal $_SESSION. Aunque la información parezca similar a serialize(), observe que está en un formato diferente, el cual está especificado en la configuración ini session.serialize_handler.

write(string $sessionId, string $data)

La llamada de retorno write es llamada cuando la sesión necesita ser almacenada y cerrada. Esta llamada de retorno recibe el ID de sesión actual, una versión serializada de la variable superglobal $_SESSION. El método de serialización usado internamente por PHP está especificado en la configuración ini session.serialize_handler.

La información serializada de sesión pasada a esta llamada de retorno debería ser almacenada junto con el ID de sesión pasado. Al recuperar esta información, la llamada de retorno read debe devolver el valor exacto que fue pasado originalmente a la llamda de retorno write.

Esta llamada de retorno es invocada cuando PHP se cierra o cuando se llamda explícitamente a session_write_close(). Observe que después de ejecutar esta función PHP ejecutará internamente la llamada de retorno close.

Nota:

El gestor "write" no se ejecuta hasta después de que se cierre el flujo de salida. Así, la salida desde declaraciones de depuración en el gestor "write" nunca serán vistas en el navegador. Si es necesaria la salida de depuración, se sugiere que la salida de depuración sea escrita en un archivo en su lugar.

destroy($sessionId)

Esta llamada de retorno es ejecutada cuando una sesión es destruida con session_destroy() o con session_regenerate_id() con el parámetro destroy establecido en true. El valor devuelto debería ser true en caso de éxito, false en caso de error.

gc($lifetime)

La llamada de retorno del recolector de basura (Garbage Collector) es invocada internamente por PHP de manera periódica para destruir información antigua de sesiones. La frecuencia es controlada por session.gc_probability y session.gc_divisor. El valor del tiempo de vida (lifetime) que es pasado a esta llamada de retorno puede establecerse en session.gc_maxlifetime. El valor devuelto debería ser true en caso de éxito, false en caso de error.

create_sid()

Esta retrollamada se ejecuta cuando se requiere un nuevo ID de sesión. No se proporcionan parámetros, y el valor devuelto debería ser un string que sea un ID de sesión válido para el gestor utilizado.

Valores devueltos

Devuelve true en caso de éxito o false en caso de error.

Ejemplos

Ejemplo #1 Gestor de seseiones personalizado: véase el código completo en la sinopsis de SessionHandlerInterface.

El siguiente código es para la versión 5.4.0 y superiror de PHP. Aquí se muestra la invocación, el código completo puede verse en la sinopsis de SessionHandlerInterface vinculada más arriba.

Observe que usamos el prototipo de POO con session_set_save_handler() y registramos la función de cierre usando la bandera de parámetro de la función. Esto se recomienda generalmente al registrar objetos como gestores de almacenamiento de sesiones.

<?php
class MySessionHandler implements SessionHandlerInterface
{
// implementar las interfaces aquí
}

$handler = new MySessionHandler();
session_set_save_handler($handler, true);
session_start();

// proceder a estblecer y recuperar valores mediante clave desde $_SESSION

Ejemplo #2 Gestor de almacenamiento de sesiones personalizado usando objetos

El siguiente código es para versiones de PHP inferiores a 5.4.0.

El siguiente ejemplo proporciona un almacenamiento de sesiones basado en fichero similar al gestor de almacenamiento de sesiones de PHP predeterminado files. Este ejemplo podría extenderse fácilmente para cubrir el almacenamiento de bases de datos usando su motor de bases de datos favorito soportado por PHP.

Observe que registramos de forma adicional la función de cierre session_write_close() usando register_shutdown_function() bajo PHP inferior a 5.4.0. Esto se recomienda generalmente al registrar objetos como gestores de almacenamiento de sesiones bajo PHP inferior a 5.4.0.

<?php
class FileSessionHandler
{
private
$savePath;

function
open($savePath, $sessionName)
{
$this->savePath = $savePath;
if (!
is_dir($this->savePath)) {
mkdir($this->savePath, 0777);
}

return
true;
}

function
close()
{
return
true;
}

function
read($id)
{
return (string)@
file_get_contents("$this->savePath/sess_$id");
}

function
write($id, $data)
{
return
file_put_contents("$this->savePath/sess_$id", $data) === false ? false : true;
}

function
destroy($id)
{
$file = "$this->savePath/sess_$id";
if (
file_exists($file)) {
unlink($file);
}

return
true;
}

function
gc($maxlifetime)
{
foreach (
glob("$this->savePath/sess_*") as $file) {
if (
filemtime($file) + $maxlifetime < time() && file_exists($file)) {
unlink($file);
}
}

return
true;
}
}

$handler = new FileSessionHandler();
session_set_save_handler(
array(
$handler, 'open'),
array(
$handler, 'close'),
array(
$handler, 'read'),
array(
$handler, 'write'),
array(
$handler, 'destroy'),
array(
$handler, 'gc')
);

// lo siguiente previene de efectos inesperados al usar objetos como gestores de almacenamiento
register_shutdown_function('session_write_close');

session_start();
// proceder para establecer y recuperar valores por clave desde $_SESSION

Notas

Advertencia

Cuando se usan objetos como gestores de almacenamiento de sesionea es importante registrar la función de cierre con PHP para evitar efectos secundarios inesperados ya que PHP internamente destruye objetos en el cierre y puede prevenir que write y close sean llamados. Normalmente se debería registrar 'session_write_close' usando la función register_shutdown_function().

A partir de PHP 5.4.0 se puede usar session_register_shutdown() o simplemente la bandera 'register shutdown' al invocar session_set_save_handler() usando el método de POO the OOP y pasando una instancia que implemente SessionHandlerInterface.

Advertencia

A partir de PHP 5.0.5 los gestores write y close son llamados después de la destrucción del objeto y por lo tanto no pueden usar objetos o lanzar excepciones. Las excepciones no pueden ser capturadas ya que no serán capturadas ni cualquier rastro de excepción será mostrado y la ejecución se interrumpirá de improviso. Sin embargo, los destructores de objetos pueden usar sesiones.

Es posible llamar a session_write_close() desde el destructor para solucionar este problema de "quién fue antes, si el huevo o la gallina" pero la forma más fiable es registrar la función de ceirre como está descrito más arraba.

Advertencia

El directorio de trabajo actual es cambiado en algunas SAPI si la sesión se cierra al finalizar el script. Es posible cerrar la sesión antes con session_write_close().

Historial de cambios

Versión Descripción
5.5.1 Se añadió el parámetro opcional create_sid.
5.4.0 Se añadió SessionHandlerInterface para la implementación de gestores de sesión y SessionHandler para exponer gestores de sesión internos de PHP.

Ver también

add a note add a note

User Contributed Notes 44 notes

up
45
andreipa at gmail dot com
9 years ago
After spend so many time to understand how PHP session works with database and unsuccessful attempts to get it right, I decided to rewrite the version from our friend stalker.

//Database
CREATE TABLE `Session` (
  `Session_Id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `Session_Expires` datetime NOT NULL,
  `Session_Data` text COLLATE utf8_unicode_ci,
  PRIMARY KEY (`Session_Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
SELECT * FROM mydatabase.Session;

<?php
//inc.session.php

class SysSession implements SessionHandlerInterface
{
    private
$link;
   
    public function
open($savePath, $sessionName)
    {
       
$link = mysqli_connect("server","user","pwd","mydatabase");
        if(
$link){
           
$this->link = $link;
            return
true;
        }else{
            return
false;
        }
    }
    public function
close()
    {
       
mysqli_close($this->link);
        return
true;
    }
    public function
read($id)
    {
       
$result = mysqli_query($this->link,"SELECT Session_Data FROM Session WHERE Session_Id = '".$id."' AND Session_Expires > '".date('Y-m-d H:i:s')."'");
        if(
$row = mysqli_fetch_assoc($result)){
            return
$row['Session_Data'];
        }else{
            return
"";
        }
    }
    public function
write($id, $data)
    {
       
$DateTime = date('Y-m-d H:i:s');
       
$NewDateTime = date('Y-m-d H:i:s',strtotime($DateTime.' + 1 hour'));
       
$result = mysqli_query($this->link,"REPLACE INTO Session SET Session_Id = '".$id."', Session_Expires = '".$NewDateTime."', Session_Data = '".$data."'");
        if(
$result){
            return
true;
        }else{
            return
false;
        }
    }
    public function
destroy($id)
    {
       
$result = mysqli_query($this->link,"DELETE FROM Session WHERE Session_Id ='".$id."'");
        if(
$result){
            return
true;
        }else{
            return
false;
        }
    }
    public function
gc($maxlifetime)
    {
       
$result = mysqli_query($this->link,"DELETE FROM Session WHERE ((UNIX_TIMESTAMP(Session_Expires) + ".$maxlifetime.") < ".$maxlifetime.")");
        if(
$result){
            return
true;
        }else{
            return
false;
        }
    }
}
$handler = new SysSession();
session_set_save_handler($handler, true);
?>

<?php
//page 1
require_once('inc.session.php');

session_start();

$_SESSION['var1'] = "My Portuguese text: SOU Gaucho!";
?>

<?php
//page 2
require_once('inc.session.php');

session_start();

if(isset(
$_SESSION['var1']){
echo
$_SESSION['var1'];
}
//OUTPUT: My Portuguese text: SOU Gaucho!
?>
up
4
ohcc at 163 dot com
7 years ago
What is not documented is that callables $validate_sid and $update_timestamp are supported since PHP 7.0. for the
prototype of "bool 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 ]]] )".

validate_sid($sessionId)
  This callback is to validate $sessionId. Its return value should be true for valid session id $sessionId or false for invalid session id $sessionId. If false is returned, a new session id is generated to replace the invalid session id $sessionId.

update_timestamp($sessionId)
  This call back is to update timestamp, and its return value should be true for success or false for failure.

If you use this prototype, if you provide less than 6 parameters or if you provide more parameters than session_set_save_handler() accepts, you will get a "Wrong parameter count for session_set_save_handler()" warning.

If you use the OOP prototype of session_set_save_handler(SessionHandlerInterface $sessionhandler [, bool $register_shutdown = true ] ), a member method named neither validate_sid nor update_timestamp of the class of $sessionhandler are not invoked even in PHP 7.2, but a member method named create_sid is supported as of PHP 5.5.1.

It's 16th December, 2017 today, the documetation even PHP may get updated sometime afterwards.
up
3
peter at brandrock dot co dot za
6 years ago
If saving to a database, as in the examples on this page, for performance, consider the following.

Build the Sessions table with an index on the SessionExpires column to quickly identify rows to be deleted in the garbage collection phase.

Rather do a garbage collection "delete from sessions where expiresOn < $now" on every session start/open. If you have an index on expiry time, this will not be a big hit, and evens out the load across all users. If it is possible that a large number of sessions will expire at the same time, include a "limit 100" clause, set for whatever number is reasonable, so that each user shares the load.

Use a varchar rather than Text to store the data, as Text will store the column off-page and is retrieved slightly slower. Use Text only if your application really does store large amounts of text in the session.
up
15
ohcc at 163 dot com
7 years ago
As of PHP 7.0, you can implement SessionUpdateTimestampHandlerInterface to
define your own session id validating method like validate_sid and the timestamp updating method like update_timestamp in the non-OOP prototype of session_set_save_handler().

SessionUpdateTimestampHandlerInterface is a new interface introduced in PHP 7.0, which has not been documented yet. It has two abstract methods: SessionUpdateTimestampHandlerInterface :: validateId($sessionId) and SessionUpdateTimestampHandlerInterface :: updateTimestamp($sessionId, $sessionData).

<?php
   
/*
       @author Wu Xiancheng
       Code structure for PHP 7.0+ only because SessionUpdateTimestampHandlerInterface is introduced in PHP 7.0
       With this class you can validate php session id and update the timestamp of php session data
       with the OOP prototype of session_set_save_handler() in PHP 7.0+
    */
   
class PHPSessionXHandler implements SessionHandlerInterface, SessionUpdateTimestampHandlerInterface {
        public function
close(){
           
// return value should be true for success or false for failure
            // ...
       
}
        public function
destroy($sessionId){
           
// return value should be true for success or false for failure
            // ...
       
}
        public function
gc($maximumLifetime){
           
// return value should be true for success or false for failure
            // ...
       
}
        public function
open($sessionSavePath, $sessionName){
           
// return value should be true for success or false for failure
            // ...
       
}
        public function
read($sessionId){
           
// return value should be the session data or an empty string
            // ...
       
}
        public function
write($sessionId, $sessionData){
           
// return value should be true for success or false for failure
            // ...
       
}
        public function
create_sid(){
           
// available since PHP 5.5.1
            // invoked internally when a new session id is needed
            // no parameter is needed and return value should be the new session id created
            // ...
       
}
        public function
validateId($sessionId){
           
// implements SessionUpdateTimestampHandlerInterface::validateId()
            // available since PHP 7.0
            // return value should be true if the session id is valid otherwise false
            // if false is returned a new session id will be generated by php internally
            // ...
       
}
        public function
updateTimestamp($sessionId, $sessionData){
           
// implements SessionUpdateTimestampHandlerInterface::validateId()
            // available since PHP 7.0
            // return value should be true for success or false for failure
            // ...
       
}
    }
?>
up
5
Steven George
10 years ago
Note that as well as destructing objects before calling write() and close(), it seems PHP also destroys classes.  That is, you can't even call a static method of an external class in the write() and close() handlers - PHP will issue a Fatal error stating "Class xxxx not found"
up
3
tomas at slax dot org
16 years ago
Regarding the SAPIs: The warning mentioned in function's description (that the Current working directory is changed with some SAPIs) is very important.

It means that if your callback 'write' function needs to write to a file in current directory, it will not find it. You have to use absolute path and not rely upon the current working directory.

I thought this warning applies only to some strange environments like Windows, but it happens exactly on Linux + Apache 2.2 + PHP 5.
up
2
polygon dot co dot in at gmail dot com
3 years ago
Below is a demo to check the order in which session function executes.

<?php

ini_set
('session.use_strict_mode',true);

function
sess_open($sess_path, $sess_name) {
    echo
'<br/>sess_open';
    return
true;
}

function
sess_close() {
    echo
'<br/>sess_close';
    return
true;
}

function
sess_read($sess_id) {
    echo
'<br/>sess_read';
    return
'';
}

function
sess_write($sess_id, $data) {
    echo
'<br/>sess_write';
    return
true;
}

function
sess_destroy($sess_id) {
    echo
'<br/>sess_destroy';
    return
true;
}

function
sess_gc($sess_maxlifetime) {
    echo
'<br/>sess_gc';
    return
true;
}

function
sess_create_sid() {
    echo
'<br/>sess_create_sid';
    return
'RNS'.rand(0,10);
}

function
sess_validate_sid($sess_id) {
    echo
'<br/>sess_validate_sid';
    return
true;
}

function
sess_update_timestamp($sess_id,$data) {
    echo
'<br/>sess_update_timestamp';
    return
true;
}

session_set_save_handler(
   
'sess_open',
   
'sess_close',
   
'sess_read',
   
'sess_write',
   
'sess_destroy',
   
'sess_gc',
   
'sess_create_sid',
   
'sess_validate_sid',
   
'sess_update_timestamp'
);

session_start();

echo
'<br/>code here...';

?>

O/P Below when above code executed first time.
sess_open
sess_create_sid
sess_read
code here...
sess_write
sess_close

O/P Below for next execution.
sess_open
sess_validate_sid
sess_read
code here...
sess_write
sess_close
up
2
tony at marston-home dot demon dot co dot uk
6 years ago
Your custom session handler should not contain calls to any of the session functions, such as session_name() or session_id(), as the relevant values are passed as arguments on various handler methods. Attempting to obtain values from alternative sources may not work as expected.
up
2
centurianii at yahoo dot co dot uk
7 years ago
Adding to the very useful class from: andreipa at gmail dot com

1. You should handle session expiration & data I/O from the SessionHandlerInterface methods,
2. You should NOT handle session regeneration and data modification from these methods but from a static method, e.g. sth like Session::start().
3. PHP gives a lot of examples but does NOT say what's the perspective under which one should work.

A skeleton of such a class:
namespace xyz;
class Session implements \SessionHandlerInterface, Singleton {
   /** @var SessionToken $token The SessionToken of this command;
          this is part of my programming approach */
   protected $token;
   /** @var PDO $dbh The PDO handler to the database */
   protected $dbh;
   /** @var $savePath Where sessions are stored */
   protected $savePath;
   /** @var $type Type of sessions (['files'|'sqlite']) */
   protected $type;
   /** @var self $instance An instance of this class */
   static private $instance = null;

   private function __construct() { ... }
   static public function getInstance() {
      if (self::$instance === null) {
         self::$instance = new self();
      }
      return self::$instance;
   }
   public function open($savePath, $sessionName) { ... }
   public function close() {
      if ($this->type == static::FILES) {
         return true;
      } elseif ($this->type == static::SQLITE) {
         return true;
      }
   }
   public function read($id) { ... }
   public function write($id, $data) { ... }
   public function destroy($id) { ... }
   public function gc($maxlifetime) { ... }
   static public function get($key) {
      return (isset($_SESSION[$key]))? $_SESSION[$key] : null;
   }
   static public function set($key, $value) {
      return $_SESSION[$key] = $value;
   }
   static public function newId() {...}
   static public function start($call = null, $log = false) {
      //1. start session (send 1st header)
      if (session_status() != PHP_SESSION_ACTIVE) {
         session_start();   //calls: open()->read()
      }

      //2. $_SESSION['session']: array of session control data
      // existed session
      if (is_array(static::get('session'))) {
         $session = static::get('session');
      // new session
      } else {
         $session = array();
      }

      $tmp = $_SESSION;
      //do sth with $session array...
      static::set('session', $session);
      session_write_close();   //calls: write()->read()->close()
      //create a new session inside if...else...
      session_id(static::newId());
      session_start();   //calls: open()->read()
      //if you want previous session data to be copied:
      //$_SESSION = $tmp;
      //do sth else with $session array and save it to new session...
      static::set('session', $session);

      //6. call callback function (only on valid/new sessions)
      if ($call)
         $call();
      session_write_close();   //calls: write()->read()->close()
   }
   /**
    * Defines custom session handler.
    */
   static public function setHandler() {
      // commit automatic session
      if (ini_get('session.auto_start') == 1) {
         session_write_close();
      }
      $handler = static::getInstance();
      session_set_save_handler($handler, true);
   }
}

Let's start a session:
Session::setHandler();
Session::start();

Trying for hours to trace my error where the 3rd Session::read() ended to use a null Session::dbh until I realized that Session::close() should NOT destroy properties of this class!
Also I avoid the use of session_create_id() as it's only for PHP 7 >= 7.1.0 and I use in place a static Session::newId().
up
2
shanikawm at gmail dot com
8 years ago
Here is a class to handle session using an Oracle table.
https://github.com/shanikawm/PHP_Oracle_Based_Session_Handler_Class

<?php
/**
* By Shanika Amarasoma
* Date: 6/24/2016
* PHP session handler using Oracle database
* Oracle Create table statement
        CREATE TABLE PHP_SESSIONS
        (
            SESSION_ID  VARCHAR2(256 BYTE) UNIQUE,
            DATA        CLOB,
            TOUCHED     NUMBER(38)
        );
*/
class session_handler implements SessionHandlerInterface
{
    private
$con;
    public function
__construct() {
        if(!
$this->con=oci_pconnect(DBUSER,DBPASS,CONNECTION_STR)){
            die(
'Database connection failed !');
        }
    }
    public function
open($save_path ,$name){
        return
true;
    }
    public function
close(){
        return
true;
    }
    public function
read($session_id){
       
$query = "SELECT \"DATA\" FROM PHP_SESSIONS WHERE SESSION_ID=Q'{" . $session_id . "}'";
       
$stid = oci_parse($this->con, $query);
       
oci_execute($stid, OCI_DEFAULT);
       
$row = oci_fetch_array($stid, OCI_ASSOC + OCI_RETURN_LOBS);
       
oci_free_statement($stid);
        return
$row['DATA'];
    }
    public function
write($session_id,$session_data){
       
$dquery="DELETE FROM PHP_SESSIONS WHERE SESSION_ID=Q'{".$session_id."}'";
       
$dstid = oci_parse($this->con,$dquery);
       
oci_execute($dstid, OCI_NO_AUTO_COMMIT);
       
oci_free_statement($dstid);
       
$query="INSERT INTO PHP_SESSIONS(SESSION_ID,TOUCHED,\"DATA\") VALUES(Q'{".$session_id."}',".time().",EMPTY_CLOB()) RETURNING \"DATA\" INTO :clob";
       
$stid = oci_parse($this->con,$query);
       
$clob=oci_new_descriptor($this->con,OCI_D_LOB);
       
oci_bind_by_name($stid, ':clob', $clob, -1, OCI_B_CLOB);
        if(!
oci_execute($stid, OCI_NO_AUTO_COMMIT)){
            @
oci_free_statement($stid);
            return
false;
        }
        if(
$clob->save($session_data)){
           
oci_commit($this->con);
           
$return=true;
        } else {
           
oci_rollback($this->con);
           
$return=false;
        }
       
$clob->free();
       
oci_free_statement($stid);
        return
$return;
    }
    public function
destroy($session_id){
       
$query="DELETE FROM PHP_SESSIONS WHERE SESSION_ID=Q'{".$session_id."}'";
       
$stid = oci_parse($this->con,$query);
       
oci_execute($stid, OCI_DEFAULT);
       
$rows=oci_num_rows($stid);
       
oci_commit($this->con);
       
oci_free_statement($stid);
        if(
$rows>0){
            return
true;
        } else {
            return
false;
        }
    }
    public function
gc($maxlifetime){
       
$query="DELETE FROM PHP_SESSIONS WHERE TOUCHED<".(time()-$maxlifetime);
       
$stid = oci_parse($this->con,$query);
       
oci_execute($stid, OCI_DEFAULT);
       
$rows=oci_num_rows($stid);
       
oci_commit($this->con);
       
oci_free_statement($stid);
        if(
$rows>0){
            return
true;
        } else {
            return
false;
        }
    }
}
session_set_save_handler(new session_handler(), true);
session_start();
up
2
korvus at kgstudios dot net
19 years ago
It seems when you call 'session_name()', php loads the session id automatically from GET ( if the index exists ) and passes it to the 'read' callback method correctly, but the 'write' callback is invoked twice: first the auto-generated session id, then the custom session id

So be aware of what queries you execute inside the callback .. I got crazy because I used a MySQL 'REPLACE' statement to agilize, and I spent a lot of hours trying to understand why 2 rows instead of 1 were being affected ( the first id was inserting, the second updating )

I hope this helps!
up
3
ivo at magstudio dot net
22 years ago
Just a few words to explain some troubles while using session_set_save_handler(). It appears that internally PHP calls session management functions in this order: open(), read(), write(), close(). Close() function is called even if you does not make a call to sesison_start(), perhaps for some reasons like cleaning.
   If you try to redefine these functions and call sessions_set_save_handler() but something doesn't work, (in my case the ssion data hasn't been written) it's a good idea to debug them in the order they are called. They doesn't produce error output to browser but you can use print or echo.
   Shortly, if your write() function doesn't work as expected take a look for errors in previous functions - open() and read().
   I hope that this will help to save someone few hours debugging.
up
5
oliver at teqneers dot de
19 years ago
For some people it might be important to know, that if the standard session handler has been overwritten with session_set_save_handler, no locking is working anymore (between session_read and session_write). The following might happen:

script "A" start         .
read session data        .
.                        script "B" start
.                        read session data
running (30secs)         add session data
.                        write sesion data
.                        script "B" stop
write session data       .
script "A" stop          .

If a script "A" runs for a long time (say 30secs) the same user might start another script "B", which also uses a session. Script "B" will start and read session data, even though script "A" is still running. Because script "B" is much faster it will finish its work and write back its session data before script "A" has ended. Now script "A" ends and overwrites all of script "B"'s session data. If you DON'T use session_set_save_handler, this cannot happend, because in this case, PHP will not start script "B" until script "A" ends.
up
4
e dot sand at elisand dot com
15 years ago
The "binary" data that is in the session data appears to surround class/object names, and if you pass your session data through a function to sanitize it for SQL injection, you may indeed run in to problems.

For example, using the PDO::quote() function to prepare the data for injection (in my case for SQLite3), it was stopping short as soon as it encountered the first bit of binary data, causing my session information to be corrupted.

This change *must* have happened somewhere in the 5.2 series, because I just started encountering this problem recently on a code base that had been tested & working on earlier versions of PHP 5.2.

This may in fact be a bug - I have not yet checked... but beware, and perhaps using base64 to encode/decode your session data is a good thing to do just to be sure (though you are now left unable to visually inspect serialized session information at the storage level which is a rather big problem for on-the-fly debugging of sessions).
up
4
james at dunmore dot me dot uk
17 years ago
I think it is very important here to stress that the WRITE method should use UPDATE+INSERT (or mysql specific REPLACE).

There is example code "out there" that uses just UPDATE for the write method, in which case, when session_regenerate_id is called, session data is lost (as an update would fail, as the key has changed).

I've just wasted a whole day due to this (I know I should have thought it through / RTFM, but it is an easy trap to fall into).
up
3
frank at interactinet dot com
13 years ago
I had trouble with committing session data.
To "commit and continue" without closing your session, put this at the top of your "write" method:

<?php

$id
= session_id();
session_write_close();
session_id($id);
session_start();

?>

Note that ANY time php generates a new session id, it is not automatically updated in a database. This can be helpful:

<?php

public function resetSessionId()
{
   
$old = session_id();
   
session_regenerate_id();
   
$new = session_id();
   
SessionHandler::regenerate_id($old,$new);
}

public function
regenerate_id($old,$new)
{
   
$db = mysqli->connect(...);

   
$db->query('UPDATE sessions SET session_id = \''.$db->escape_string($new).'\'
    WHERE session_id = \''
.$db->escape_string($old).'\'');
}
?>
up
4
carlos dot vini at gmail dot com
8 years ago
If you have a custom handler registered ini_get('session.save_handler') will return 'user' instead of 'file'
up
5
matt at openflows dot org
18 years ago
Note that for security reasons the Debian and Ubuntu distributions of php do not call _gc to remove old sessions, but instead run /etc/cron.d/php*, which check the value of session.gc_maxlifetime in php.ini and delete the session files in /var/lib/php*.  This is all fine, but it means if you write your own session handlers you'll need to explicitly call your _gc function yourself.  A good place to do this is in your _close function, like this:

<?php
function _close() {
   
_gc(get_cfg_var("session.gc_maxlifetime"));
  
// rest of function goes here
}
?>
up
2
dummynick at gmail dot com
14 years ago
I was getting Fatal error: Exception thrown without a stack frame and it took days to figure out the reason. I am using memcache to store sessions and in my custom class I use Memcache class in write method.

I put the code in the write method inside try-catch block and it solved my problem.
up
2
joel the usual at sign then purerave.com
15 years ago
When storing sessions in a DB, it's usually beneficial to use an existing custom DB object, but this creates problems with the latest version of PHP 5.3.1. This used to work fine on PHP 5.2.x (Linux and Windows).

The problem now is that session_write_close() is not automatically called when execution ends, but rather after all the objects have been destructed, including the DB object!

There are two ways around this, either manually calling session_write_close() at the end of your script(s), or not using the DB object.

I'm sure this is the intended behavior from the beginning.
up
4
yangqingrong at gmail dot com
15 years ago
session_set_save_handler is used before session_start.if your session is setted as auto start. it will return FALSE value.so you need add session_write_close() before session_set_save_handler to cancel the session's auto start.it  likes this:

<?php
/*
qq:290359552
*/
session_write_close(); //cancel the session's auto start,important

function open()
{
   ...
}
....
session_set_save_handler( ... );
session_start();
?>
up
1
bart2yk at yahoo dot com
14 years ago
You can call the session_write in db object destructor to be shore that you still have a connection to mysql and the session is write.
up
1
sneakyimp AT hotmail DOT com
18 years ago
the behavior, return values, and exact time of calling for these functions is pretty poorly documented here.  i thought folks might like to know that:

1) calling session_start() triggers PHP to first call your open function and then call your read function before resuming with the code immediately following your session_start() call.

2) calling session_id('some_value') within your open function WILL NOT SET THE SESSION COOKIE (at least not on my setup - PHP 4.4.1).  Assuming you defined some function to validate a session id called my_func(), you might want to do something like this in your open function:

<?php
 
function _open($save_path, $session_name) {
   
// check for session id
   
$sess_id = session_id();
    if (empty(
$sess_id) || !myfunc($sess_id)) {
     
//session_id is INVALID - generating new
     
$new_id = md5(uniqid("some random seed here"));
     
session_id($new_id);
     
setcookie(session_name(),
                   
$new_id,
                   
0,
                   
"/",
                   
".mydomain.com");
      }

    return
true;
  }
// _open()
?>
up
5
harald at hholzer at
15 years ago
after spending 8 hours to find out whats going on..

just for the records, because php.net ignore the real world out there:

debian 5 installs by default the php-suhosin module, which changes the behavior of session_set_save_handler read/write function.

on calling the session write function the session data will be encrypted, and the returning string from the read function are decrypted and verified.

the encrypted data is no more compatible with session_encode/session_decode.

and breaks by default, subdomain handling and multiple host setups where different document roots are used.

for futher information look at:
http://www.hardened-php.net/suhosin/configuration.html

session sample data (debian 4):
test|s:3:"sdf";

session sample data (debian 5, with php-suhosin):
3GdlPEGr2kYgRFDs-pUSoKomZ4fN7r5BM5oKOCMsWNc...

i thing the suhosin patch should report a warning in case of invalid session data, to get a clue whats going wrong.
up
2
james dot ellis at gmail dot com
16 years ago
When writing your own session handler, particularly database session handlers, play close attention to garbage cleanup and how it could affect server load.

To pick a round number example:

If you have 1000 requests per minute on session enabled pages, everyone needs a session started but the session garbage cleanup does not need to run every request. Doing so would cause unrequired queries on the database server.

In this example, setting your probability/divisor to 1/1000 would be sufficient to clean up old sessions at a minimum once a minute. If you don't need that kind of granularity, increase the gc divisor.

Finding the tradeoff between clearing up old sessions and server load is the important aspect here.
up
2
information at saunderswebsolutions dot com
18 years ago
Note that if session.auto_start is set to On in the php.ini, your session_set_save_handler will return false as the session has already been initialized.

If you are finding that your code works OK on one machine but doesn't work on another, check to see if session.auto_start is set to On
up
8
stalker at ruun dot de
18 years ago
object- and mysql-based session-handler, requires the following table:

CREATE TABLE `ws_sessions` (
  `session_id` varchar(255) binary NOT NULL default '',
  `session_expires` int(10) unsigned NOT NULL default '0',
  `session_data` text,
  PRIMARY KEY  (`session_id`)
) TYPE=InnoDB;

<?php
class session {
   
// session-lifetime
   
var $lifeTime;
   
// mysql-handle
   
var $dbHandle;
    function
open($savePath, $sessName) {
      
// get session-lifetime
      
$this->lifeTime = get_cfg_var("session.gc_maxlifetime");
      
// open database-connection
      
$dbHandle = @mysql_connect("server","user","password");
      
$dbSel = @mysql_select_db("database",$dbHandle);
      
// return success
      
if(!$dbHandle || !$dbSel)
           return
false;
      
$this->dbHandle = $dbHandle;
       return
true;
    }
    function
close() {
       
$this->gc(ini_get('session.gc_maxlifetime'));
       
// close database-connection
       
return @mysql_close($this->dbHandle);
    }
    function
read($sessID) {
       
// fetch session-data
       
$res = mysql_query("SELECT session_data AS d FROM ws_sessions
                            WHERE session_id = '
$sessID'
                            AND session_expires > "
.time(),$this->dbHandle);
       
// return data or an empty string at failure
       
if($row = mysql_fetch_assoc($res))
            return
$row['d'];
        return
"";
    }
    function
write($sessID,$sessData) {
       
// new session-expire-time
       
$newExp = time() + $this->lifeTime;
       
// is a session with this id in the database?
       
$res = mysql_query("SELECT * FROM ws_sessions
                            WHERE session_id = '
$sessID'",$this->dbHandle);
       
// if yes,
       
if(mysql_num_rows($res)) {
           
// ...update session-data
           
mysql_query("UPDATE ws_sessions
                         SET session_expires = '
$newExp',
                         session_data = '
$sessData'
                         WHERE session_id = '
$sessID'",$this->dbHandle);
           
// if something happened, return true
           
if(mysql_affected_rows($this->dbHandle))
                return
true;
        }
       
// if no session-data was found,
       
else {
           
// create a new row
           
mysql_query("INSERT INTO ws_sessions (
                         session_id,
                         session_expires,
                         session_data)
                         VALUES(
                         '
$sessID',
                         '
$newExp',
                         '
$sessData')",$this->dbHandle);
           
// if row was created, return true
           
if(mysql_affected_rows($this->dbHandle))
                return
true;
        }
       
// an unknown error occured
       
return false;
    }
    function
destroy($sessID) {
       
// delete session-data
       
mysql_query("DELETE FROM ws_sessions WHERE session_id = '$sessID'",$this->dbHandle);
       
// if session was deleted, return true,
       
if(mysql_affected_rows($this->dbHandle))
            return
true;
       
// ...else return false
       
return false;
    }
    function
gc($sessMaxLifeTime) {
       
// delete old sessions
       
mysql_query("DELETE FROM ws_sessions WHERE session_expires < ".time(),$this->dbHandle);
       
// return affected rows
       
return mysql_affected_rows($this->dbHandle);
    }
}
$session = new session();
session_set_save_handler(array(&$session,"open"),
                         array(&
$session,"close"),
                         array(&
$session,"read"),
                         array(&
$session,"write"),
                         array(&
$session,"destroy"),
                         array(&
$session,"gc"));
session_start();
// etc...
?>
up
2
Rusty X
12 years ago
It is important to understand that PHP's default file-based session handling LOCKS the session file, inherently allowing ONLY ONE thread handling any given session at a time.
When you implement a DB-backed session storage and you do not do any locking, you may run into situations where more than one thread is serving the same session, and you may LOSE DATA because the second thread will overwrite any session changes done by the first thread.
You should therefore think about locking the session somehow if you want to have the exact same behavior as with the default file-based implementation. For example, with InnoDB you could do a SELECT ... FOR UPDATE, or you can use the GET_LOCK() function.
up
4
pavelc at users dot sourceforge dot net
13 years ago
I write a class that unites whole handler functionality. It's not even needed to save instances of this class in variables. Just add a row:
<?php
new SessionSaveHandler();
?>
and the handler will rule the sessions ;-)
<?php

class SessionSaveHandler {
    protected
$savePath;
    protected
$sessionName;

    public function
__construct() {
       
session_set_save_handler(
            array(
$this, "open"),
            array(
$this, "close"),
            array(
$this, "read"),
            array(
$this, "write"),
            array(
$this, "destroy"),
            array(
$this, "gc")
        );
    }

    public function
open($savePath, $sessionName) {
       
$this->savePath = $savePath;
       
$this->sessionName = $sessionName;
        return
true;
    }

    public function
close() {
       
// your code if any
       
return true;
    }

    public function
read($id) {
       
// your code
   
}

    public function
write($id, $data) {
       
// your code
   
}

    public function
destroy($id) {
       
// your code
   
}

    public function
gc($maxlifetime) {
       
// your code
   
}
}

new
SessionSaveHandler();

?>
up
1
cmanley
11 years ago
Below is a session id value validator I just wrote. It is especially important to validate session id cookie values when using a custom file based validator, otherwise hackers could potentially trick it into overwriting non-session files.

/**
* Validates the value (the session id) of a session cookie.
* Useful for detecting potential hack attempts.
* It is up to the caller to delete the cookie if necessary.
* See also: http://lxr.php.net/xref/PHP_TRUNK/ext/session/session.c#php_session_valid_key
*
* @param string $value
* @param boolean $debug
* @return boolean
*/
function session_validate($cookie_value, $debug = false) {
    // session.hash_function allows you to specify the hash algorithm used to generate the session IDs. '0' means MD5 (128 bits) and '1' means SHA-1 (160 bits). Since PHP 5.3.0 it is also possible to specify any of the algorithms provided by the hash extension (if it is available), like sha512 or whirlpool. A complete list of supported algorithms can be obtained with the hash_algos() function.
    // session.hash_bits_per_character allows you to define how many bits are stored in each character when converting the binary hash data to something readable. The possible values are '4' (0-9, a-f), '5' (0-9, a-v), and '6' (0-9, a-z, A-Z, "-", ",").
    if (!(isset($cookie_value) && is_string($cookie_value) && strlen($cookie_value))) {
        return false;
    }
    $bits = null;
    if (1) {
        $hash_function = ini_get('session.hash_function');
        $hash_function_to_bits = array(
            0    => 128,
            1    => 160,
        );
        $bits = @$hash_function_to_bits[$hash_function];
    }
    $bits_per_char = ini_get('session.hash_bits_per_character');
    $bits_per_char_to_charclass = array(
        4    => '0-9a-f',
        5    => '0-9a-v',
        6    => '0-9a-zA-Z\-,',    // this is also the default
    );
    $charclass = array_key_exists($bits_per_char, $bits_per_char_to_charclass) ? $bits_per_char_to_charclass[$bits_per_char] : $bits_per_char_to_charclass[6];
    $charlength = $bits ? (integer)ceil($bits / $bits_per_char) : '1,128'; // the last value is a somewhat arbitrary default
    $re = '/^[' . $charclass . ']{' . $charlength . '}$/';
    $result = preg_match($re, $cookie_value);
    $debug && error_log(__FUNCTION__ . ' regexp: ' . $re . "\tresult: " .intval($result));
    return $result;
}
up
1
mixailo at mercenaries dot ru
17 years ago
It is useful to use MEMORY storage engine in MySQL while handling sessions.
http://dev.mysql.com/doc/refman/5.0/en/memory-storage-engine.html
up
1
Balu
20 years ago
If a session is closed the save-handlers seem to be resetted (in PHP 4.1.2 they are), so you need to run session_set_save_handler() again after e.g. running session_write_close() and restarting the session with session_start();
up
1
skds1433 at hotmail dot com
15 years ago
I pulled a really stupid move. If you are trying to debug your garbage collector, make sure you call the following >>> BEFORE <<< "session_start":

<?php
ini_set
('session.gc_probability', 100);
ini_set('session.gc_divisor', 100);
?>

I was sure it was a bug in PHP, but turned out (like 99% of the time) to be me own fault.
up
1
biba dot vasyl at gmail dot com
10 months ago
if you use the interface (SessionHandlerInterface) to implement the storage of the session in the mysql database, it is not clear what the read method should return, because the return is specified in the interface: string|false, that is, if I refer to the type:
public function read(string $id): string|false {
      if ($id) {
            $sql = "SELECT `session_id`, `data` FROM `session` WHERE `session_id` = '" . $this->db->escape($id) . "'";
            pp($sql);
            $query = $this->db->query($sql);
            if ($query->num_rows) {
                //return (isset($query->one['data']) ? (array)json_decode($query->one['data'], true) : []);
                return $query->one['session_id'];
            } else {
                return '';
            }
      }
      return false;
}
ref.: https://www.php.net/manual/ru/sessionhandlerinterface.read.php
then I will receive an error response of the following plan: Error #: 2, message: session_start(): Failed to decode session object. Session has been destroyed
up
1
polygon dot co dot in at gmail dot com
1 year ago
Although, session_set_save_handler() has support for saving session data via other modes, This does not support a way for saving session data in COOKIES. This is required for a site with a huge concurrent request the recommended solution by session_set_save_handler() does not fit the load site is handling.
So, the other way around to do this is as below.

<?php
// start.php
ob_start(); // Turn on output buffering

$sessCookieName = session_name();
$_SESSION = json_decode(base64_decode($_COOKIE[$sessCookieName]), true);

// Code
function echosess() {
    echo
$_SESSION['id'];
}
echosess();
$_SESSION['id'] = 1;

// end.php
$op = ob_get_clean(); // Get current buffer contents and delete current output buffer
$encryptedData = base64_encode(json_encode($_SESSION));
setcookie($sessCookieName, $encryptedData, time() + (ini_get("session.gc_maxlifetime")), '/');
echo
$op;
?>

If more security is required about the data of the session in cookie, then key based encryption/decryption can solve the issue.
up
2
dimzon541 at gmail dot com
9 years ago
Persisting PHP sessions into mongodb (allows NLB without affinity)
https://gist.github.com/dimzon/62eeb9b8561bcb9f0c6d
up
0
polygon dot co dot in at gmail dot com
1 year ago
How to use Cookie for managing session data with encryption.

<?php
class MySessionHandler implements SessionHandlerInterface
{
    function
__construct()
    {
       
// Store the key and IV somewhere safe
        //$key = openssl_random_pseudo_bytes(32); // 256-bit key
        //$iv = openssl_random_pseudo_bytes(16); // 128-bit IV
       
        // Store the base64 key and IV somewhere safe
        //$key_base64 = base64_encode($key);
        //$iv_base64 = base64_encode($vi);

        // Use the store base64 key and IV below
       
$key_base64 = 's8Livn/jULM6HDdPY76E3aXtfELdleTaqOC8HgTfW7M=';
       
$iv_base64 = 'nswqKP23TT+deVNuaV5nXQ==';
       
$this->key = base64_decode($key_base64);
       
$this->iv = base64_decode($iv_base64);
    }

   
// Encryption
   
function encryptSess($plaintext)
    {
        return
openssl_encrypt($plaintext, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA, $this->iv);   
    }

   
// Decryption
   
function decryptSess($ciphertext)
    {
        return
openssl_decrypt($ciphertext, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA, $this->iv);
    }

    public function
open($savePath, $sessionName): bool
   
{
       
ob_start(); // Turn on output buffering
       
return true;
    }

    public function
close(): bool
   
{
        return
true;
    }

   
#[\ReturnTypeWillChange]
   
public function read($id)
    {
        if (isset(
$_COOKIE[session_name()])) {
            return (string)
$this->decryptSess(base64_decode($_COOKIE[session_name()]));
        } else {
            return
'';
        }
    }

    public function
write($id, $data): bool
   
{
       
$op = ob_get_clean();
       
$encryptedData = base64_encode($this->encryptSess($data));
       
setcookie(session_name(), $encryptedData, time() + (ini_get("session.gc_maxlifetime")), '/');
        echo
$op;

        return
true;
    }

    public function
destroy($id): bool
   
{
        return
true;
    }

   
#[\ReturnTypeWillChange]
   
public function gc($maxlifetime)
    {
        return
true;
    }
}

$handler = new MySessionHandler();
session_set_save_handler($handler, true);
session_start();
var_dump($_SESSION);
$_SESSION['id'] = 10000;

echo
'<br/>Hello World';
?>
up
1
nickleus
7 years ago
i dont see any mention of what happens when eg "open" calls "die", like mentioned in docs for "register_shutdown_function":

"If you call exit() within one registered shutdown function, processing will stop completely and no other registered shutdown functions will be called."

http://php.net/manual/en/function.register-shutdown-function.php

my result: same behavior--"read" will not get called if "open" calls "die"/"exit".
up
0
jamesbenson944 at hotmail dot com
11 years ago
I'm not using objects for the save handlers I'm using functions but still get weird behaviour with session writing not being called.

This fixes the problem though:
register_shutdown_function('session_write_close');
up
0
anonymous at anonymous dot org
16 years ago
if you simply append the information from session variables every time you'll have many multiples for variables each time they are changed. a simple way to do this is explode the data twice to seperate the variable name from the other relevant information and foreach() check against the stored set. here is a little bit of a mess i wrote to do it.
assuming stored session variables in both database and passed through function:

<?php
$buffer
= array();
$buffer = explode('|',$sessiondata);
$buf1 = array();
$buf2 = array();
$finalbuff = '';
foreach(
$buffer as $i){
   
$i = explode(';',$i);
    foreach(
$i as $b){
       
array_push($buf1,$b);
    }
}
$buffer = explode('|',$result['data']);
foreach(
$buffer as $i){ $i = explode(';',$i); foreach($i as $b){ array_push($buf2,$b);}}
$z = 0;
$x = 0;
while(
$buf2[$z]){
    while(
$buf1[$x]){
        if(
$buf2[$z] == $buf1[$x]){
           
$buf2[($z+1)] = $buf1[($x+1)];
        }
       
$x+=2;
    }
   
$z+=2;
}
foreach(
$buf2 as $i){ $finalbuff .= $i; }
?>

$sessiondata is the variable passed through the function and $result['data'] is the data stored in an sql database.
up
0
Colin
17 years ago
When using a custom session handler, if the first callback function (sessOpen in my case) finds no session id, one is set by the time the second argument (sessRead in my case) is called.
up
0
mjohnson at pitsco dot com
18 years ago
With regards to the read handler, the docs say:

  "Read function must return string value always to make save
  handler work as expected. Return empty string if there is no
  data to read."

I can't emphasize this enough. I just spent half a day trying to figure out why my sessions weren't storing any information. I was blithely returning the results of a query on the database from the read handler. Since there was no match for the new ID, the result was NULL. Since it wasn't a string, sessions were essentially disabled. So, the safe thing might be something like this:

<?php
function sessRead($id)
{
   
// Look up data
   
$results = getStuff($id);
   
   
// Make sure it's a string
   
settype($results, 'string');
    return
$results;
}
?>

Of course, you can do whatever you want with it. But, no matter what, make sure you return a string.

HTH,
Michael
up
0
coco at digitalco2 dot com
21 years ago
When using mySQL for your session handling functions, don't forget to call mysql_select_db() to change the database if you are using a separate database for your session data. Call mysql_select_db() INSIDE every handler function that accesses the database, since if you write session data after accessing another database, it will not change the database to your session database, and therefore, not write the session data.
up
0
spam at skurrilo dot de
22 years ago
You can't use the session autostart feature with

session.save_handler = user

set in your php.ini. Use instead the auto_prepend_file directive in the php.ini and point it to your save_handler with an session_start() at the end.
To Top