ScotlandPHP

session_set_save_handler

(PHP 4, PHP 5, PHP 7)

session_set_save_handlerSets user-level session storage functions

Description

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 ]]] )

Since PHP 5.4 it is possible to register the following prototype:

bool session_set_save_handler ( SessionHandlerInterface $sessionhandler [, bool $register_shutdown = true ] )

session_set_save_handler() sets the user-level session storage functions which are used for storing and retrieving data associated with a session. This is most useful when a storage method other than those supplied by PHP sessions is preferred, e.g. storing the session data in a local database.

Parameters

This function has two prototypes.

sessionhandler

An instance of a class implementing SessionHandlerInterface, such as SessionHandler, to register as the session handler. Since PHP 5.4 only.

register_shutdown

Register session_write_close() as a register_shutdown_function() function.

or
open(string $savePath, string $sessionName)

The open callback works like a constructor in classes and is executed when the session is being opened. It is the first callback function executed when the session is started automatically or manually with session_start(). Return value is TRUE for success, FALSE for failure.

close()

The close callback works like a destructor in classes and is executed after the session write callback has been called. It is also invoked when session_write_close() is called. Return value should be TRUE for success, FALSE for failure.

read(string $sessionId)

The read callback must always return a session encoded (serialized) string, or an empty string if there is no data to read.

This callback is called internally by PHP when the session starts or when session_start() is called. Before this callback is invoked PHP will invoke the open callback.

The value this callback returns must be in exactly the same serialized format that was originally passed for storage to the write callback. The value returned will be unserialized automatically by PHP and used to populate the $_SESSION superglobal. While the data looks similar to serialize() please note it is a different format which is specified in the session.serialize_handler ini setting.

write(string $sessionId, string $data)

The write callback is called when the session needs to be saved and closed. This callback receives the current session ID a serialized version the $_SESSION superglobal. The serialization method used internally by PHP is specified in the session.serialize_handler ini setting.

The serialized session data passed to this callback should be stored against the passed session ID. When retrieving this data, the read callback must return the exact value that was originally passed to the write callback.

This callback is invoked when PHP shuts down or explicitly when session_write_close() is called. Note that after executing this function PHP will internally execute the close callback.

Note:

The "write" handler is not executed until after the output stream is closed. Thus, output from debugging statements in the "write" handler will never be seen in the browser. If debugging output is necessary, it is suggested that the debug output be written to a file instead.

destroy($sessionId)

This callback is executed when a session is destroyed with session_destroy() or with session_regenerate_id() with the destroy parameter set to TRUE. Return value should be TRUE for success, FALSE for failure.

gc($lifetime)

The garbage collector callback is invoked internally by PHP periodically in order to purge old session data. The frequency is controlled by session.gc_probability and session.gc_divisor. The value of lifetime which is passed to this callback can be set in session.gc_maxlifetime. Return value should be TRUE for success, FALSE for failure.

create_sid()

This callback is executed when a new session ID is required. No parameters are provided, and the return value should be a string that is a valid session ID for your handler.

Return Values

Returns TRUE on success or FALSE on failure.

Examples

Example #1 Custom session handler: see full code in SessionHandlerInterface synopsis.

The following code is for PHP version 5.4.0 and above. We just show the invocation here, the full example can be seen in the SessionHandlerInterface synopsis linked above.

Note we use the OOP prototype with session_set_save_handler() and register the shutdown function using the function's parameter flag. This is generally advised when registering objects as session save handlers.

<?php
class MySessionHandler implements SessionHandlerInterface
{
    
// implement interfaces here
}

$handler = new MySessionHandler();
session_set_save_handler($handlertrue);
session_start();

// proceed to set and retrieve values by key from $_SESSION

Example #2 Custom session save handler using objects

The following code is for PHP versions less than 5.4.0.

The following example provides file based session storage similar to the PHP sessions default save handler files. This example could easily be extended to cover database storage using your favorite PHP supported database engine.

