You want to store session data in a database instead of in files. If multiple web servers all have access to the same database, the session data is then mirrored across all the web servers.
Set session.save_handler to user in php.ini and use the pc_DB_Session class shown in Example 8-1. For example:
$s = new pc_DB_Session('mysql://user:password@localhost/db'); ini_get('session.auto_start') or session_start();
One of the most powerful aspects of the session module is its abstraction of how sessions get saved. The session_set_save_handler( ) function tells PHP to use different functions for the various session operations such as saving a session and reading session data. The pc_DB_Session class stores the session data in a database. If this database is shared between multiple web servers, users' session information is portable across all those web servers. So, if you have a bunch of web servers behind a load balancer, you don't need any fancy tricks to ensure that a user's session data is accurate no matter which web server they get sent to.
To use pc_DB_Session, pass a data source name (DSN) to the class when you instantiate it. The session data is stored in a table called php_session whose structure is:
CREATE TABLE php_session ( id CHAR(32) NOT NULL, data MEDIUMBLOB, last_access INT UNSIGNED NOT NULL, PRIMARY KEY(id) )
If you want the table name to be different than php_session, set session.save_path in php.ini to your new table name. Example 8-1 shows the pc_DB_Session class.
require 'PEAR.php'; require 'DB.php'; class pc_DB_Session extends PEAR { var $_dbh; var $_table; var $_connected = false; var $_gc_maxlifetime; var $_prh_read; var $error = null; /** * Constructor */ function pc_DB_Session($dsn = null) { if (is_null($dsn)) { $this->error = PEAR::raiseError('No DSN specified'); return; } $this->_gc_maxlifetime = ini_get('session.gc_maxlifetime'); // Sessions last for a day unless otherwise specified. if (! $this->_gc_maxlifetime) { $this->_gc_maxlifetime = 86400; } $this->_table = ini_get('session.save_path'); if ((! $this->_table) || ('/tmp' == $this->_table)) { $this->_table = 'php_session'; } $this->_dbh = DB::connect($dsn); if (DB::isError($this->_dbh)) { $this->error = $this->_dbh; return; } $this->_prh_read = $this->_dbh->prepare( "SELECT data FROM $this->_table WHERE id LIKE ? AND last_access >= ?"); if (DB::isError($this->_prh_read)) { $this->error = $this->_prh_read; return; } if (! session_set_save_handler(array(&$this,'_open'), array(&$this,'_close'), array(&$this,'_read'), array(&$this,'_write'), array(&$this,'_destroy'), array(&$this,'_gc'))) { $this->error = PEAR::raiseError('session_set_save_handler() failed'); return; } return $this->_connected = true; } function _open() { return $this->_connected; } function _close() { return $this->_connected; } function _read($id) { if (! $this->_connected) { return false; } $sth = $this->_dbh->execute($this->_prh_read, array($id,time() - $this->_gc_maxlifetime)); if (DB::isError($sth)) { $this->error = $sth; return ''; } else { if (($sth->numRows() == 1) && ($ar = $sth->fetchRow(DB_FETCHMODE_ORDERED))) { return $ar[0]; } else { return ''; } } } function _write($id,$data) { $sth = $this->_dbh->query( "REPLACE INTO $this->_table (id,data,last_access) VALUES (?,?,?)", array($id,$data,time())); if (DB::isError($sth)) { $this->error = $sth; return false; } else { return true; } } function _destroy($id) { $sth = $this->_dbh->query("DELETE FROM $this->_table WHERE id LIKE ?", array($id)); if (DB::isError($sth)) { $this->error = $sth; return false; } else { return true; } } function _gc($maxlifetime) { $sth = $this->_dbh->query("DELETE FROM $this->_table WHERE last_access < ?", array(time() - $maxlifetime)); if (DB::isError($sth)) { $this->error = $sth; return false; } else { return true; } } }
The pc_DB_Session::_write( ) method uses a MySQL-specific SQL command, REPLACE INTO, which updates an existing record or inserts a new one, depending on whether there is already a record in the database with the given id field. If you use a different database, modify the _write( ) function to accomplish the same task. For instance, delete the existing row (if any), and insert a new one, all inside a transaction:
function _write($id,$data) { $sth = $this->_dbh->query('BEGIN WORK'); if (DB::isError($sth)) { $this->error = $sth; return false; } $sth = $this->_dbh->query("DELETE FROM $this->_table WHERE id LIKE ?", array($id)); if (DB::isError($sth)) { $this->error = $sth; $this->_dbh->query('ROLLBACK'); return false; } $sth = $this->_dbh->query( "INSERT INTO $this->_table (id,data,last_access) VALUES (?,?,?)", array($id,$data,time())); if (DB::isError($sth)) { $this->error = $sth; $this->_dbh->query('ROLLBACK'); return false; } $sth = $this->_dbh->query('COMMIT'); if (DB::isError($sth)) { $this->error = $sth; $this->_dbh->query('ROLLBACK'); return false; } return true; }
Documentation on session_set_save_handler( ) at http://www.php.net/session-set-save-handler; a handler using PostgreSQL is available at http://www.zend.com/codex.php?id=456&single=1; the format for data source names is discussed in Recipe 10.4.
Copyright © 2003 O'Reilly & Associates. All rights reserved.