Compare commits

..

8 Commits

Author SHA1 Message Date
Daniele Alessandri aa1b070f9b Bump VERSION and update CHANGELOG. 2013-02-03 13:59:55 +01:00
Daniele Alessandri 3e125c964c Implement Predis\Session\SessionHandler.
This class makes it easy to use Predis to store PHP sessions on Redis.

NOTE: requires PHP >= 5.4.0 or a polyfill for PHP's SessionHandlerInterface.
2013-02-02 15:14:06 +01:00
Daniele Alessandri e9fdc47d3f Minor fix in readme.
[ci skip]
2013-02-02 10:48:22 +01:00
Daniele Alessandri d3930ea298 Update markdown files to use relative links.
See https://help.github.com/articles/relative-links-in-readmes for details.

[ci skip]
2013-02-02 10:44:54 +01:00
Daniele Alessandri 0efcbb7992 Add a way to get the default value of an option from options.
It is not possible to get the default value of a client option using
either its name or instance:

  $options = array(
    'profile' => function ($options, $option) {
      // instance of Predis\Option\OptionInterface
      $profile = $options->getDefault($option);

      // string representing an option handled by $options,
      // returns NULL if the specified name is not handled.
      $profile = $options->getDefault('profile');

      return $profile;
    },
  );

This addition makes it less awkward to get the default value of an
option, especially when not in the context of a callable option
initializer.
2013-02-02 10:30:53 +01:00
Daniele Alessandri f2af247c63 Push minor changes in the standard executor internals. 2013-01-29 12:38:19 +01:00
Daniele Alessandri a5cf6d72cd Fix standard pipeline executor not parsing raw replies.
This bug was actually introduced right before pushing the stable release
of v0.8.0 in which we moved the responsibility of parsing raw replies
with command parser to consumer classes.