Note we additionally register the shutdown function session_write_close() using register_shutdown_function() under PHP less than 5.4.0. This is generally advised when registering objects as session save handlers under PHP less than 5.4.0.

<?php
class FileSessionHandler
{
    private 
$savePath;

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

        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')
    );

// the following prevents unexpected effects when using objects as save handlers
register_shutdown_function('session_write_close');

session_start();
// proceed to set and retrieve values by key from $_SESSION

Notes

Warning

When using objects as session save handlers, it is important to register the shutdown function with PHP to avoid unexpected side-effects from the way PHP internally destroys objects on shutdown and may prevent the write and close from being called. Typically you should register 'session_write_close' using the register_shutdown_function() function.

As of PHP 5.4.0 you can use session_register_shutdown() or simply use the 'register shutdown' flag when invoking session_set_save_handler() using the OOP method and passing an instance that implements SessionHandlerInterface.

Warning

As of PHP 5.0.5 the write and close handlers are called after object destruction and therefore cannot use objects or throw exceptions. Exceptions are not able to be caught since will not be caught nor will any exception trace be displayed and the execution will just cease unexpectedly. The object destructors can however use sessions.

It is possible to call session_write_close() from the destructor to solve this chicken and egg problem but the most reliable way is to register the shutdown function as described above.

Warning

Current working directory is changed with some SAPIs if session is closed in the script termination. It is possible to close the session earlier with session_write_close().

Changelog

Version Description
5.5.1 Added the optional create_sid parameter.
5.4.0 Added SessionHandlerInterface for implementing session handlers and SessionHandler to expose internal PHP session handlers.

See Also

add a note add a note

User Contributed Notes 43 notes

