2015-02-15

Session expire under Zend Framework 2

Few days ago I met a problem with session expire under Zend Framework 2. I set gc_maxlifetime in SessionManager but my session didn’t expire. Ok, you’ll say that I can set session.gc_maxlifetime in php.ini. Yes, I can and it’s one of the solutions. But in my solution, I want to manage session lifetime in application configuration. I looked deeper in ZF2 session implementation to find the problem and solutions. This is what I found.

My configuration looks like this:
<?php
//…
'session' => [
        'config' => [
            'class' => 'Zend\Session\Config\SessionConfig',
            'options' => [
                'name' => 'session_name',
                'use_cookies' => true,
                'gc_maxlifetime' => 1800
            ]
        ],
        'storage' => 'Zend\Session\Storage\SessionArrayStorage'
    ],
//…

And my SessionManager factory:

<?php
//…
class SessionManagerFactory implements FactoryInterface
{

    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $config = $serviceLocator->get('config');

        if (isset($config['session'])) {
            $session = $config['session'];

            $sessionConfig = null;
            if (isset($session['config'])) {
                $class = isset($session['config']['class']) ? $session['config']['class'] : 'Zend\Session\Config\SessionConfig';
                $options = isset($session['config']['options']) ? $session['config']['options'] : array();
                $sessionConfig = new $class();
                $sessionConfig->setOptions($options);
            }

            $sessionStorage = null;
            if (isset($session['storage'])) {
                $class = $session['storage'];
                $sessionStorage = new $class();
            }

            $sessionSaveHandler = null;
            if (isset($session['save_handler'])) {
                will require constructor arguments
                $sessionSaveHandler = $serviceLocator->get($session['save_handler']);
            }

            $sessionManager = new SessionManager($sessionConfig, $sessionStorage, $sessionSaveHandler);
        } else {
            $sessionManager = new SessionManager();
        }

        return $sessionManager;
    }
}

Ok, next step, I created session Container:

$sessionManager = $serviceLocator->get('my_project\SessionManager');        $container = new Container(’container_name’, sessionManager);

And what happened after 30 minutes? Nothing. My session still existed.

Let’s go deeper.

After first request Container will be created. If we print $_SESSION we will see something like this:

__ZF =>
[’container_name’] =>
['EXPIRE'] => 1423757542


After 30 minutes $_SESSION looks like this:
__ZF =>
[’container_name’] => null


And finally, my implementation to check if session has expired looks like this:

//...
abstract class ContainerFactoryAbstract implements FactoryInterface
{

    private $containerName;

    private $config;

    private $sessionManager;

    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        if (is_null($this->containerName)) {
            throw new RuntimeException('Is not set name of container!');
        }

        $this->config = $serviceLocator->get('Config');
        $this->sessionManager = $serviceLocator->get('SysSession\SessionManager');

        $container = $this->getContainer();

        if ($this->isSessionExpired($this->containerName)) {
            $this->sessionManager->destroy();
        }

        if ($this->getSessionMaxLifeTime()) {
            $container->setExpirationSeconds($this->getSessionMaxLifeTime());
        }

        return $container;
    }

    public function setContainerName($name)
    {
        $this->containerName = $name;
    }

    private function getContainer()
    {
        return new Container($this->containerName, $this->sessionManager);
    }

    private function isSetInitialSecureData(Container $container)
    {
        return isset($container->init);
    }

    private function isSessionExpired($containerName)
    {
        if (! $this->isSetSessionMaxLifeTime($this->config)) {
            return false;
        }

        return (isset($_SESSION['__ZF'][$containerName]) && (! $this->isSetExpireTimestamp($containerName)));
    }

    private function isSetSessionMaxLifeTime()
    {
        return isset($this->config['session']['config']['options']['gc_maxlifetime']);
    }

    private function getSessionMaxLifeTime()
    {
        if (! $this->isSetSessionMaxLifeTime()) {
            throw new RuntimeException('Is not set value for gc_maxlifetime session!');
        }

        return $this->config['session']['config']['options']['gc_maxlifetime'];
    }

    private function isSetExpireTimestamp($containerName)
    {
        return isset($_SESSION['__ZF'][$containerName]['EXPIRE']);
    }
}