Skip to content

Commit

Permalink
Session: non-native session handler with locks is back!
Browse files Browse the repository at this point in the history
I was sad the native handler doesn't care about locks, so I had to revert the old session handler written in PHP. The native is still default, but this pull adds new option to turn it back on

redis: session: {native: off}
  • Loading branch information
fprochazka committed Aug 20, 2013
1 parent 40e2328 commit a7b53fa
Show file tree
Hide file tree
Showing 4 changed files with 740 additions and 20 deletions.
67 changes: 47 additions & 20 deletions src/Kdyby/Redis/DI/RedisExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class_alias('Nette\Config\Configurator', 'Nette\Configurator');
class RedisExtension extends Nette\DI\CompilerExtension
{

const DEFAULT_SESSION_PREFIX = 'Nette.Session:';
const DEFAULT_SESSION_PREFIX = Kdyby\Redis\RedisSessionHandler::NS_NETTE;

/**
* @var array
Expand Down Expand Up @@ -108,34 +108,61 @@ public function loadConfiguration()
'database' => $config['database'],
'prefix' => self::DEFAULT_SESSION_PREFIX,
'auth' => $config['auth'],
'native' => TRUE,
));

$params = array_diff_key($session, array_flip(array('host', 'port')));
if (substr($session['host'], 0, 1) === '/') {
$savePath = $session['host'];
if ($session['native']) {
$this->loadNativeSessionHandler($session);

} else {
$savePath = sprintf('tcp://%s:%d', $session['host'], $session['port']);
}
$builder->addDefinition($this->prefix('sessionHandler'))
->setClass('Kdyby\Redis\RedisSessionHandler', array(
new Nette\DI\Statement('Kdyby\Redis\RedisClient', array(
'host' => $session['host'],
'port' => $session['port'],
'database' => $session['database'],
'timeout' => $session['timeout'],
'auth' => $session['auth']
))
));

$options = array(
'saveHandler' => 'redis',
'savePath' => $savePath . ($params ? '?' . http_build_query($params, '', '&') : ''),
);

foreach ($builder->getDefinition('session')->setup as $statement) {
if ($statement->entity === 'setOptions') {
$statement->arguments[0] = Nette\DI\Config\Helpers::merge($options, $statement->arguments[0]);
unset($options);
break;
}
$builder->getDefinition('session')
->addSetup('setStorage', array($this->prefix('@sessionHandler')));
}
}
}

if (isset($options)) {
$builder->getDefinition('session')
->addSetup('setOptions', array($options));


protected function loadNativeSessionHandler(array $session)
{
$builder = $this->getContainerBuilder();

$params = array_diff_key($session, array_flip(array('host', 'port', 'native')));
if (substr($session['host'], 0, 1) === '/') {
$savePath = $session['host'];

} else {
$savePath = sprintf('tcp://%s:%d', $session['host'], $session['port']);
}

$options = array(
'saveHandler' => 'redis',
'savePath' => $savePath . ($params ? '?' . http_build_query($params, '', '&') : ''),
);

foreach ($builder->getDefinition('session')->setup as $statement) {
if ($statement->entity === 'setOptions') {
$statement->arguments[0] = Nette\DI\Config\Helpers::merge($options, $statement->arguments[0]);
unset($options);
break;
}
}

if (isset($options)) {
$builder->getDefinition('session')
->addSetup('setOptions', array($options));
}
}


Expand Down
185 changes: 185 additions & 0 deletions src/Kdyby/Redis/RedisSessionHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?php

/**
* This file is part of the Kdyby (http://www.kdyby.org)
*
* Copyright (c) 2008 Filip Procházka ([email protected])
*
* For the full copyright and license information, please view the file license.md that was distributed with this source code.
*/

namespace Kdyby\Redis;

use Kdyby;
use Nette\Diagnostics\Debugger;
use Nette;



/**
* Redis session handler allows to store session in redis using Nette\Http\Session.
*
* <code>
* $session->setStorage(new Kdyby\Redis\RedisSessionHandler($redisClient));
* </code>
*
* @author Filip Procházka <[email protected]>
*/
class RedisSessionHandler extends Nette\Object implements Nette\Http\ISessionStorage
{

/** @internal cache structure */
const NS_NETTE = 'Nette.Session:';

/**
* @var string
*/
private $savePath;

/**
* @var array
*/
private $ssIds = array();

/**
* @var RedisClient
*/
private $client;



/**
* @param RedisClient $redisClient
*/
public function __construct(RedisClient $redisClient)
{
$this->client = $redisClient;
}



/**
* @param $savePath
* @param $sessionName
*
* @return bool
*/
public function open($savePath, $sessionName)
{
$this->savePath = $savePath;
return TRUE;
}



/**
* @param string $id
*
* @return string
*/
public function read($id)
{
try {
$key = $this->formatKey($id);
$this->ssIds[$key] = $this->client->lock($key);
return (string) $this->client->get($key);

} catch (Exception $e) {
Debugger::log($e, 'redis-session');
return FALSE;
}
}



/**
* @param string $id
* @param string $data
*
* @return bool
*/
public function write($id, $data)
{
try {
$key = $this->formatKey($id);
$this->ssIds[$key] = $this->client->lock($key);
$this->client->setex($key, ini_get("session.gc_maxlifetime"), $data);
return TRUE;

} catch (Exception $e) {
Debugger::log($e, 'redis-session');
return FALSE;
}
}



/**
* @param string $id
*
* @return bool
*/
public function remove($id)
{
try {
$key = $this->formatKey($id);
$this->client->multi(function (RedisClient $client) use ($key) {
$client->del($key);
$client->unlock($key);
});
unset($this->ssIds[$key]);
return TRUE;

} catch (Exception $e) {
Debugger::log($e, 'redis-session');
return FALSE;
}
}



/**
* @param string $id
*
* @return string
*/
private function formatKey($id)
{
return self::NS_NETTE . $id;
}



/**
* @return bool
*/
public function close()
{
foreach ($this->ssIds as $key => $yes) {
$this->client->unlock($key);
}
$this->ssIds = array();

return TRUE;
}



/**
* @param int $maxLifeTime
*
* @return bool
*/
public function clean($maxLifeTime)
{
return TRUE;
}



public function __destruct()
{
$this->close();
}

}
Loading

0 comments on commit a7b53fa

Please sign in to comment.