up
22
andreipa at gmail dot com
1 year 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
5
matt at openflows dot org
11 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
3
james at dunmore dot me dot uk
10 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
1
frank at interactinet dot com
6 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
3
yangqingrong at gmail dot com
8 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
korvus at kgstudios dot net
12 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
1
ivo at magstudio dot net
14 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
4
harald at hholzer at
8 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
e dot sand at elisand dot com
8 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
2
oliver at teqneers dot de
12 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
1
skds1433 at hotmail dot com
8 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
information at saunderswebsolutions dot com
11 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
1
spam at skurrilo dot de
15 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.
up
2
pavelc at users dot sourceforge dot net
6 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
2
stalker at ruun dot de
11 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
1
shanikawm at gmail dot com
1 year 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
1
dimzon541 at gmail dot com
2 years ago
Persisting PHP sessions into mongodb (allows NLB without affinity)
https://gist.github.com/dimzon/62eeb9b8561bcb9f0c6d
up
0
centurianii at yahoo dot co dot uk
1 month 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
0
nickleus
9 months 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
carlos dot vini at gmail dot com
1 year ago
If you have a custom handler registered ini_get('session.save_handler') will return 'user' instead of 'file'
up
0
Steven George
3 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
0
jamesbenson944 at hotmail dot com
4 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
dummynick at gmail dot com
7 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
0
bart2yk at yahoo dot com
7 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
0
joel the usual at sign then purerave.com
7 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
0
anonymous at anonymous dot org
9 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
tomas at sthe variable passed through the functiole4uf2
lid="84300">
22tessong class="user">tomas at sthe variabtmailough ellism>">
lid="84300">
When storing sessions in lled.   ,ing aicularionthrough f/>   rllplay f youm> anuallnumb m T say w call exit() handler reg1000n aquesterpsessr /tx andamp; datanthe ctiogeallie maown nbedreion::setH />Ses begrite men::setH isrb-se f sanup the pshtnnbednablrunave man aquest. Doariab.2.xerticaom Dun aquise.A7/>  he e mhe code in thenms witT say w,le bd.A7:newIsr /tx. handledoic mnbednaose(ki, or ngetoularitybjectemcafrom tgc 1s=isyohe code in thFi, r />
up
0
7ss=essong class="user">tomas at sthe variabmixronoasitmercant=r />> ¶">
lid="84300">
I was getting Fatal e calandreipaustom DMEMORYtabasa d engnux r_sMySQLclass=; &nbs, it's usualhe code et/manual/en/fudev.n is 561bcboc/man?id/5.0r-shmemyoy-abasa d-engnux. Fataet="_blank">http://php.net/manual/en/fudev.n is 561bcboc/man?id/5.0r-shmemyoy-abasa d-engnux. Fat/div>
up
0
7374essong class="user">tomas at sthe variabCos, class="genanchor" href="#98071"> ¶">
rs a0 lid="84300">
When storing sessions in
aler.
   ,(i nsult[irstonly on valid/new se/>&nOcalI use Meabsp;an e pshspan>
&nR
up
0
686tessong class="user">tomas at sthe variabsneakyomp AT>">
lid="84300">
When storing session> ht.<?php
$buffer
$zgc($buf2 keyword">,$i){ ){ sessred ss I'pan> $z_s="keyword">= oci_parse($buf2_s=keyword">){ $buf2($buf2_s=keyword">){ ){ se) at theid mmiINVALID atmodifica
){ <>= oci_parse($buf2(';'edn of "keyword">.(}
){
<>(){ <>);}}
}
){ <>($stid){ $x);}}
$x;
while($x.($x.(}
true
;
    default />){
ses_ />&nbclass="default">?>

up
0
4580essong class="user">tomas at sthe variabBsp; class="genanchor" href="#98071"> ¶">
If you have a custom haaspan>
up
078agocong class="user">tomas at sthe variabRusty X class="genanchor" href="#112609"> ¶="date" title="2010-01-22 01:57">
I was getting Fatal e calaomportanr ustod wo; &ndnaose(al 's be/span   &nbsr />LOCKS>
ht.riables ndamp; datsit tt. ahe codes in ndleandlerIntbenef-on vpd /> abasa d &ndnndledooshtndooriaby prr /dendlesaylruna NLB situa."

 , &ndnndlesaylLOSE DATAtilcaom Dsimcopcossi anlleIif "ooverestrucriab
&nb to d/>>

up
tomas at sthe variabmjohot ctsitpitscng>">
lid="84300">
When storing sessionsan orhisrbsbr />&nbnlleI  &nb PHbutdosay call exit() />&nbsnlleh="http://php.Itatic memphasiz
A7/>  ant infornlleI  .bsp;censulf aror:nsomaved s I'm usnbspID,>

<?php
$buffer ?>
gc
&nR (){ <>);}}
){ sesLut tup tp;   // existed sekeyword">){ <>= oci_parse(){ <>);}}
){ sesMakusiI amial ta /an cl  // existed sekeyword">){ <>($stid,$i,true,?>

up
tomas at sthe variabcocoasitdigiitlco2g>">
When storing sessions in
mySQLcfobja sr />  &nbsr />get weird,edoic mfa>  esessioe wa
alse>  sp;&. Cly rn is _selbsp_dbg INSIDEave manni_get('['data'] auctincmplees/>  ,M;p;cenisessionstrucopied:
&s have cmpletelyanmationsp; ,Mstrif "open"b to d/>  toja sr /> sp; ,Mis tm>v>
up
When storing sessionBelrite_c ipan> siobjtojustn/>asse t calae onew_writomportanr ustbr />sidepan> aler.
siob,rmatiowis=; &ckons aion rpotns w_writan ckMstr NLB overestr.
sidd througp; n(.
&nbooki".* Defi* Udreipaaobjdete callipotns w_w; &ckm>&nbn endou stderegiof PHthoki"fisenempleaoyhe code* Sat hlso: et/manual/en/fulxr.nction.rxman/PHP_TRUNK/> _an aet="_blank">http://php.net/manual/en/fulxr.nction.rxman/PHP_TRUNK/> _an esult: sam * Defi* @"datm /an clf$ugp; * Defi* @"datm boo sane$debug* Defi* @class="boo san* Defi*static ) {
&ning c;br />sid($thoki"_bsp; ,e$debug = fhlsep;      ses) at th.hash_['data'] i">htsessioLB sonewfy>
riabo nsulti"gostrhmlatesvie begy>

&nbs omrshutg0nllethe .the dpoiedhe fbsp; a,e wa'4' (0-9,Mi-f)do'5' (0-9,Mi-v),Mis t'6' (0-9,Mi-z, A-Z, "-", ",")nenbsp;    .aut!(ileet($thoki"_bsp; ) "Vote"Voter s_/an cl($thoki"_bsp; ) "Vote"Voter/anlen($thoki"_bsp; ))p;      session_set_sclass="fhlse   session_set_s}  session_set_s$e tso=ntil   session_set_s.aut1p;      session_set_s$hash_['data'] =ve_handler') will hash_['data']'p   session_set_s session_set_s$hash_['data']_to_e tso=nn>
up
tomas at sthe variabRobein Chap, class="genanchor" href="#98071"> ¶">
When storing sessionSmp; datsum>   rblem now is thats functions A7.1.nlleIis testrucahucopied:
estself,Mis tif "open" behtessioLB te in db viitl ) {
&ning c;rhiedificueid()xes the probleo adsiextratsum>&' sio'nlle';  rblem now is tEnjoy, -- Miqrov>
up
04104< ong class="user">tomas at sthe variablukas. //cecek class="genanchor" href="#112609"> ¶="date" title="2013-07-04 09:29">7 y3 05:12<6/div>
I was getting Fatal erfor vspan> I cmplenstruct),loan onction('session_write_clos. Justonly cl xit() "><?php
$buffer (';'
,?>

(/codeefese.<?php
$buffer ,?>
Smp; daHi_get('keyword">,asssp;&"default">true);}}
gc($stid);}}
true);}}
gc= oci_parse);}}
oci_parse(';'
,gc($buf2 keyword">,$i){ gc){ gc(){ <>);}}
gc (){ <>);}}
$i);
gc(){ <>);}}
gc($buf2);}}
,
up
tomas at sthe variabbachir class="genanchor" href="#98071"> ¶">
lid="84300">
I was getting Fatal cl the ic mmakusriabsred s ab usiPHPn>thoki"fh() ch,() and mak, itriab
up
tomas at sthe variabccavasitmaxbaudg> ¶">
I was getting Fatal Ok,&s havemuch hairpuy cl, I'veon. I adm usiarsucmpsseipaplode tabasenstructedino i
&n
alpostgre <?php
$buffer
?>
gc(){ <>);}}
$i);
$i);
oci_parsesesmy;global"dbscirnelass="structn>$z= $z);
$stid(){ <>_s=keyword">){ oci_parsese.x (g oan oriabtyp"n>$z);
gc(';'.(oci_parsesesderegiooldcustom csclcclan>$z);
gc(';'){ <>);}}
pan clas', 'keyword">){
<>);
.(
oci_parseseinsein NLB svtom c tthe n>$ztrue;
    default}class="defaultn class="default">gc
