Sunday, January 27, 2008

Try Zend Framework vol.11 "Save session data into DB"

How do you handle session in your site? Usually you may not need to change it from default.
Let's think your site is on the several web servers and need to use unique session. You probably want to store session into database.
Zend Framework provides interface of session save handler. so you just need to implement the interface when you create your own session save handler.
Then I tried to write my own session save handler here.
But before writing it, I draw upon Zend Developer Zone tips below.

Trick-Out Your Session Handler
To know how to write session save handler.

Zend_Session_SaveHandler_DbTable
To know the concept of the session save handler for Zend Framework.
But I did not implement all of them but some of them.

According to "Trick-Out Your Session Handler", I should write opening database connection in "open()" method, and closing database connection in "close()" method. But in my class I just made it return "true". :P

First, we are going to store session into this table(in this case I used PostgreSQL, you may change timestamp to datetime with MySQL)
CREATE TABLE sessions (
id char(32) default NULL,
lifetime timestamp default NULL,
data text
);

OK, here this is a session save handler of database.
library/Hoge/Session/SaveHandler/Db.php
<?php
require_once 'Zend/Db/Table/Abstract.php';

/**
 * Hoge_Session_SaveHandler_Db
 */
class Hoge_Session_SaveHandler_Db extends Zend_Db_Table_Abstract implements Zend_Session_SaveHandler_Interface
{
    /**
     * constatns
     */
    // session table name
    const TABLE_NAME         = 'sessions';
    // primary key column name
    const COLUMN_PRIMARY_KEY = 'id';
    // lifetime column name
    const COLUMN_LIFETIME    = 'lifetime';
    // data column name
    const COLUMN_DATA        = 'data';

    /**
     * primary key
     * @var string
     */
    protected $_primary = self::COLUMN_PRIMARY_KEY;

    /**
     * table name
     * @var string
     */
    protected $_name = self::TABLE_NAME;

    /**
     * table columns.
     * @var array
     */
    protected $_columnMap = array(
        self::COLUMN_PRIMARY_KEY => self::COLUMN_PRIMARY_KEY,
        self::COLUMN_LIFETIME    => self::COLUMN_LIFETIME,
        self::COLUMN_DATA        => self::COLUMN_DATA
    );

    /**
     * session maxlifetime
     * @var null|intger
     */
    protected $_lifetime = null;

    /**
     * constructor
     * @param string $table      Session table name
     * @param array  $columnMap  Session table column names
     */
    public function __construct($table = null, $columnMap = null)
    {
        // set table name
        if ($table) {
            $this->_name = $table;
        }

        if ($columnMap) {
            // set primary key name
            if (isset($columnMap[self::COLUMN_PRIMARY_KEY])) {
                $this->_columnMap[self::COLUMN_PRIMARY_KEY] = $columnMap[self::COLUMN_PRIMARY_KEY];
                $this->_primary = $columnMap[self::COLUMN_PRIMARY_KEY];
            }
            // set lifetime column name
            if (isset($columnMap[self::COLUMN_LIFETIME])) {
                $this->_columnMap[self::COLUMN_LIFETIME] = $columnMap[self::COLUMN_LIFETIME];
            }
            // set session data column name
            if (isset($columnMap[self::COLUMN_DATA])) {
                $this->_columnMap[self::COLUMN_DATA] = $columnMap[self::COLUMN_DATA];
            }
        }

        parent::__construct();
    }

    /**
     * Set session max lifetime.
     * @param $lifetime
     */
    public function setLifetime($lifetime)
    {
        $this->_lifetime = $lifetime;
    }

    /**
     * Open Session - retrieve resources
     *
     * @param string $save_path
     * @param string $name
     */
    public function open($save_path, $name)
    {
        return true;
    }

    /**
     * Close Session - free resources
     *
     */
    public function close()
    {
        return true;
    }

    /**
     * Read session data
     *
     * @param string $id
     * @return string
     */
    public function read($id)
    {
        $return = '';
        $where = $this->getAdapter()->quoteInto($this->_columnMap[self::COLUMN_PRIMARY_KEY] . "=?", $id);
        if ($row = $this->fetchRow($where)) {
            $return = $row->{$this->_columnMap[self::COLUMN_DATA]};
        }
        return $return;
    }

    /**
     * Write Session - commit data to resource
     *
     * @param string $id
     * @param mixed $data
     * @return bool
     */
    public function write($id, $data)
    {
        $return = false;
        $dataSet = array(
            $this->_columnMap[self::COLUMN_PRIMARY_KEY] => $id,
            $this->_columnMap[self::COLUMN_LIFETIME]    => date("Y-m-d H:i:s", mktime()),
            $this->_columnMap[self::COLUMN_DATA]        => $data
        );
        $where = $this->getAdapter()->quoteInto($this->_columnMap[self::COLUMN_PRIMARY_KEY] . "=?", $id);

        if ($this->fetchRow($where)) {
            $return = ($this->update($dataSet, $where)) ? true : false;
        } else {
            $return = ($this->insert($dataSet)) ? true: false;
        }

        return $return;
    }

    /**
     * Destroy Session - remove data from resource for
     * given session id
     *
     * @param string $id
     * @return bool
     */
    public function destroy($id)
    {
        $where = $this->getAdapter()->quoteInto($this->_columnMap[self::COLUMN_PRIMARY_KEY] . "=?", $id);
        return ($this->delete($where)) ? true : false;
    }

    /**
     * Garbage Collection - remove old session data older
     * than $maxlifetime (in seconds)
     *
     * @param int $maxlifetime
     * @return bool
     */
    public function gc($maxlifetime)
    {
        $lifetime = ($this->_lifetime) ? $this->_lifetime : $maxlifetime;
        $expiry = date("Y-m-d H:i:s", mktime() - $lifetime);
        $where = $this->getAdapter()->quoteInto($this->_columnMap[self::COLUMN_LIFETIME] . "<=?", $expiry);
        return ($this->delete($where)) ? true : false;
    }
}

Well, Let's use that session save handler.
Basically all you need to do is initialize that save handler and set it to Zend_Session_SaveHandler.

www/index.php
require_once 'Hoge/Session/SaveHandler/Db.php';
$saveHandler = new Hoge_Session_SaveHandler_Db();
Zend_Session::setSaveHandler($saveHandler);

If you want to change table name and column name, set it on constructor.
$table = 'hoge';
$columns = array('id'=>'foo', 'lifetime'=>'bar', 'data'=> 'buz');
$saveHandler = new Hoge_Session_SaveHandler_Db($table, $columns);

If you want to set session max life time, use lifetime setter.
$saveHandler = new Hoge_Session_SaveHandler_Db();
$saveHandler->setLifetime(60*60*12);

That's it. But Zend Framework release version 1.5 soon or later.
It may includes "Zend_Session_SaveHandler_DbTable".
May be it much flexible and useful :)
But I didn't want to wait it. Just wanted to write it like somebody talked to me "Don't wait somebody write code, just write as you need!" :)
(weight 88.6kg BMI 31%)

Labels:

2 Comments:

At 5:36 AM, Anonymous Anonymous said...

Hi,
When you write data to Db, time is setting for now, but if somebody want to use remeberMe or any longer lifetime need to set $_lifetime in date.

Am I right?

 
At 5:44 AM, Anonymous Anonymous said...

$exp = $this->_lifetime != null ? date( "Y-m-d H:i:s", time() + $this->_lifetime ):date( "Y-m-d H:i:s", time() );

is it ok?

 

Post a Comment

<< Home