PHP
downloads | documentation | faq | getting help | mailing lists | reporting bugs | php.net sites | links | conferences | my php.net

search for in the

session_start> <session_set_cookie_params
Last updated: Fri, 15 Aug 2008

view this page in

session_set_save_handler

(PHP 4, PHP 5)

session_set_save_handlerSets user-level session storage functions

Description

bool session_set_save_handler ( callback $open , callback $close , callback $read , callback $write , callback $destroy , callback $gc )

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. i.e. Storing the session data in a local database.

Parameters

open

Open function, this works like a constructor in classes and is executed when the session is being opened. The open function expects two parameters, where the first is the save path and the second is the session name.

close

Close function, this works like a destructor in classes and is executed when the session operation is done.

read

Read function must return string value always to make save handler work as expected. Return empty string if there is no data to read. Return values from other handlers are converted to boolean expression. TRUE for success, FALSE for failure.

write

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

The destroy handler, this is executed when a session is destroyed with session_destroy() and takes the session id as its only parameter.

gc

The garbage collector, this is executed when the session garbage collector is executed and takes the max session lifetime as its only parameter.

Return Values

Returns TRUE on success or FALSE on failure.

Examples

Example #1 session_set_save_handler() example

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.

<?php
function open($save_path$session_name)
{
  global 
$sess_save_path;

  
$sess_save_path $save_path;
  return(
true);
}

function 
close()
{
  return(
true);
}

function 
read($id)
{
  global 
$sess_save_path;

  
$sess_file "$sess_save_path/sess_$id";
  return (string) @
file_get_contents($sess_file);
}

function 
write($id$sess_data)
{
  global 
$sess_save_path;

  
$sess_file "$sess_save_path/sess_$id";
  if (
$fp = @fopen($sess_file"w")) {
    
$return fwrite($fp$sess_data);
    
fclose($fp);
    return 
$return;
  } else {
    return(
false);
  }

}

function 
destroy($id)
{
  global 
$sess_save_path;

  
$sess_file "$sess_save_path/sess_$id";
  return(@
unlink($sess_file));
}

function 
gc($maxlifetime)
{
  global 
$sess_save_path;

  foreach (
glob("$sess_save_path/sess_*") as $filename) {
    if (
filemtime($filename) + $maxlifetime time()) {
      @
unlink($filename);
    }
  }
  return 
true;
}

session_set_save_handler("open""close""read""write""destroy""gc");

session_start();

// proceed to use sessions normally

?>

Notes

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. 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.

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().

See Also



session_start> <session_set_cookie_params
Last updated: Fri, 15 Aug 2008
 
add a note add a note User Contributed Notes
session_set_save_handler
Anonymous
16-Aug-2008 02:47
Hey,
I just found i very nice example in german how to store sessions in a MySQL database. Look up at
http://www.mywebsolution.de/workshops/1/page_5/show_Sessions-in-PHP.html#up
james at dunmore dot me dot uk
13-Aug-2008 11:31
If your using database session handler and your database is in UTF8, but you happen to have a script running in IS0-8859 that tries to put symbols such as £ signs into the database - then the session will corrupt and data will go missing.

You can either - make sure you never put special characters into the session, or - more helpful, do this in your 'write' function

<?php