(){ <>);}}
$i);
$z[2);
gc(';'.(2);
gc);}}
2)];
  &nbs ;      keyword">){
[2);
gc(){ <>;
while(}
){
$ioci_parse($z);
$stid);
gc);
$x;
}
fore])p; }
}
true);
truepan clas""keyword">.(,
$sessiondata is the Makusdanbsp;&.gp; ncolumnn(.)dinoahucopied:&nyp" intal ehtirbde) rializcddesoesulf 'r:nsonbednabldisnr /uish,()twudin.yp"s.class="defaulthe d
up
tomas at sthe variabniklpassedremovrshustitsuldhtndot bi.gpdg>">
I was getting Fatal Mriabpeoy w e wa
dler($handler, true);
&nsp; ,Mwhingeofaiorseois bon obr />Mis tsm/cassp;ceniaa(aion )jectemcacopcurity. ert a satic Whsitmaiabpeoy w fa> a PHeing ultpae editr />alseer($hathoki"fh()tT say w*) ert a satic * handle estra sr /it ,ssred sAdd n>edit Choki"s e wrned:Istr s*. Oneession rnve mess=sto aaite men: e m stself adsisetthelasssiescapeensatio viitl b trachavs. ert a satic * A googlmen:ared s I'"SQLcInjelass="bles s 716 000nhsts. ert a satic E say wsnhod,opene2.x (e s call ex>,
er ?>
gc(){ <>(truetruepan class="default">oci_parse(';'2(;
while(true);
oci_parse);
$stid);
,
 ID>, er ?>
gc(){ <>(oci_parseses( Chdn by>nction. )  sessin_set_sdefault">oci_parse2($ztruepan class="default">oci_parse(){ <>(oci_parsesesQs fisenon NLeger  sessin_set_sdefault">oci_parse$buf2(){ <>($ztruepan class="default">oci_parse(){ <>(truetruepan class="default">oci_parse(';'2(;
while(truetruepan class="default">oci_parse);
$stid);
,
up
tomas at sthe variabtitdrigassedgtrong>">
When storing sessionSomr al ehhdn s I'memcasred />   .class="default"><?php
$buffer
?>
?>
Smp; daHibsp;n>?>
truetruepan class="default">oci_parse;
while(gc(true(truetruepan class="default">oci_parse(';',true;
    default />gc
(){ <>);}}
true(true(';'?>
){
<>);}}
';',gc (){ <>);}}
$i);
true(true(';'?>
){
<>);}}
';',$i);
$i(true,gc(){ <>);}}
true(true(';'?>
){
<>);}}
';',gc(gc(true;
    ;}default  n_set_spublic )ta."csn class="default">gc
){ true;
    ;}default  n_set_spublic n class="default">gc
(true
(,
up
08939tomas at sthe variabCloudrto dr class="genanchor" href="#98071"> ¶="date" title="2010-01-22 01:57">
When storing sessionEe m sp;cenwtes-gradednabl to wornwtefor vhleIi s; a, not being caabasa d
visi yor>
&nbsirect Iabasoa nomp;os I'pacurityte lsird,ewhingeleas s ssionan of PHopass="sf ;offl to nbspd garba d collelass="is testrr />a sr B objmo r />aplodant i
aplodant ia sr wtb/it e&isIisI usio nsultquestio blem now is thaucoandlestnsoluass="rn s und,ewhingeonoahuceallr ok ab usi2 minutis.inoaotit,ois timmedi22 ly" x (pd s I'e inwtb/it s onoahucop e m,"ror:ao om Dsqliniblem now is tSomr al eirdte ia."
&isIror:oandly>a cbsp;o nsultnk">htr />sioncs : yumlirdte io cl-sqliniban clasOoce2>for vuo>edit a sr nctiini &nbnk">htr />two pabd. =i/var/lib/ncte) at thcall exit() NEW VALUES call eopied:< r, true);
=i/un> /to/sirect Ia/aincain. ab vr>as tte dss=";o nsultsp; e waa sblem now is tal eif "ocr   etself,Mbrite m direct Iabsustobject>as tbde cmplethe .lem now is tOoce2ndlefor vmadeem>b uoce2Apasre oan orpasrectl gracreipa I'epasrectl nlabartcall exit() Notde>&ctinny' PHes onoa sr /> e m if "ofor vuheir /> s.nlaetv>
e-note.php?id=843addmp;page=fupactt-save-handler&vote=up" title="Vote'edirect=l/en/fufr2.nction.r?id=843en/-save-handler&vote=up" title=.nct">eimg src='/ima ds/> add@2x.png' alt='adsia note' ifdth='12' height='12'>">adsia note> me i>" tit class> elass=>