This commit closes #101.
2013-01-29 12:13:01 +01:00
Daniele Alessandri 7173f0c80c Get back to development. 2013-01-29 11:55:47 +01:00
13 changed files with 360 additions and 21 deletions
+15
View File
@@ -1,3 +1,18 @@
v0.8.2 (2013-02-03)
===============================================================================
- Added `Predis\Session\SessionHandler` to make it easy to store PHP sessions
on Redis using Predis. Please note that this class needs either PHP >= 5.4.0
or a polyfill for PHP's `SessionHandlerInterface`.
- Added the ability to get the default value of a client option directly from
`Predis\Option\ClientOption` using the `getDefault()` method by passing the
option name or its instance.
- __FIX__: the standard pipeline executor was not using the response parser
methods associated to commands to process raw responses (ISSUE #101).
v0.8.1 (2013-01-19)
===============================================================================
+1 -1
View File
@@ -68,7 +68,7 @@ $client->hmset('my:hash', array('field1'=>'value1', 'field2'=>'value2'); // valu
```
The only exception to this _rule_ is the [SORT](http://redis.io/commands/sort) command for which modifiers are
[passed using a named array](https://github.com/nrk/predis/blob/master/tests/Predis/Command/KeySortTest.php#L56-77).
[passed using a named array](tests/Predis/Command/KeySortTest.php#L56-77).
+3 -3
View File
@@ -140,7 +140,7 @@ $client = new Predis\Client('tcp://127.0.0.1', array(
```
For a more in-depth insight on how to create new connection backends you can look at the actual
implementation of the classes contained in `Predis\Connection` namespace.
implementation of the classes contained in the `Predis\Connection` namespace.
### Defining and registering new commands on the client at runtime ###
@@ -214,7 +214,7 @@ Redis. If you do not have Redis up and running, integration tests can be disable
suite is configured to execute integration tests using the server profile for Redis v2.4 (which is the
current stable version of Redis). You can optionally run the suite against a Redis instance built from
the `unstable` branch with the development profile by changing the `REDIS_SERVER_VERSION` to `dev` in
the `phpunit.xml` file. More details about testing Predis are available in `tests/README.md`.
the `phpunit.xml` file. More details on testing Predis can be found in [the tests README](tests/README.md).
Predis uses Travis CI for continuous integration. You can find the results of the test suite and the build
history [on its project page](http://travis-ci.org/nrk/predis).
@@ -252,4 +252,4 @@ history [on its project page](http://travis-ci.org/nrk/predis).
## License ##
The code for Predis is distributed under the terms of the MIT license (see LICENSE).
The code for Predis is distributed under the terms of the MIT license (see [LICENSE](LICENSE)).
+1 -1
View File
@@ -1 +1 @@
0.8.1
0.8.2
+2 -2
View File
@@ -51,8 +51,8 @@ $parameters = array(
);
$options = array(
'profile' => function ($options) {
$profile = ServerProfile::get('2.6');
'profile' => function ($options, $option) {
$profile = $options->getDefault($option);
$profile->defineCommand('hmgetall', 'HashMultipleGetAll');
return $profile;
+39
View File
@@ -0,0 +1,39 @@
<?php
require 'SharedConfigurations.php';
// This example demonstrates how to leverage Predis to save PHP sessions on Redis.
//
// The value of `session.gc_maxlifetime` in `php.ini` will be used by default as the
// the TTL for keys holding session data on Redis, but this value can be overridden
// when creating the session handler instance with the `gc_maxlifetime` option.
//
// Note that this class needs PHP >= 5.4 but can be used on PHP 5.3 if a polyfill for
// SessionHandlerInterface (see http://www.php.net/class.sessionhandlerinterface.php)
// is provided either by you or an external package like `symfony/http-foundation`.
if (!interface_exists('SessionHandlerInterface')) {
die("ATTENTION: the session handler implemented by Predis needs PHP >= 5.4.0 or a polyfill ".
"for \SessionHandlerInterface either provided by you or an external package.\n");
}
// Instantiate a new client just like you would normally do. We'll prefix our session keys here.
$client = new Predis\Client($single_server, array('prefix' => 'sessions:'));
// Set `gc_maxlifetime` so that a session will be expired after 5 seconds since last access.
$handler = new Predis\Session\SessionHandler($client, array('gc_maxlifetime' => 5));
// Register our session handler (it uses `session_set_save_handler()` internally).
$handler->register();
// Set a fixed session ID just for the sake of our example.
session_id('example_session_id');
session_start();
if (isset($_SESSION['foo'])) {
echo "Session has `foo` set to {$_SESSION['foo']}\n";
} else {
$_SESSION['foo'] = $value = mt_rand();
echo "Empty session, `foo` has been set with $value\n";
}
+1 -1
View File
@@ -33,7 +33,7 @@ use Predis\Transaction\MultiExecContext;
*/
class Client implements ClientInterface
{
const VERSION = '0.8.1';
const VERSION = '0.8.2';
private $options;
private $profile;
+19
View File
@@ -103,4 +103,23 @@ class ClientOptions implements ClientOptionsInterface
return $value;
}
}
/**
* Returns the default value for the specified option.
*
* @param string|OptionInterface $option Name or instance of the option.
* @return mixed
*/
public function getDefault($option)
{
if ($option instanceof OptionInterface) {
return $option->getDefault($this);
}
$options = $this->getDefaultOptions();
if (isset($options[$option])) {
return $options[$option]->getDefault($this);
}
}
}
+37 -10
View File
@@ -11,9 +11,12 @@
namespace Predis\Pipeline;
use Iterator;
use SplQueue;
use Predis\ResponseErrorInterface;
use Predis\ResponseObjectInterface;
use Predis\ServerException;
use Predis\Command\CommandInterface;
use Predis\Connection\ConnectionInterface;
use Predis\Connection\ReplicationConnectionInterface;
@@ -50,6 +53,27 @@ class StandardExecutor implements PipelineExecutorInterface
}
}
/**
* Handles a response object.
*
* @param ConnectionInterface $connection
* @param CommandInterface $command
* @param ResponseObjectInterface $response
* @return mixed
*/
protected function onResponseObject(ConnectionInterface $connection, CommandInterface $command, ResponseObjectInterface $response)
{
if ($response instanceof ResponseErrorInterface) {
return $this->onResponseError($connection, $response);
}
if ($response instanceof Iterator) {
return $command->parseResponse(iterator_to_array($response));
}
return $response;
}
/**
* Handles -ERR responses returned by Redis.
*
@@ -58,6 +82,10 @@ class StandardExecutor implements PipelineExecutorInterface
*/
protected function onResponseError(ConnectionInterface $connection, ResponseErrorInterface $response)
{
if (!$this->exceptions) {
return $response;
}
// Force disconnection to prevent protocol desynchronization.
$connection->disconnect();
$message = $response->getMessage();
@@ -70,24 +98,23 @@ class StandardExecutor implements PipelineExecutorInterface
*/
public function execute(ConnectionInterface $connection, SplQueue $commands)
{
$size = count($commands);
$values = array();
$exceptions = $this->exceptions;
$this->checkConnection($connection);
foreach ($commands as $command) {
$connection->writeCommand($command);
}
for ($i = 0; $i < $size; $i++) {
$response = $connection->readResponse($commands->dequeue());
$values = array();
if ($response instanceof ResponseErrorInterface && $exceptions === true) {
$this->onResponseError($connection, $response);
while (!$commands->isEmpty()) {
$command = $commands->dequeue();
$response = $connection->readResponse($command);
if ($response instanceof ResponseObjectInterface) {
$values[] = $this->onResponseObject($connection, $command, $response);
} else {
$values[] = $command->parseResponse($response);
}
$values[$i] = $response instanceof \Iterator ? iterator_to_array($response) : $response;
}
return $values;
+141
View File
@@ -0,0 +1,141 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Session;
use SessionHandlerInterface;
use Predis\ClientInterface;
/**
* Session handler class that relies on Predis\Client to store PHP's sessions
* data into one or multiple Redis servers.
*
* This class is mostly intended for PHP 5.4 but it can be used under PHP 5.3 provided
* that a polyfill for `SessionHandlerInterface` is defined by either you or an external
* package such as `symfony/http-foundation`.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class SessionHandler implements SessionHandlerInterface
{
protected $client;
protected $ttl;
/**
* @param ClientInterface $client Fully initialized client instance.
* @param array $options Session handler options.
*/
public function __construct(ClientInterface $client, Array $options = array())
{
$this->client = $client;
$this->ttl = (int) (isset($options['gc_maxlifetime']) ? $options['gc_maxlifetime'] : ini_get('session.gc_maxlifetime'));
}
/**
* Registers the handler instance as the current session handler.
*/
public function register()
{
if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
session_set_save_handler($this, true);
} else {
session_set_save_handler(
array($this, 'open'),
array($this, 'close'),
array($this, 'read'),
array($this, 'write'),
array($this, 'destroy'),
array($this, 'gc')
);
}
}
/**
* {@inheritdoc}
*/
public function open($save_path, $session_id)
{
// NOOP
return true;
}
/**
* {@inheritdoc}
*/
public function close()
{
// NOOP
return true;
}
/**
* {@inheritdoc}
*/
public function gc($maxlifetime)
{
// NOOP
return true;
}
/**
* {@inheritdoc}
*/
public function read($session_id)
{
if ($data = $this->client->get($session_id)) {
return $data;
}
return '';
}
/**
* {@inheritdoc}
*/
public function write($session_id, $session_data)
{
$this->client->setex($session_id, $this->ttl, $session_data);
return true;
}
/**
* {@inheritdoc}
*/
public function destroy($session_id)
{
$this->client->del($session_id);
return true;
}
/**
* Returns the underlying client instance.
*
* @return ClientInterface
*/
public function getClient()
{
return $this->client;
}
/**
* Returns the session max lifetime value.
*
* @return int
*/
public function getMaxLifeTime()
{
return $this->ttl;
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ name = "Predis"
desc = "Flexible and feature-complete PHP client library for Redis"
homepage = "http://github.com/nrk/predis"
license = "MIT"
version = "0.8.1"
version = "0.8.2"
stability = "stable"
channel = "pear.nrk.io"
+47
View File
@@ -78,4 +78,51 @@ class ClientOptionsTest extends StandardTestCase
$this->assertTrue(isset($options->custom));
$this->assertFalse(isset($options->profile));
}
/**
* @group disconnected
*/
public function testGetDefaultUsingOptionName()
{
$options = new ClientOptions();
$this->assertInstanceOf('Predis\Connection\PredisCluster', $options->getDefault('cluster'));
}
/**
* @group disconnected
*/
public function testGetDefaultUsingUnhandledOptionName()
{
$options = new ClientOptions();
$option = new ClientCluster();
$this->assertNull($options->getDefault('foo'));
}
/**
* @group disconnected
*/
public function testGetDefaultUsingOptionInstance()
{
$options = new ClientOptions();
$option = new ClientCluster();
$this->assertInstanceOf('Predis\Connection\PredisCluster', $options->getDefault($option));
}
/**
* @group disconnected
*/
public function testGetDefaultUsingUnhandledOptionInstance()
{
$options = new ClientOptions();
$option = new CustomOption(array(
'default' => function ($options) {
return 'foo';
},
));
$this->assertSame('foo', $options->getDefault($option));
}
}
+53 -2
View File
@@ -15,6 +15,7 @@ use \PHPUnit_Framework_TestCase as StandardTestCase;
use SplQueue;
use Predis\ResponseError;
use Predis\ResponseObjectInterface;
use Predis\Profile\ServerProfile;
/**
@@ -40,7 +41,7 @@ class StandardExecutorTest extends StandardTestCase
$replies = $executor->execute($connection, $pipeline);
$this->assertTrue($pipeline->isEmpty());
$this->assertSame(array('PONG', 'PONG', 'PONG'), $replies);
$this->assertSame(array(true, true, true), $replies);
}
/**
@@ -64,7 +65,29 @@ class StandardExecutorTest extends StandardTestCase
$replies = $executor->execute($connection, $pipeline);
$this->assertTrue($pipeline->isEmpty());
$this->assertSame(array('PONG', 'PONG', 'PONG'), $replies);
$this->assertSame(array(true, true, true), $replies);
}
/**
* @group disconnected
*/
public function testExecutorDoesNotParseResponseObjects()
{
$executor = new StandardExecutor();
$response = $this->getMock('Predis\ResponseObjectInterface');
$this->simpleResponseObjectTest($executor, $response);
}
/**
* @group disconnected
*/
public function testExecutorCanReturnRedisErrors()
{
$executor = new StandardExecutor(false);
$response = $this->getMock('Predis\ResponseErrorInterface');
$this->simpleResponseObjectTest($executor, $response);
}
/**
@@ -90,6 +113,34 @@ class StandardExecutorTest extends StandardTestCase
// ---- HELPER METHODS ------------------------------------------------ //
// ******************************************************************** //
/**
* Executes a test for the Predis\ResponseObjectInterface type.
*
* @param PipelineExecutorInterface $executor
* @param ResponseObjectInterface $response
*/
protected function simpleResponseObjectTest(PipelineExecutorInterface $executor, ResponseObjectInterface $response)
{
$pipeline = new SplQueue();
$command = $this->getMock('Predis\Command\CommandInterface');
$command->expects($this->never())
->method('parseResponse');
$connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
$connection->expects($this->once())
->method('writeCommand');
$connection->expects($this->once())
->method('readResponse')
->will($this->returnValue($response));
$pipeline->enqueue($command);
$replies = $executor->execute($connection, $pipeline);
$this->assertTrue($pipeline->isEmpty());
$this->assertSame(array($response), $replies);
}
/**
* Returns a list of queued command instances.
*