public
static function write( $key, $val )
        {
           
$val = utf8_encode( $val );
           
           
$val = preg_replace( '!s:(\d+):"(.*?)";!se', "'s:'.strlen('$2').':\"$2\";'", $val );

              
//......

?>

probably an overhead on this, but less overhead than loosing data.

You could also use WDDX for storing sessions I suppose
bob at nospam dot com
28-Jul-2008 10:50
If using mysqli in procedural style you may expect the mysqli handle to be valid in the write callback. It is not the case, the handle in procedural style is a object that will be destroy unlink the plain mysql interface handle. Just register a shutdown function after set save handler like this:

session_set_save_handler('_session_open', '_session_close', '_session_read', '_session_write', '_session_destroy', '_session_gc');
register_shutdown_function('session_write_close');

Since PHP 4.1, it more of a PRE-Shutdown and it fix the issue for me.
tomas at slax dot org
09-Jul-2008 12:00
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.
andreas dot beder at gmx dot at
27-Jun-2008 05:46
in reply to shaun freeman mdb2 session handler:
i just tried your code and want to correct some lines.
at first the database, i wonder why there is a session_id column  when no line uses it ??
the second one: the return value of the read method is a database resource so no session variable will be set.

database structure:

CREATE TABLE IF NOT EXISTS `sessions` (
  `session` varchar(255) character set utf8 collate utf8_bin NOT NULL,
  `session_expires` int(10) unsigned NOT NULL default '0',
  `session_data` mediumtext collate utf8_unicode_ci,
  KEY `session` (`session`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

you could replace the read method with:
<?
   
function read($sessID) {
        global
$php_errormsg;
       
// fetch session-data
       
$query = "
            SELECT session_data FROM sessions
            WHERE session = '$sessID'
            AND session_expires >
        "
.time();
       
$result = $this->mdb2->query($query);
       
// return data or an empty string at failure
       
if (MDB2::isError($result)) {
           
$php_errormsg .= $result->getMessage();
           
$php_errormsg .= $result->getDebugInfo ();
            return
false;
        }
        list(
$value)=@$result->fetchrow();
        return
$value;
    }
?>

it also took me a while to figure out what parameter i have to give the
session class.

<?
ini_set
("session.save_handler", "user");
require_once
'MDB2.php';
include(
"sessions.php"); // here should be your database class

$dsn = array(
   
'phptype'  => $cfg[db_type],
   
'username' => $cfg[db_user],
   
'password' => $cfg[db_pass],
   
'hostspec' => $cfg[db_host],
   
'database' => $cfg[db],
);

$options = array(
   
'debug'       => 2,
   
'portability' => MDB2_PORTABILITY_ALL,
);

$db =& MDB2::connect($dsn, $options);
if (
PEAR::isError($db)) {
    die(
$db->getMessage());
}

$session=new Session($dsn);
session_start();
echo
$_SESSION["login"]=$_SESSION["login"]+1;
?>
toby at telegraphics dot com dot au
21-Jun-2008 01:16
1) Re: dmclain, "MySQL will do extra work to allow you to do text-style searching on [a TEXT column]."

Absolutely not so, unless you ask for an index.

2) to anyone implementing garbage collection with SQL DELETE, this is going to be *much* more efficient if you add an index on the 'expire' timestamp column (which allows the comparison to efficiently find rows).
dmclain at expressdynamics dot com
15-May-2008 03:23
I recently implemented MySQL Database Stored Sessions, with a similar solution to those posted here.

I ran into a problem where my Session seemed like it was loosing data!  It turns out I had hit the length limit that a 'text' field could hold, yet the data was still de-serializing properly, and not throwing any error.

Using a 'text' column is fine, but limits you to 2^16 characters (65536-1) in length, and MySQL will do extra work to allow you to do text-style searching on it.

Since you will probably never write a query that selects sessions from your table based on the data that's in them... I suggest using a blob instead of text (blob stores it's data in binary format), and I would suggest going straight to mediumblob; which will allow you to store 2^24 (1.6 Million.. give or take a few) characters before you hit the wall.

Hope this helps!
anddriga at gmail dot com
10-May-2008 03:44
Some PHP code for memcached session handler.

class SessionHadler
{
    private static $lifetime = 0;

    public static function open()
    {
        self::$lifetime = ini_get('session.gc_maxlifetime');
       
        return true;
    }
   
    public static function read($id)
    {
        return memcached::get("sessions/{$id}");
    }
   
    public static function write($id, $data)
    {
        return memcached::set("sessions/{$id}", $data, self::$lifetime);
    }
   
    public static function destroy($id)
    {
        return memcached::delete("sessions/{$id}");
    }
   
    private function __construct(){}
    public static function gc(){ return true; }
    public static function close(){    return true; }
    public function __destruct()
    {
        session_write_close();
    }
}
james at enginecreative dot co dot uk
07-Apr-2008 08:22
With regards to db session handling:

