Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ZF2 Dafault Database Session Storage

I am busy writing a DB Session module so I can quickly install it between multiple applications. The module will be loaded from the autoloader as the first module to be started. What I'm trying to accomplish is to change the default session container / session handler to be the default session handler for all modules and it should also be database hosted sessions. I've been strugling with zf2 session handler for quite a while now and the errors in the logs make 0 sense. So here what I have so far. A basic module with Module.php containing...

namespace DBSession;

use Zend\Mvc\ModuleRouteListener;

class Module {

    public function onBootstrap($e) {
        $e->getApplication()->getServiceManager()->get('translator');
        $eventManager = $e->getApplication()->getEventManager();
        $moduleRouteListener = new ModuleRouteListener();
        $moduleRouteListener->attach($eventManager);
        $config = $e->getApplication()->getServiceManager()->get('Config');
        $controller = $e->getTarget();
        $controller->config = $config;
        new \DBSession\Storage\DBStorage();
    }

    public function getConfig() {
        return include __DIR__ . '/config/module.config.php';
    }

    public function getAutoloaderConfig() {
        return array(
            'Zend\Loader\ClassMapAutoloader' => array(
                __DIR__ . '/autoload_classmap.php',
            ),
            'Zend\Loader\StandardAutoloader' => array(
                'namespaces' => array(
                    __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
                ),
            ),
        );
    }
}

And the actual class that initiates the DB session handler.

namespace DBSession\Storage;

use Zend\Session\SaveHandler\DbTableGateway;
use Zend\Session\SaveHandler\DbTableGatewayOptions;
use Zend\Db\Adapter\Adapter;
use Zend\Session\SessionManager;
use Zend\Session\Container;

class DBStorage {

    public function __construct() {
        $dbAdapter = new Adapter(array(
                    'driver' => 'Mysqli',
                    'host' => 'localhost',
                    'dbname' => 'zf2_session',
                    'username' => 'zf2',
                    'password' => 'testme',
                    'options' => array(
                        'buffer_results' => true,
                    ),
                ));

        $tableGateway = new \Zend\Db\TableGateway\TableGateway('session', $dbAdapter);


        $gwOpts = new DbTableGatewayOptions();
        $gwOpts->setDataColumn('data');
        $gwOpts->setIdColumn('id');
        $gwOpts->setLifetimeColumn('lifetime');
        $gwOpts->setModifiedColumn('modified');
        $gwOpts->setNameColumn('name');


        $saveHandler = new DbTableGateway($tableGateway, $gwOpts);
        $sessionManager = new SessionManager();
        $sessionManager->setSaveHandler($saveHandler);
        return Container::setDefaultManager($sessionManager);
    }

}

When trying to create a session I see the following in the logs which I have 0 clue how to fix. This is starting to make me hate magic...

[29-Nov-2012 20:47:28 UTC] PHP Fatal error:  Uncaught exception 'Zend\Db\Sql\Exception\InvalidArgumentException' with message 'Not a valid magic property for this object' in /document_root/vendor/zendframework/zendframework/library/Zend/Db/Sql/Select.php:764
Stack trace:
#0 /document_root/vendor/zendframework/zendframework/library/Zend/Db/Sql/Select.php(163): Zend\Db\Sql\Select->__get('tableReadOnly')
#1 /document_root/vendor/zendframework/zendframework/library/Zend/Db/Sql/Select.php(146): Zend\Db\Sql\Select->from('session')
#2 /document_root/vendor/zendframework/zendframework/library/Zend/Db/Sql/Sql.php(65): Zend\Db\Sql\Select->__construct('session')
#3 /document_root/vendor/zendframework/zendframework/library/Zend/Db/TableGateway/AbstractTableGateway.php(191): Zend\Db\Sql\Sql->select()
#4 /document_root/vendor/zendframework/zendframework/library/Zend/Session/SaveHandler/DbTableGateway.php(134): Zend\Db\TableGateway\AbstractTableGateway->select(Array)
#5 [internal function]: Zend\Session\SaveHandler\DbTableGateway->write(' in /document_root/vendor/zendframework/zendframework/library/Zend/Db/Sql/Select.php on line 764
like image 993
Will H Avatar asked Jan 14 '23 21:01

Will H


2 Answers

update :

zend framework >= 2.2, this issue is no more .

Old answer :

I have faced same issue on storing session into database , I have written initDbSession function inside basic module class \module\Application\Module.php

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        $e->getApplication()->getServiceManager()->get('translator');
        $eventManager        = $e->getApplication()->getEventManager();
        $moduleRouteListener = new ModuleRouteListener();
        $moduleRouteListener->attach($eventManager);

        // session start from here
        $this->initDbSession( $e );

    }


    /** 
     * Store session into database
     * 
     * @param type $e
     */
    private function initDbSession( MvcEvent $e )
    {
        // grab the config array
        $serviceManager     = $e->getApplication()->getServiceManager();
        $config             = $serviceManager->get('config');

        $dbAdapter          = $serviceManager->get('Zend\Db\Adapter\Adapter');

        /* some how this not works for me 
        $sessionOptions = new \Zend\Session\SaveHandler\DbTableGatewayOptions( null );

        $sessionTableGateway = new \Zend\Db\TableGateway\TableGateway('session', $dbAdapter);
        $saveHandler = new \Zend\Session\SaveHandler\DbTableGateway($sessionTableGateway, $sessionOptions);

        */

        /* I written my own save handler , I am using mysql as database */
        $saveHandler   = new \My\Session\SaveHandler\Mysql( $config['db'] );

        $sessionConfig = new \Zend\Session\Config\SessionConfig();
        $sessionConfig->setOptions($config['session']);

        // pass the saveHandler to the sessionManager and start the session
        $sessionManager = new \Zend\Session\SessionManager( $sessionConfig , NULL, $saveHandler );
        $sessionManager->start();

        \Zend\Session\Container::setDefaultManager($sessionManager);

    }    

    // other function goes here ...

Here my config file , which is located in \config\autoload\global.php

return array(        
        'db' => array(
            'driver'         => 'Pdo',
            'dsn'            => 'mysql:dbname=zf2;host=localhost',
            'driver_options' => array(
                PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',
                'buffer_results' => true
            ),
            'username'       => 'root',
            'password'       => '',
            'host'           => 'localhost',
            'dbname'         => 'zf2',
        ),         
        'session' => array(
              'remember_me_seconds' => 2419200,
              'use_cookies'       => true,
              'cookie_httponly'   => true,
              'cookie_lifetime'   => 2419200,
              'gc_maxlifetime'    => 2419200,

        ),
        'service_manager' => array(
            'factories' => array(                
                'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory'
            )
        )
);

Mysql Table structure is

CREATE TABLE `session` (
`id` CHAR(32) NOT NULL DEFAULT '',
`name` VARCHAR(255) NOT NULL,
`modified` INT(11) NULL DEFAULT NULL,
`lifetime` INT(11) NULL DEFAULT NULL,
`data` TEXT NULL,
PRIMARY KEY (`id`)
)COLLATE='utf8_general_ci' ENGINE=InnoDB;

Custom Mysql save handler class is given below . I have written this class, because new Zend\Session\SaveHandler\DbTableGateway not working on my server . My custom Mysql session save handler class written on \library\My\Session\SaveHandler\Mysql.php

<?php

namespace My\Session\SaveHandler;

use Zend\Session\SaveHandler\SaveHandlerInterface;

/**
 * Description of Mysql
 *
 * @author rab
 */
class Mysql  implements SaveHandlerInterface 
{

    /**
     * Session Save Path
     *
     * @var string
     */
    protected $sessionSavePath;

    /**
     * Session Name
     *
     * @var string
     */
    protected $sessionName;

    /**
     * Lifetime
     * @var int
     */
    protected $lifetime;

    /**
     * Constructor
     *
     */
    public function __construct( $dbConfig )
    {

        $this->dbconn  = mysql_connect(
            $dbConfig['host'],
            $dbConfig['username'], 
            $dbConfig['password']
        );

        if ( $this->dbconn ) {
            return mysql_select_db($dbConfig['dbname'], $this->dbconn);
        }
    }


    /**
     * Open the session
     * 
     * @return bool
     */
    public function open( $savePath, $name )
    {
        $this->sessionSavePath = $savePath;
        $this->sessionName     = $name;
        $this->lifetime        = ini_get('session.gc_maxlifetime');

        return true;

    }


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

        return mysql_close($this->dbconn);
    }


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

            $id     = mysql_real_escape_string($id);

            $sql    = "SELECT `data` FROM `session` " .
                      "WHERE id = '$id'";

            if ( $result = mysql_query($sql, $this->dbconn)) {
                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 ) 
    {

            $data   = (string) $data ;

            $dbdata = array(
                'modified' => time(),
                'data'     => mysql_real_escape_string( $data  ) ,
            );

            $selectSql = "SELECT * FROM session 
                          WHERE id = '$id' AND name = '{$this->sessionName}' ";

            $rs          = mysql_query( $selectSql, $this->dbconn );

            if ( $rs = mysql_query( $selectSql , $this->dbconn)) {
                if ( mysql_num_rows($rs) ) {

                    $updateSql = "UPDATE `session` SET 
                           `modified`= '".$dbdata['modified'] . "' ,
                           `data`= '".$dbdata['data']. "' 
                            WHERE id= '$id' AND name = '{$this->sessionName}' ";


                    mysql_query( $updateSql ,  $this->dbconn );
                    return true;

                }
            }


            $dbdata['lifetime']  = $this->lifetime;
            $dbdata['id']        = $id;
            $dbdata['name']      = $this->sessionName;

            $insertSql =  "INSERT INTO session (". implode(',' , array_keys($dbdata)) .")"
                           ."VALUES ('" . implode("','" , array_values( $dbdata )). "')";

            return mysql_query( $insertSql, $this->dbconn);

    }


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

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


    /**
     * Garbage Collector
     * 
     * @param int life time (sec.)
     * @return bool
     */
    public function gc( $maxlifetime ) 
    {

            $sql = sprintf("DELETE FROM `session` WHERE `modified` < '%s'",
                    mysql_real_escape_string(time() - $maxlifetime)
            );

            return mysql_query($sql, $this->dbconn);
    }

}

Which saves my session values into db table . For more information you can check http://php.net/manual/en/function.session-set-save-handler.php

like image 140
rab Avatar answered Jan 17 '23 10:01

rab


After all of the fixes that have recently gone into ZF2 my original solution is now working without modification however, I have added a new composer module for anyone that wants to use it, it reduces the need to do it all manually.

Installation details are in the readme file on: https://github.com/Nitecon/DBSessionStorage

Enjoy and let me know if you have any issues.

like image 42
Will H Avatar answered Jan 17 '23 10:01

Will H