Remember if you use the REPLACE INTO method to have your session key as the primary key otherwise you will end up with duplicate records in your table.

http://dev.mysql.com/doc/refman/5.0/en/replace.html
james dot ellis at gmail dot com
04-Apr-2008 10:39
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.
klose at openriverbed dot de
13-Mar-2008 07:01
An tested example with static class.
Initiated from maria at junkies dot jp comment.
<?php
/**
 * PHP session handling with MySQL-DB
 *
 * Created on 12.03.2008
 * @license    http://www.opensource.org/licenses/cpl.php Common Public License 1.0
 */

class Session
{
   
/**
     * a database connection resource
     * @var resource
     */
   
private static $_sess_db;

   
/**
     * Open the session
     * @return bool
     */
   
public static function open() {
       
        if (
self::$_sess_db = mysql_connect('localhost',
                                           
'root',
                                           
'')) {
            return
mysql_select_db('my_application', self::$_sess_db);
        }
        return
false;
    }

   
/**
     * Close the session
     * @return bool
     */
   
public static function close() {
        return
mysql_close(self::$_sess_db);
    }

   
/**
     * Read the session
     * @param int session id
     * @return string string of the sessoin
     */
   
public static function read($id) {
       
$id = mysql_real_escape_string($id);
       
$sql = sprintf("SELECT `session_data` FROM `sessions` " .
                      
"WHERE `session` = '%s'", $id);
        if (
$result = mysql_query($sql, self::$_sess_db)) {
            if (
mysql_num_rows($result)) {
               
$record = mysql_fetch_assoc($result);
                return
$record['session_data'];
            }
        }
        return
'';
    }

   
/**
     * Write the session
     * @param int session id
     * @param string data of the session
     */
   
public static function write($id, $data) {
       
$sql = sprintf("REPLACE INTO `sessions` VALUES('%s', '%s', '%s')",
                      
mysql_real_escape_string($id),
                      
mysql_real_escape_string(time()),
                      
mysql_real_escape_string($data)
                       );
        return
mysql_query($sql, self::$_sess_db);
    }

   
/**
     * Destoroy the session
     * @param int session id
     * @return bool
     */
   
public static function destroy($id) {
       
$sql = sprintf("DELETE FROM `sessions` WHERE `session` = '%s'", $id);
        return
mysql_query($sql, self::$_sess_db);
    }

   
/**
     * Garbage Collector
     * @param int life time (sec.)
     * @return bool
     * @see session.gc_divisor      100
     * @see session.gc_maxlifetime 1440
     * @see session.gc_probability    1
     * @usage execution rate 1/100
     *        (session.gc_probability/session.gc_divisor)
     */
   
public static function gc($max) {
       
$sql = sprintf("DELETE FROM `sessions` WHERE `session_expires` < '%s'",
                      
mysql_real_escape_string(time() - $max));
        return
mysql_query($sql, self::$_sess_db);
    }
}

//ini_set('session.gc_probability', 50);
ini_set('session.save_handler', 'user');

session_set_save_handler(array('Session', 'open'),
                         array(
'Session', 'close'),
                         array(
'Session', 'read'),
                         array(
'Session', 'write'),
                         array(
'Session', 'destroy'),
                         array(
'Session', 'gc')
                         );

if (
session_id() == "") session_start();
//session_regenerate_id(false); //also works fine
if (isset($_SESSION['counter'])) {
   
$_SESSION['counter']++;
} else {
   
$_SESSION['counter'] = 1;
}
echo
'<br/>SessionID: '. session_id() .'<br/>Counter: '. $_SESSION['counter'];

?>

And don't miss the table dump. ^^

CREATE TABLE IF NOT EXISTS `sessions` (
  `session` varchar(255) character set utf8 collate utf8_bin NOT NULL,
  `session_expires` int(10) unsigned NOT NULL default '0',
  `session_data` text collate utf8_unicode_ci,
  PRIMARY KEY  (`session`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
maria at junkies dot jp
10-Dec-2007 01:51
blow example and ta summary of these comments.
and using the simple native functions of mysql.

<?php
class Session
{

   
/**
     * a database connection resource
     * @var resource
     */
   
private $_sess_db;

   
/**
     * Open the session
     * @return bool
     */
   
public function open() {

        if (
$this->_sess_db = mysql_connect(SESSION_DB_HOST,
                                           
SESSION_DB_USER,
                                           
SESSION_DB_PASS)) {
            return
mysql_select_db(SESSION_DB_DATABASE, $this->_sess_db);
        }
        return
false;

    }

   
/**
     * Close the session
     * @return bool
     */
   
public function close() {

        return
mysql_close($this->_sess_db);

    }

   
/**
     * Close the session
     * @return bool
     */
   
public function close() {

        return
mysql_close($this->_sess_db);

    }

   
/**
     * Read the session
     * @param int session id
     * @return string string of the sessoin
     */
   
public function read($id) {

       
$id = mysql_real_escape_string($id);
       
$sql = sprintf("SELECT `data` FROM `sessions` " .
                      
"WHERE id = '%s'", $id);
        if (
$result = mysql_query($sql, $this->_sess_db)) {
            if (
mysql_num_rows($result)) {
               
$record = mysql_fetch_assoc($result);
                return
$record['data'];
            }
        }
        return
'';

    }

   
/**
     * Write the session
     * @param int session id
     * @param string data of the session
     */
   
public function write($id, $data) {

       
$sql = sprintf("REPLACE INTO `sessions` VALUES('%s', '%s', '%s')",
                      
mysql_real_escape_string($id),
                      
mysql_real_escape_string($data),
                      
mysql_real_escape_string(time()));
        return
mysql_query($sql, $this->_sess_db);

    }

   
/**
     * Destoroy the session
     * @param int session id
     * @return bool
     */
   
public function destroy($id) {

       
$sql = sprintf("DELETE FROM `sessions` WHERE `id` = '%s'", $id);
        return
mysql_query($sql, $this->_sess_db);

}

   
/**
     * Garbage Collector
     * @param int life time (sec.)
     * @return bool
     * @see session.gc_divisor      100
     * @see session.gc_maxlifetime 1440
     * @see session.gc_probability    1
     * @usage execution rate 1/100
     *        (session.gc_probability/session.gc_divisor)
     */
   
public function gc($max) {

       
$sql = sprintf("DELETE FROM `sessions` WHERE `timestamp` < '%s'",
                      
mysql_real_escape_string(time() - $max));
        return
mysql_query($sql, $this->_sess_db);

    }

}

//ini_set('session.gc_probability', 50);
ini_set('session.save_handler', 'user');

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

// below sample main

session_start();
session_regenerate_id(true);

if (isset(
$_SESSION['counter'])) {
   
$_SESSION['counter']++;
} else {
   
$_SESSION['counter'] = 1;
}

?>
rsumibcay at reddoor dot biz
27-Oct-2007 03:46
In the below example posted by "shaun at shaunfreeman dot co dot uk". You shouldn't call gc() within the close() method. This would undermine PHP's ability to call gc() based on session.gc_probability and session.gc_divisor. Not to mention an expensive database hit if you are calling gc() on every request. This can become more problematic for load balanced servers talking to a single database server. And even more expensive if the database is setup to replicate your data for failover.
mixailo at mercenaries dot ru
12-Oct-2007 09:55
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
james at dunmore dot me dot uk
12-Oct-2007 01:52
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).
shaun at shaunfreeman dot co dot uk
09-Oct-2007 02:36
After much messing around to get php to store session in a database and reading all these notes, I come up with this revised code based on 'stalker at ruun dot de' class.
I wanted to use PEAR::MDB2. The only assumption I make is your PEAR::MDB2 object is called $db

SQL:
CREATE TABLE `sessions` (
  `session_id` int(10) unsigned NOT NULL auto_increment,
  `session` varchar(255) character set utf8 collate utf8_bin NOT NULL,
  `session_expires` int(10) unsigned NOT NULL default '0',
  `session_data` mediumtext collate utf8_unicode_ci,
  PRIMARY KEY  (`session_id`),
  KEY `session` (`session`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

<?php
class Session {
   
// session-lifetime
   
public $lifeTime;
    function
__construct ($db) {
       
// get session-lifetime
       
$this->lifeTime = get_cfg_var("session.gc_maxlifetime");
          
// open database-connection
       
$this->mdb2 =& MDB2::factory($db);
        if (
PEAR::isError($this->mdb2)) {
           
$php_errormsg .= $this->mdb2->getMessage();
           
$php_errormsg .= $this->mdb2->getDebugInfo();
        }
       
session_set_save_handler(array(&$this, 'open'),
                                array(&
$this, 'close'),
                                array(&
$this, 'read'),
                                array(&
$this, 'write'),
                                array(&
$this, 'destroy'),
                                array(&
$this, 'gc'));
       
register_shutdown_function('session_write_close');
       
session_start();
           return
true;
    }
    function
open($savePath, $sessName) {
       
// get session-lifetime
       
$this->lifeTime = get_cfg_var("session.gc_maxlifetime");
        return
true;
    }
    function
close() {
       
$this->gc(ini_get('session.gc_maxlifetime'));
       
// close database-connection
       
return $this->mdb2->disconnect();
    }
    function
read($sessID) {
        global
$php_errormsg;
       
// fetch session-data
       
$query = "
            SELECT session_data FROM sessions
            WHERE session = '$sessID'
            AND session_expires >
        "
.time();
       
$result = $this->mdb2->queryOne($query);
       
// return data or an empty string at failure
       
if (MDB2::isError($result)) {
           
$php_errormsg .= $result->getMessage();
           
$php_errormsg .= $result->getDebugInfo ();
            return
false;
        }
        return
$result;
    }
    function
write($sessID,$sessData) {
        global
$php_errormsg;
       
// new session-expire-time
       
$newExp = time() + $this->lifeTime;
       
// is a session with this id in the database?
       
$query = "
            SELECT * FROM sessions
            WHERE session = '$sessID'
        "
;
       
$result = $this->mdb2->query($query);
       
// if yes,
         
if($result->numRows()) {
           
// ...update session-data
           
$query = "
                UPDATE sessions
                SET session_expires = '$newExp',
                 session_data = '$sessData'
                WHERE session = '$sessID'
            "
;
          }
       
// if no session-data was found,
         
else {
           
// create a new row
           
$query = "
                INSERT INTO sessions (
                     session,
                      session_expires,
                      session_data)
                VALUES(
                     '$sessID',
                      '$newExp',
                      '$sessData')
            "
;
          }
       
$result = $this->mdb2->exec($query);
       
// if something happened, return true
       
if (MDB2::isError($result)) {
           
$php_errormsg .= $result->getMessage();
           
$php_errormsg .= $result->getDebugInfo ();
            return
false;
        } else {
           
// ...else return true
           
return true;
        }
    }
    function
destroy($sessID) {
        global
$php_errormsg;
       
// delete session-data
       
$query = "
            DELETE FROM sessions
            WHERE session = '$sessID'
        "
;
       
$result = $this->mdb2->exec($query);
       
// if session was not deleted, return false,
        
if (MDB2::isError($result)) {
           
$php_errormsg .= $result->getMessage();
           
$php_errormsg .= $result->getDebugInfo ();
            return
false;
         } else {
           
// ...else return true
           
return true;
        }
    }
    function
gc($sessMaxLifeTime) {
        global
$php_errormsg;
       
// delete old sessions
       
$query = "
            DELETE FROM sessions
            WHERE session_expires <
        "
.time();
       
$result = $this->mdb2->exec($query);
       
// return affected rows
       
if (MDB2::isError($result)) {
           
$php_errormsg .= $result->getMessage();
           
$php_errormsg .= $result->getDebugInfo ();
        }
        return
$result;
    }
}
?>
maximumpig at yahoo d0t c0m
14-Aug-2007 07:19
I believe equazcion is not correct--the environment is refreshed with each script so session_set_save_handler() should be called with each script, not once per session.
sroby at colubris dot company
25-May-2007 01:56
The Web app I work on stores sessions in a database using PEAR::DB, so when I migrated it to PHP 5 everything broke because the DB object was unallocated by the time the write/close handlers were called.

However, I've found that adding <?php register_shutdown_function("session_write_close"); ?> works fine as a workaround to the problem.
equazcion
11-Mar-2007 06:44
I know this might be obvious, but session_set_save_handler() should only be called once per session, or else your saved data will keep getting reset.

If your script doesn't have a predictable start page that will only be called only once per session, place the session_set_save_handler statement in an include file, and call it via require_once().
Colin
09-Mar-2007 08:25
Aspects of this have been posted in various comments but it's helpful to make it clearer.

The custom session handler seems to perform actions in these orders:

When session_start() is called:

open
read
clean (if cleaning is being done this call)
write
close

When session_destroy() is called after session_start():

open
read
clean (if cleaning is being done this call)
destroy
close

When session_regenerate_id(1) is called after session_start():

open
read
clean (if cleaning is being done this call)
destroy
write
close
Colin
09-Mar-2007 07:10
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.
Jonathan Zylstra
02-Dec-2006 12:58
In response to  korvus at kgstudios dot net
11-Jun-2005 05:34:

Actually, using MySQL REPLACE, and getting the number of
affected rows = 2, means that one row was being deleted,
and one row being inserted.
That would happen if your session id was already in the database, and you write to it using 'REPLACE' ... it deletes the old session data, and REPLACE's it with the updated session data, thus resulting in two affected rows.

See:
http://dev.mysql.com/doc/refman/4.1/en/replace.html

I would be careful in assuming that the 'write' callback is called twice because of the behavior of your SQL code.
matt at openflows dot org
20-Sep-2006 10:02
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
}
?>
information at saunderswebsolutions dot com
17-Aug-2006 04:56
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
sneakyimp AT hotmail DOT com
05-Aug-2006 02:20
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()
?>
djmaze@cpgnuke cms
21-May-2006 03:51
Since register_shutdown_function() and __destruct() will not work on object based sessions, ESPECIALY when they are database based thru a class, then it seems you're screwed.

Issue:
register_shutdown_function('shutdown_function');
new sql();
new session();

on destruct:
sql __destruct()
session __destruct() // can't call sql since already destroyed
shutdown_function() // can't call instances since already destroyed

A trick is to use an "first class instance"
<?php
class my_proper_destructor
{
   
protected $destroyers = array();

    function
add($class_instance)
    {
        if (
is_class($class_instance))
           
$destroyers[] = $class_instance;
    }

    function
__destruct()
    {
        foreach(
$destroyers as $des)
           
$des->on_destroy();
    }
}

$destruct = new my_proper_destructor();

// All your code starts here like:
class session
{
    function
__construct()
    {
        global
$destruct;
       
$destruct->add($this);
    }
}

?>

Since my_proper_destructor is created first it also gets destroyed as the first. That way you can still call important class instances that need to do something on destruct.
mjohnson at pitsco dot com
29-Mar-2006 06:04
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
mastabog at hotmail dot com
24-Feb-2006 02:09
In regard to the note dated 28-Jan-2006 09:15 (there is no email) and the webpage it links to.

That is not the way objects should be saved into session. You should not just assign the entire object to $_SESSION['myObj'] and hope for the best while waiting for the script to end and for the php internal engine to serialize your object, call destructors etc.

You should serialize your object beforehand by specifically calling serialize() on your object and assign the serialized string to a session key.

Yeah, You might be wondering if this isn't defeating the whole purpose. It's not. This way you are sure that your object is serialized correctly and don't have to worry about how session handlers mess with it. You now have a string variable to assign wherever you want and you avoid all the mess of session write()/close() being called before object destructors. You also have more control over the state of the object you want to save into session (the serialized object will not reflect changes done to the object after serialization) and will also end up with a code that is more likely to survive over time in case the guys at PHP decide to make other changes to the session functions.

Marcus has recently introduced the Serializable interface (php 5.1.2) which IMO will improve things *a lot* and is the right way to serialize objects. The problem with it though is that it breaks object references in some cases. I already made a bug report here: http://bugs.php.net/bug.php?id=36424

You can mimic the Serializable interface's methods by adding a public method to your classes that assigns serialize($this) to $_SESSION['myObj'] and another static method that acts as a constructor, returning unserialize($_SESSION['myObj']). This way you have even more control for serializing your objects as you can modify your Foo::serialize()/unserialize() methods to perform other tasks as well.

<?php

class Foo
{
    const
SESSION_KEY = 'someKeyName';

   
// ...
    // other definitions go here
    // ...

   
static function unserialize ($key = self::SESSION_KEY)
    {
        return isset(
$_SESSION[$key]) ? unserialize($_SESSION[$key]) : false;
    }

   
public function serialize ($key = self::SESSION_KEY)
    {
       
$_SESSION[$key] = serialize($this);
    }
}

$Obj = new Foo();

// do somehting on $Obj here

$Obj->serialize();

// continue until the end of script

?>

When getting your object from session you just do:

<?php

// try getting your obj from session
$Obj = Foo::unserialize();

// and to make sure you do a valid Foo instance:
// (you can also do this inside Foo::unserialize() if you want)
$Obj instanceOf Foo or $Obj = new Foo();

?>

If you need to save multiple instances of the same class then just pass a different parameter to Foo::serialize() and Foo::unserialize(). You can also extend your classes over a class like Foo above and overload the Foo::serialize()/unserialize() methods. Of course, you can do all that without having dedicated methods in your classes (yuck).
29-Jan-2006 04:15
As posted here (http://marc.theaimsgroup.com/?l=php-general&m=113833844422096&w=2) the session module cannot handle objects anymore (tested in 5.1.2).

You have to add

> session_write_close();

at the end of your script to save the object values into the session.

This may indicate general problem. session_write_close() is called in the session module's rshutdown() method, which is much too late: Since 5.1 the zend_object_store is cleaned before the module's rshutdown hook is called.  Furthermore it is not guaranteed that the session module's rshutdown method is called before your module's rshutdown gets called.

In other words: Session shutdown seems to be broken in all PHP versions. It is better to always add session_write_close() to the end of your scripts.  This works with all php versions and it will work with future versions of php whether or not this problem gets fixed.
18-Jan-2006 08:04
function __construct() {
    session_set_save_handler(array(&$this, 'open'),
                             array(&$this, 'close'),
                             array(&$this, 'read'),
                             array(&$this, 'write'),
                             array(&$this, 'destroy'),
                             array(&$this, 'gc'));
    register_shutdown_function('session_write_close');
    session_start();
}
boswachter at xs4all nl
13-Jan-2006 10:37
If you're creating a sessionhandler class, and use a database-class and you are experiencing problems because of destroyed objects when write is called, you can fix this relatively easily:

register_shutdown_function("session_write_close");

This way, the session gets written of before your database-class is destroyed.
stalker at ruun dot de
04-Jan-2006 02:25
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...
?>
cthree at s2ki dot com
26-Dec-2005 03:53
In reply to rudy dot metzger at pareto dot nl:

One option to work around this problem is to instantiate your database connection from within session class:

class Session {
  public $db;

  public function open( $path, $name ) {
    $this->db = mysql_connect(...); // or whatever works
    return true;
  }

  ...

  public function close() {
    mysql_close( $this->db ); // or whatever works
    return true;
  }
}

$session = new Session;
session_set_save_handler(
  array(&$session, "open"),
  ...
  array(&$session, "close"),
  ...
  );
session_start();
$db =& $session->db;
...

HTH
bachir
05-Dec-2005 05:48
php doesn't make any checks about PHPSESSID cookie format, it is then important to verify cookie format before making any sql request.

if your read session request is :
SELECT DataValue FROM sessions WHERE SessionID='$aKey'

this generic cookie could succeed to access others session: PHPSES