Compare commits

...

69 Commits

Author SHA1 Message Date
Daniele Alessandri 8c0498a893 Bump VERSION and update CHANGELOG. 2013-02-18 15:03:45 +01:00
Daniele Alessandri aba24d0f6f Remove a few more unnecessary strict comparisons. 2013-02-17 20:46:31 +01:00
Daniele Alessandri a1df4f20da Expose private method used to parse URI strings.
This is useful for 3rd party libraries such as PredisServiceProvider
so it makes sense to have it public and static.
2013-02-17 20:38:46 +01:00
Daniele Alessandri 116eaba75e Deprecate the whole Predis\Helpers class. 2013-02-17 15:21:01 +01:00
Daniele Alessandri de4bae3f9b Prevent E_NOTICE messages on __destruct() if $parameters is not set.
This can only happen when throwing on invalid connection parameters,
this is a quick fix that will do for now.
2013-02-17 15:21:01 +01:00
Daniele Alessandri b50a5975ac Use a nicer looking version for the same code. 2013-02-17 15:21:01 +01:00
Daniele Alessandri 1c1b4096f6 Remove a few more unnecessary strict comparisons. 2013-02-17 15:20:57 +01:00
Daniele Alessandri f8e4ba152f Use hexadecimal notation. 2013-02-17 12:27:45 +01:00
Daniele Alessandri 6b6b73f5f4 Add getArgument($index) in Predis\Command\CommandInterface.
This method should have been part of the interface since start since
it is used through the library. We also do not specify a default value
for the index argument since it does not make much sense.
2013-02-17 12:22:00 +01:00
Daniele Alessandri 729e40d6c0 Use 0 to indicate no arguments for KEYS[] in Lua scripted commands.
We previously used FALSE for that but in the end it does not make much
sense. Luckily for us this does not represent a breaking change since
existing code will keep to work, so we can safely push this change into
the next patch release.
2013-02-16 17:40:49 +01:00
Daniele Alessandri fbcfdc343e Do not implicitly set multibulk replies on composable connections.
This should be done externally on the underlying protocol instance.
2013-02-16 17:05:38 +01:00
Daniele Alessandri 8cbcb09c4c Remove a few unnecessary strict comparisons. 2013-02-16 17:05:12 +01:00
Daniele Alessandri 2781bd780f Fetch connection factory directly from client options. 2013-02-16 14:36:04 +01:00
Daniele Alessandri 9675626aac Add "tcp_nodelay" in the list of supported connection parameters.
[ci skip]
2013-02-16 12:14:05 +01:00
Daniele Alessandri f8829985f7 Tweak CHANGELOG.
[ci skip]
2013-02-16 11:59:43 +01:00
Daniele Alessandri e2e809c9d4 Fix test to handle different hashes generated on 64bits builds of PHP.
Just noticed it on Travis CI as they recently switched to 64bits PHP builds,
also great to see that the library seems to work fine anyway.
2013-02-16 11:46:42 +01:00
Daniele Alessandri ef0067e1a4 Use slightly faster version of the same code.
Yet again, micro-optimizations here.
2013-02-16 11:32:29 +01:00
Daniele Alessandri da343046e5 No need for strict comparison here.
Should be also slightly faster (at least on a micro-optimization scale).
2013-02-16 11:14:55 +01:00
Daniele Alessandri 76d6681f68 Fix unserialization of Predis/Connection/PhpiredisStreamConnection. 2013-02-16 11:14:55 +01:00
Daniele Alessandri 8d01be388d Support TCP_NODELAY for stream-based connections on PHP >= 5.4.0.
This cannot be implemented for previous versions of PHP because we
need socket_import_stream() to extract the underlying socket resource
from the stream in order to be able to set the TCP_NODELAY flag.
2013-02-16 11:14:50 +01:00
Daniele Alessandri c38376dcc4 Accepts callable as first argument of Predis\Client::__construct().
Users can then use callables to wrap the creation and initialization
of the underlying connection with custom strategies:
2013-02-15 11:36:19 +01:00
Daniele Alessandri c354d02105 Aggregated connection for redis-cluster now uses 16384 hash slots.
This commit reflects the recent change from the redis unstable branch
in which the number of hash slots was increased from 4096 to 16384.

See https://github.com/antirez/redis/commit/ebd666d for reference.
2013-02-14 21:54:23 +01:00
Daniele Alessandri d6685a424a Update FAQ. 2013-02-09 10:11:16 +01:00
Daniele Alessandri 05ef62ff97 Tweak README. 2013-02-09 10:06:30 +01:00
Daniele Alessandri b76e876b73 No need for "iterable_multibulk" as a default connection parameter.
Furthermore, connections that do not support certain parameters should
check and throw whether they are set, independently from their value.
2013-02-09 09:56:07 +01:00
Daniele Alessandri ebd895a67d Update link to phpiredis. 2013-02-09 09:49:57 +01:00
Daniele Alessandri 923e7ed5fd Add Predis\Connection\PhpiredisStreamConnection.
This class works just like Predis\Connection\PhpiredisConnection but
it does not require the socket extensions since it relies on PHP's
native streams thus allowing the use of persistent connections.
2013-02-09 09:46:48 +01:00
Daniele Alessandri 9e0fe7bdc7 Back to development for the next patch release.
Forgot to do that prior to merge, my bad.

[ci skip]
2013-02-08 14:54:53 +01:00
Daniele Alessandri 5d774dc581 Merge branch 'gh/pr/102' into v0.8 2013-02-08 12:37:43 +01:00
Daniele Alessandri dffbb8b042 [tests] Fix version check to work on Redis < 2.6.0.
INFO does not have the concept of sections prior to 2.6.0 while our
test suite can be run against 2.4.0 (provided that phpunit.xml uses
the correct server profile), so we need to make sure that the check
does not break with prior version of Redis even if this is unlikely
to happen at this point.

Also changed a bit the naming of variables since things are already
pretty much explicit in this context.
2013-02-08 12:33:07 +01:00
Raphael Stolt 06d5475129 Added PHPUnit utility method markTestSkippedOnRedisVersionBelow and applied it where necessary 2013-02-07 22:46:25 +01:00
Raphael Stolt 1536455fea Added CLIENT SET/GETNAME command 2013-02-07 20:21:29 +01:00
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
Daniele Alessandri 495e3c6f8a Bump VERSION and update CHANGELOG.
[ci skip]
2013-01-19 11:18:58 +01:00
Daniele Alessandri 3465bc7c1f Add PHP 5.5 to the CI build matrix and switch to branch blacklisting. 2013-01-19 11:16:20 +01:00
Daniele Alessandri 8bf896f81f Update CHANGELOG. 2013-01-19 09:56:51 +01:00
Daniele Alessandri 0085bc08ae Fix CHANGELOG. 2013-01-19 09:46:58 +01:00
Daniele Alessandri f7232b9ddc Highlight PHP code in FAQ. 2013-01-19 09:44:14 +01:00
Daniele Alessandri 95cc528904 Remove <?php tags from README.
It seems like GitHub can now highlight PHP syntax without those ones.
2013-01-19 09:44:09 +01:00
Daniele Alessandri 4a16cb9417 Update .gitignore.
[ci skip]
2013-01-13 13:54:09 +01:00
Daniele Alessandri 94758d5f58 Update CHANGELOG.
[ci skip]
2013-01-13 13:53:22 +01:00
Daniele Alessandri acc0f04bdb Add missing param to phpdoc entry.
[ci skip]
2013-01-13 13:48:50 +01:00
Daniele Alessandri 09de7be7cb Add optional callable to drive extraction of node hash in distributor.
This is mainly in response to the longstanding issue #36 in which my
proposed solution was fine in terms of functionalities, but eventually
never made into the repository since it was far from being clean enough
for my taste.

Now developers can optionally pass a callable object when creating the
hashring instance to decide how the distributor should extract the hash
from a node (really a connection instance) to populate the ring:

  use Predis\Cluster\Distribution\HashRing;
  use Predis\Connection\PredisCluster;

  $servers = array(
    'tcp://10.0.0.1?alias=node01',
    'tcp://10.0.0.2?alias=node02',
  );

  $options = array(
    'nodehash' => function ($connection) {
      return $connection->getParameters()->alias;
    },
    'cluster' => function ($options) {
      $replicas = HashRing::DEFAULT_REPLICAS;
      $hashring = new HashRing($replicas, $options->nodehash);
      $cluster  = new PredisCluster($hashring);

      return $cluster;
    },
  );

  $client = new Predis\Client($servers, $options);

Both HashRing and KetamaPureRing in the Predis\Cluster\Distribution
namespace support this new approach.
2013-01-13 13:10:01 +01:00
Daniele Alessandri 70b279bbdc Bump year in LICENSE.
On a related note, happy new year!

[ci skip]
2013-01-01 13:58:18 +01:00
Daniele Alessandri f8cbbc5b8f Update URL for phpiredis.
Ownership of the repository has been transferred. Thanks again to
@seppo0010, the original author of phpiredis, for his work on the
extension!

[ci skip]
2012-12-28 20:51:34 +01:00
Daniele Alessandri 83720075f3 Make it possible to cast key prefix processor instance to string.
Mostly useful with string interpolation when getting the current prefix
associated to a client instance.
2012-12-27 10:50:25 +01:00
Daniele Alessandri 9901233fa1 Instantiate the current class when subclassing Client.
Previously the getClientFor() method in a subclass of Predis\Client
returned an instance of Predis\Client instead of a new instance of
the subclass. The new behaviour is more correct.
2012-12-24 14:50:07 +01:00
Daniele Alessandri dcb4093046 Update CHANGELOG. 2012-12-15 17:46:00 +01:00
Daniele Alessandri 1e8af648cb [tests] Add missing tests for Predis\Option\ClientReplication. 2012-12-15 17:22:10 +01:00
Daniele Alessandri ff5e3515c3 Implement callable initializer for "connections" client option. 2012-12-15 17:12:37 +01:00
Daniele Alessandri 59813cd74e Pass option handler instance to callable initializers.
This is just a convention implemented to client options supporting
callable initializers such as "profile", "cluster" and "replication".

This is useful to get a fully-initialized default value and perform
additional operations before returning it. An example with "profile":

  $options = array(
    'commands' => array(
      'test1' => 'Predis\Command\ConnectionEcho',
      'test2' => 'Predis\Command\ConnectionEcho',
    ),
    'profile'  => function ($options, $option) {
      $profile = $option->getDefault($options);

      if (is_array($options->commands)) {
        foreach ($options->commands as $id => $cmd) {
          $profile->defineCommand($id, $cmd);
        }
      }

      return $profile;
    },
  );
2012-12-15 16:47:32 +01:00
Daniele Alessandri bfd96b15dc Fix response parsing with scripted commands after -NOSCRIPT.
This commit fixes issue #94.
2012-12-12 14:28:06 +01:00
Daniele Alessandri 51b466267d Fix missing use directives.
This miss caused a PHP error when Redis did not return a +QUEUED
reply inside a MULTI / EXEC command.
2012-11-21 22:57:23 +01:00
Daniele Alessandri 78f3596566 Merge branch 'tests/update-redis-2.8' into v0.8 2012-11-21 22:13:42 +01:00
Daniele Alessandri ac01c1a17f [tests] TTL and PTTL return -2 on non existing keys in Redis >= 2.8. 2012-11-21 22:09:05 +01:00
Daniele Alessandri 46920c9c77 [tests] Fix expected exception message on wrong key type.
Redis >= 2.8 returns -WRONGTYPE errors instead of -ERR when executing
operations on wrong key type (such as trying to LPUSH on a string key).

Luckily for us, phpunit's @expectedExceptionMessage annotation actually
does not perform an exact match but works on a substring so we just omit
the initial part of the exception message to make the test work.
2012-11-21 21:49:49 +01:00
Daniele Alessandri b26d6103a6 Avoid channels reordering issues when testing UNSUBSCRIBE.
When using UNSUBSCRIBE without a list of channels (which actually means
"unsubscribe from all the subscribed channels") Redis 2.6 does not
guarantee that channels are returned following the same order of
subscription.
2012-11-13 16:44:55 +01:00
Daniele Alessandri a4760d65cd Bump Redis version used in tests on Travis CI.
It seems like Travis CI switched silently to Redis 2.6, which means
that we can enable the full test suite of Predis.
2012-11-13 16:13:07 +01:00
Daniele Alessandri 49aa6f0aca Update performance numbers in the related FAQ section.
We use newer version of PHP, phpiredis, phpredis, Redis and Ubuntu.

[ci skip]
2012-11-08 13:06:24 +01:00
Daniele Alessandri 292f9939b2 Tweak CONTRIBUTING instructions.
[ci skip]
2012-11-03 16:02:43 +01:00
Daniele Alessandri daa36a980d Make Predis\Connection\PhpiredisConnection::write() protected.
We should actually make use of more protected methods in this
class instead of the private ones, this is most likely going
to be addressed in future commits.

See also issue #86 on GitHub for the reason behind this change.
2012-11-03 15:21:52 +01:00
Daniele Alessandri 0b09d2c359 Back to development for the next patch release. 2012-11-03 15:16:01 +01:00
162 changed files with 1679 additions and 361 deletions
+2
View File
@@ -2,4 +2,6 @@
*.phar
phpunit.xml
package.xml
composer.lock
experiments/
vendor/
+6 -4
View File
@@ -2,10 +2,12 @@ language: php
php:
- 5.3
- 5.4
- 5.5
branches:
only:
- master
- v0.7
- v0.8
except:
- v0.5
- v0.6
- php5.2_backport
- documentation
services: redis-server
script: phpunit -c phpunit.xml.travisci
+78 -2
View File
@@ -1,3 +1,79 @@
v0.8.3 (2013-02-18)
===============================================================================
- Added `CLIENT SETNAME` and `CLIENT GETNAME` (ISSUE #102).
- Implemented the `Predis\Connection\PhpiredisStreamConnection` class using the
`phpiredis` extension like `Predis\Connection\PhpiredisStreamConnection`, but
without requiring the `socket` extension since it relies on PHP's streams.
- Added support for the TCP_NODELAY flag via the `tcp_nodelay` parameter for
stream-based connections, namely `Predis\Connection\StreamConnection` and
`Predis\Connection\PhpiredisStreamConnection` (requires PHP >= 5.4.0).
- Updated the aggregated connection class for redis-cluster to work with 16384
hash slots instead of 4096 to reflect the recent change from redis unstable
([see this commit](https://github.com/antirez/redis/commit/ebd666d)).
- The constructor of `Predis\Client` now accepts a callable as first argument
returning `Predis\Connection\ConnectionInterface`. Users can create their
own self-contained strategies to create and set up the underlying connection.
- Users should return `0` from `Predis\Command\ScriptedCommand::getKeysCount()`
instead of `FALSE` to indicate that all of the arguments of a Lua script must
be used to populate `ARGV[]`. This does not represent a breaking change.
- The `Predis\Helpers` class has been deprecated and it will be removed in
future releases.
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)
===============================================================================
- The `connections` client option can now accept a callable object returning
an instance of `Predis\Connection\ConnectionFactoryInterface`.
- Client options accepting callable objects as factories now pass their actual
instance to the callable as the second argument.
- `Predis\Command\Processor\KeyPrefixProcessor` can now be directly casted to
string to obtain the current prefix, useful with string interpolation.
- Added an optional callable argument to `Predis\Cluster\Distribution\HashRing`
and `Predis\Cluster\Distribution\KetamaPureRing` constructor that can be used
to customize how the distributor should extract the connection hash when
initializing the nodes distribution (ISSUE #36).
- Correctly handle `TTL` and `PTTL` returning -2 on non existing keys starting
with Redis 2.8.
- __FIX__: a missing use directive in `Predis\Transaction\MultiExecContext`
caused PHP errors when Redis did not return `+QUEUED` replies to commands
when inside a MULTI / EXEC context.
- __FIX__: the `parseResponse()` method implemented for a scripted command was
ignored when retrying to execute a Lua script by falling back to `EVAL` after
a `-NOSCRIPT` error (ISSUE #94).
- __FIX__: when subclassing `Predis\Client` the `getClientFor()` method returns
a new instance of the subclass instead of a new instance of `Predis\Client`.
v0.8.0 (2012-10-23)
===============================================================================
@@ -351,7 +427,7 @@ v0.6.0 (2010-05-24)
to instances of Redis 1.2.x).
- Support for Redis 1.0 is now optional and it is provided by requiring
'Predis_Compatibility.php' before creating an instance of Predis\Client.
'Predis_Compatibility.php' before creating an instance of `Predis\Client`.
- New commands added to the Redis 2.0 profile since Predis 0.5.1:
- Strings: `SETEX`, `APPEND`, `SUBSTR`
@@ -364,7 +440,7 @@ v0.6.0 (2010-05-24)
- Introduced client-level options with the new `Predis\ClientOptions` class.
Options can be passed to the constructor of `Predis\Client` in its second
argument as an array or an instance of Predis\ClientOptions. For brevity's
argument as an array or an instance of `Predis\ClientOptions`. For brevity's
sake and compatibility with older versions, the constructor still accepts
an instance of `Predis\RedisServerProfile` in its second argument. The
currently supported client options are:
+5 -4
View File
@@ -6,10 +6,11 @@ section of the project.
When reporting bugs, in addition to the obvious description of your issue you __must__ always provide
some essential information about your environment such as:
1. version of Predis.
2. version of PHP.
3. name and version of the operating system.
4. when possible, a small snippet of code that reproduces the issue.
1. version of Predis (check the `VERSION` file or the `Predis\Client::VERSION` constant).
2. version of Redis (check the `redis_version` field returned by [`INFO`](http://redis.io/commands/info)).
3. version of PHP.
4. name and version of the operating system.
5. when possible, a small snippet of code that reproduces the issue.
__Think about it__: we do not have a crystal ball and cannot predict things and peer into the unknown,
so please provide as much details as possible to help us isolating issues and fix them.
+33 -26
View File
@@ -37,7 +37,7 @@ connections to the server, but this behavior can be inconvenient in certain scen
need to do an upfront check to detect if the server is up and running and eventually catch exceptions on
failures. In this case developers can use `Predis\Client::connect()` to explicitly connect to the server:
```
```php
$client = new Predis\Client();
try {
@@ -59,7 +59,7 @@ Redis works or if you need to look up how to use certain commands. Alternatively
accept an array for keys or values (depending on the command) instead of a list of arguments. See for
example how [RPUSH](http://redis.io/commands/rpush) or [HMSET](http://redis.io/commands/hmset) work:
```
```php
$client->rpush('my:list', 'value1', 'value2', 'value3'); // values as arguments
$client->rpush('my:list', array('value1', 'value2', 'value3')); // values as single argument array
@@ -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).
@@ -79,18 +79,18 @@ _________________________________________________
### Predis is a pure-PHP implementation: it can not be fast enough! ###
It really depends, but most of the times the answer is: _yes, it is fast enough_. I will give you
a couple of easy numbers using a single Predis client with PHP 5.3.5 (custom build) and Redis 2.2
(localhost) under Ubuntu 11.04 (running on a Intel Q6600):
a couple of easy numbers using a single Predis client with PHP 5.4.7 (custom build) and Redis 2.2
(localhost) under Ubuntu 12.04.1 (running on a Intel Q6600):
19600 SET/sec using 12 bytes for both key and value
18900 GET/sec while retrieving the very same values
0.200 seconds to fetch 30000 keys using _KEYS *_.
21500 SET/sec using 12 bytes for both key and value
21000 GET/sec while retrieving the very same values
0.130 seconds to fetch 30000 keys using _KEYS *_.
How does it compare with a nice C-based extension such as [__phpredis__](http://github.com/nicolasff/phpredis)?
30500 SET/sec using 12 bytes for both key and value
31000 GET/sec while retrieving the very same values
0.030 seconds to fetch 30000 keys using "KEYS *"".
30100 SET/sec using 12 bytes for both key and value
29400 GET/sec while retrieving the very same values
0.035 seconds to fetch 30000 keys using "KEYS *"".
Wow, __phpredis__ looks so much faster! Well we are comparing a C extension with a pure-PHP library so
lower numbers are quite expected, but there is a fundamental flaw in them: is this really how you are
@@ -104,14 +104,14 @@ Redis, but how these numbers change when we hit the network by connecting to ins
reside on other servers?
Using Predis:
3600 SET/sec using 12 bytes for both key and value
3600 GET/sec while retrieving the very same values
0.210 seconds to fetch 30000 keys using "KEYS *".
3200 SET/sec using 12 bytes for both key and value
3200 GET/sec while retrieving the very same values
0.132 seconds to fetch 30000 keys using "KEYS *".
Using phpredis:
4000 SET/sec using 12 bytes for both key and value
4000 GET/sec while retrieving the very same values
0.051 seconds to fetch 30000 keys using "KEYS *".
3500 SET/sec using 12 bytes for both key and value
3500 GET/sec while retrieving the very same values
0.045 seconds to fetch 30000 keys using "KEYS *".
There you go, you get almost the same average numbers and the reason is quite simple: network latency
is a real performance killer and you cannot do (almost) anything about that. As a disclaimer, please
@@ -124,14 +124,21 @@ to how your application will use Redis.
### I am convinced, but performances for multi-bulk replies (e.g. _KEYS *_) are still worse ###
Fair enough, but there is actually an option for you if you need even more speed and it consists on
installing __[phpiredis](http://github.com/seppo0010/phpiredis)__ (note the additional _i_ in the
name) and let Predis using it. __phpiredis__ is a C-based extension that wraps __hiredis__ (the
official Redis C client library) with a thin layer that exposes its features to PHP. You will now
get the benefits of a faster protocol parser just by adding a single line of code in your application:
installing __[phpiredis](http://github.com/nrk/phpiredis)__ (note the additional _i_ in the name)
and let Predis using it. __phpiredis__ is a C-based extension that wraps __hiredis__ (the official
Redis C client library) with a thin layer that exposes its features to PHP. You can choose between
two different connection backend classes: `Predis\Connection\PhpiredisConnection` (it depends on the
`socket` extension) and `Predis\Connection\PhpiredisStreamConnection` (it uses PHP's native streams).
You will now get the benefits of a faster protocol parser just by adding a couple of lines of code:
$client = new Predis\Client('tcp://127.0.0.1', array(
'connections' => array('tcp' => 'Predis\Connection\PhpiredisConnection')
));
```php
$client = new Predis\Client('tcp://127.0.0.1', array(
'connections' => array(
'tcp' => 'Predis\Connection\PhpiredisConnection',
'unix' => 'Predis\Connection\PhpiredisConnection',
),
));
```
As simple as it is, nothing will really change in the way you use the library in your application. So,
how fast is it now? There are not much improvements for inline or short bulk replies (e.g. _SET_ or
@@ -139,8 +146,8 @@ _GET_), but the speed for parsing multi-bulk replies is now on par with phpredis
Using Predis with a phpiredis-based connection to fetch 30000 keys using _KEYS *_:
0.031 seconds from a local Redis instance
0.058 seconds from a remote Redis instance
0.035 seconds from a local Redis instance
0.047 seconds from a remote Redis instance
### If I need to install a C extension to get better performances, why not using phpredis? ###
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2009-2012 Daniele Alessandri
Copyright (c) 2009-2013 Daniele Alessandri
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
+24 -25
View File
@@ -3,12 +3,12 @@
Predis is a flexible and feature-complete PHP (>= 5.3) client library for the Redis key-value store.
The library does not require any additional extension loaded in PHP but it can be optionally paired
with the [phpiredis](https://github.com/seppo0010/phpiredis) C extension to lower the overhead of
with the [phpiredis](https://github.com/nrk/phpiredis) C-based extension to lower the overhead of
serializing and parsing the Redis protocol. Predis is also available in an asynchronous fashion
through the experimental client provided by the [Predis\Async](http://github.com/nrk/predis-async)
library.
For a list of frequently asked questions about Predis, see __FAQ.md__ in the root of the repository.
For a list of frequently asked questions about Predis see our [FAQ](FAQ.md).
More details are available on the [official wiki](http://wiki.github.com/nrk/predis) of the project.
@@ -46,7 +46,7 @@ compatible with most of the major frameworks and libraries. Autoloading in your
automatically when managing the dependencies with Composer, but you can also leverage its own autoloader
class if you are going to use it in a project or script without any PSR-0 compliant autoloading facility:
``` php
```php
<?php
// prepend a base path if Predis is not present in your "include_path".
require 'Predis/Autoloader.php';
@@ -68,8 +68,7 @@ using functions such as `require` and `include`, but this practice is not encour
By default Predis uses `127.0.0.1` and `6379` as the default host and port when creating a new client
instance without specifying any connection parameter:
``` php
<?php
```php
$redis = new Predis\Client();
$redis->set('foo', 'bar');
$value = $redis->get('foo');
@@ -77,8 +76,7 @@ $value = $redis->get('foo');
It is possible to specify the various connection parameters using URI strings or named arrays:
``` php
<?php
```php
$redis = new Predis\Client('tcp://10.0.0.1:6379');
// is equivalent to:
@@ -98,8 +96,7 @@ Furthermore, pipelining works transparently even on aggregated connections. To a
supports client-side sharding using consistent-hashing on keys while clustered connections are supported
natively by the client class.
``` php
<?php
```php
$redis = new Predis\Client(array(
array('host' => '10.0.0.1', 'port' => 6379),
array('host' => '10.0.0.2', 'port' => 6379)
@@ -116,14 +113,19 @@ $replies = $redis->pipeline(function ($pipe) {
### Multiple and customizable connection backends ###
Predis can optionally use different connection backends to connect to Redis. One of them leverages
the [phpiredis](http://github.com/seppo0010/phpiredis) C extension resulting in a major speed bump
especially when dealing with long multibulk replies (the `socket` extension is also required):
Predis can optionally use different connection backends to connect to Redis. Two of them leverage
the [phpiredis](http://github.com/nrk/phpiredis) C-based extension resulting in a major speed bump
especially when dealing with long multibulk replies, namely `Predis\Connection\PhpiredisConnection`
(the `socket` extension is also required) and `Predis\Connection\StreamPhpiredisConnection` (it
does not require additional extensions since it relies on PHP's native streams). Both of them can
connect to Redis using standard TCP/IP connections or UNIX domain sockets:
``` php
<?php
```php
$client = new Predis\Client('tcp://127.0.0.1', array(
'connections' => array('tcp' => 'Predis\Connection\PhpiredisConnection')
'connections' => array(
'tcp' => 'Predis\Connection\PhpiredisConnection',
'unix' => 'Predis\Connection\PhpiredisStreamConnection',
)
));
```
@@ -131,8 +133,7 @@ Developers can also create their own connection backends to add support for new
existing ones or provide different implementations. Connection backend classes must implement
`Predis\Connection\SingleConnectionInterface` or extend `Predis\Connection\AbstractConnection`:
``` php
<?php
```php
class MyConnectionClass implements Predis\Connection\SingleConnectionInterface
{
// implementation goes here
@@ -145,7 +146,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 ###
@@ -156,8 +157,7 @@ code or waiting for it to find its way into a stable Predis release, then you ca
a new class that matches the command type and its behaviour and then bind it to a client instance at
runtime. Actually, it is easier done than said:
``` php
<?php
```php
class BrandNewRedisCommand extends Predis\Command\AbstractCommand
{
public function getId()
@@ -181,8 +181,7 @@ in the server profile being used by the client instance. Internally, scripted co
to save bandwidth, but they are capable of falling back to [EVAL](http://redis.io/commands/eval)
when needed:
``` php
<?php
```php
class ListPushRandomValue extends Predis\Command\ScriptedCommand
{
public function getKeysCount()
@@ -221,7 +220,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).
@@ -255,8 +254,8 @@ history [on its project page](http://travis-ci.org/nrk/predis).
- [Lorenzo Castelli](http://github.com/lcastelli)
- [Jordi Boggiano](http://github.com/Seldaek) ([twitter](http://twitter.com/seldaek))
- [Sebastian Waisbrot](http://github.com/seppo0010) ([twitter](http://twitter.com/seppo0010))
for his work on extending [phpiredis](http://github.com/seppo0010/phpiredis) for Predis
for his past work on extending [phpiredis](http://github.com/nrk/phpiredis) for 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.0
0.8.3
+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";
}
+30 -13
View File
@@ -33,12 +33,11 @@ use Predis\Transaction\MultiExecContext;
*/
class Client implements ClientInterface
{
const VERSION = '0.8.0';
const VERSION = '0.8.3';
private $options;
private $profile;
private $connection;
private $connections;
/**
* Initializes a new client with optional connection parameters and client options.
@@ -50,7 +49,6 @@ class Client implements ClientInterface
{
$this->options = $this->filterOptions($options);
$this->profile = $this->options->profile;
$this->connections = $this->options->connections;
$this->connection = $this->initializeConnection($parameters);
}
@@ -64,7 +62,7 @@ class Client implements ClientInterface
*/
protected function filterOptions($options)
{
if ($options === null) {
if (!isset($options)) {
return new ClientOptions();
}
@@ -94,13 +92,26 @@ class Client implements ClientInterface
}
if (is_array($parameters) && isset($parameters[0])) {
$replication = isset($this->options->replication) && $this->options->replication;
$connection = $this->options->{$replication ? 'replication' : 'cluster'};
$options = $this->options;
$replication = isset($options->replication) && $options->replication;
$connection = $options->{$replication ? 'replication' : 'cluster'};
return $this->connections->createAggregated($connection, $parameters);
return $options->connections->createAggregated($connection, $parameters);
}
return $this->connections->create($parameters);
if (is_callable($parameters)) {
$connection = call_user_func($parameters, $this->options);
if (!$connection instanceof ConnectionInterface) {
throw new \InvalidArgumentException(
'Callable parameters must return instances of Predis\Connection\ConnectionInterface'
);
}
return $connection;
}
return $this->options->connections->create($parameters);
}
/**
@@ -126,7 +137,7 @@ class Client implements ClientInterface
*/
public function getConnectionFactory()
{
return $this->connections;
return $this->options->connections;
}
/**
@@ -138,11 +149,11 @@ class Client implements ClientInterface
*/
public function getClientFor($connectionID)
{
if (($connection = $this->getConnectionById($connectionID)) === null) {
if (!$connection = $this->getConnectionById($connectionID)) {
throw new \InvalidArgumentException("Invalid connection ID: '$connectionID'");
}
return new Client($connection, $this->options);
return new static($connection, $this->options);
}
/**
@@ -267,10 +278,16 @@ class Client implements ClientInterface
$eval = $this->createCommand('eval');
$eval->setRawArguments($command->getEvalArguments());
return $this->executeCommand($eval);
$response = $this->executeCommand($eval);
if (!$response instanceof ResponseObjectInterface) {
$response = $command->parseResponse($response);
}
return $response;
}
if ($this->options->exceptions === true) {
if ($this->options->exceptions) {
throw new ServerException($response->getMessage());
}
+11 -5
View File
@@ -26,19 +26,21 @@ class HashRing implements DistributionStrategyInterface, HashGeneratorInterface
const DEFAULT_REPLICAS = 128;
const DEFAULT_WEIGHT = 100;
private $nodes;
private $ring;
private $ringKeys;
private $ringKeysCount;
private $replicas;
private $nodeHashCallback;
private $nodes = array();
/**
* @param int $replicas Number of replicas in the ring.
* @param mixed $nodeHashCallback Callback returning the string used to calculate the hash of a node.
*/
public function __construct($replicas = self::DEFAULT_REPLICAS)
public function __construct($replicas = self::DEFAULT_REPLICAS, $nodeHashCallback = null)
{
$this->replicas = $replicas;
$this->nodes = array();
$this->nodeHashCallback = $nodeHashCallback;
}
/**
@@ -120,7 +122,7 @@ class HashRing implements DistributionStrategyInterface, HashGeneratorInterface
return;
}
if (count($this->nodes) === 0) {
if (!$this->nodes) {
throw new EmptyRingException('Cannot initialize empty hashring');
}
@@ -164,7 +166,11 @@ class HashRing implements DistributionStrategyInterface, HashGeneratorInterface
*/
protected function getNodeHash($nodeObject)
{
return (string) $nodeObject;
if ($this->nodeHashCallback === null) {
return (string) $nodeObject;
}
return call_user_func($this->nodeHashCallback, $nodeObject);
}
/**
@@ -24,11 +24,11 @@ class KetamaPureRing extends HashRing
const DEFAULT_REPLICAS = 160;
/**
*
* @param mixed $nodeHashCallback Callback returning the string used to calculate the hash of a node.
*/
public function __construct()
public function __construct($nodeHashCallback = null)
{
parent::__construct($this::DEFAULT_REPLICAS);
parent::__construct($this::DEFAULT_REPLICAS, $nodeHashCallback);
}
/**
@@ -64,7 +64,7 @@ class CRC16HashGenerator implements HashGeneratorInterface
$strlen = strlen($value);
for ($i = 0; $i < $strlen; $i++) {
$crc = (($crc << 8) ^ $CCITT_16[($crc >> 8) ^ ord($value[$i])]) & 65535; // 0xFFFF
$crc = (($crc << 8) ^ $CCITT_16[($crc >> 8) ^ ord($value[$i])]) & 0xFFFF;
}
return $crc;
@@ -294,9 +294,11 @@ class PredisClusterHashStrategy implements CommandHashStrategyInterface
*/
protected function getKeyFromScriptingCommands(CommandInterface $command)
{
$keys = $command instanceof ScriptedCommand
? $command->getKeys()
: array_slice($args = $command->getArguments(), 2, $args[1]);
if ($command instanceof ScriptedCommand) {
$keys = $command->getKeys();
} else {
$keys = array_slice($args = $command->getArguments(), 2, $args[1]);
}
if ($keys && $this->checkSameHashForKeys($keys)) {
return $keys[0];
@@ -341,7 +343,7 @@ class PredisClusterHashStrategy implements CommandHashStrategyInterface
*/
protected function checkSameHashForKeys(Array $keys)
{
if (($count = count($keys)) === 0) {
if (!$count = count($keys)) {
return false;
}
@@ -246,9 +246,11 @@ class RedisClusterHashStrategy implements CommandHashStrategyInterface
*/
protected function getKeyFromScriptingCommands(CommandInterface $command)
{
$keys = $command instanceof ScriptedCommand
? $command->getKeys()
: array_slice($args = $command->getArguments(), 2, $args[1]);
if ($command instanceof ScriptedCommand) {
$keys = $command->getKeys();
} else {
$keys = array_slice($args = $command->getArguments(), 2, $args[1]);
}
if (count($keys) === 1) {
return $keys[0];
+32 -2
View File
@@ -65,9 +65,9 @@ abstract class AbstractCommand implements CommandInterface
*
* @param array $arguments Position of the argument.
*/
public function getArgument($index = 0)
public function getArgument($index)
{
if (isset($this->arguments[$index]) === true) {
if (isset($this->arguments[$index])) {
return $this->arguments[$index];
}
}
@@ -129,4 +129,34 @@ abstract class AbstractCommand implements CommandInterface
$this->getId()
);
}
/**
* Normalizes the arguments array passed to a Redis command.
*
* @param array $arguments Arguments for a command.
* @return array
*/
public static function normalizeArguments(Array $arguments)
{
if (count($arguments) === 1 && is_array($arguments[0])) {
return $arguments[0];
}
return $arguments;
}
/**
* Normalizes the arguments array passed to a variadic Redis command.
*
* @param array $arguments Arguments for a command.
* @return array
*/
public static function normalizeVariadic(Array $arguments)
{
if (count($arguments) === 2 && is_array($arguments[1])) {
return array_merge(array($arguments[0]), $arguments[1]);
}
return $arguments;
}
}
+7
View File
@@ -59,6 +59,13 @@ interface CommandInterface
*/
public function getArguments();
/**
* Gets the argument of the command at the specified index.
*
* @return array
*/
public function getArgument($index);
/**
* Parses a reply buffer and returns a PHP object.
*
+1 -3
View File
@@ -11,8 +11,6 @@
namespace Predis\Command;
use Predis\Helpers;
/**
* @link http://redis.io/commands/hdel
* @author Daniele Alessandri <suppakilla@gmail.com>
@@ -32,6 +30,6 @@ class HashDelete extends PrefixableCommand
*/
protected function filterArguments(Array $arguments)
{
return Helpers::filterVariadicValues($arguments);
return self::normalizeVariadic($arguments);
}
}
+1 -3
View File
@@ -11,8 +11,6 @@
namespace Predis\Command;
use Predis\Helpers;
/**
* @link http://redis.io/commands/hmget
* @author Daniele Alessandri <suppakilla@gmail.com>
@@ -32,6 +30,6 @@ class HashGetMultiple extends PrefixableCommand
*/
protected function filterArguments(Array $arguments)
{
return Helpers::filterVariadicValues($arguments);
return self::normalizeVariadic($arguments);
}
}
+1 -3
View File
@@ -11,8 +11,6 @@
namespace Predis\Command;
use Predis\Helpers;
/**
* @link http://redis.io/commands/del
* @author Daniele Alessandri <suppakilla@gmail.com>
@@ -32,7 +30,7 @@ class KeyDelete extends AbstractCommand implements PrefixableCommandInterface
*/
protected function filterArguments(Array $arguments)
{
return Helpers::filterArrayArguments($arguments);
return self::normalizeArguments($arguments);
}
/**
+1 -3
View File
@@ -11,8 +11,6 @@
namespace Predis\Command;
use Predis\Helpers;
/**
* @link http://redis.io/commands/rpush
* @author Daniele Alessandri <suppakilla@gmail.com>
@@ -32,6 +30,6 @@ class ListPushTail extends PrefixableCommand
*/
protected function filterArguments(Array $arguments)
{
return Helpers::filterVariadicValues($arguments);
return self::normalizeVariadic($arguments);
}
}
@@ -61,4 +61,12 @@ class KeyPrefixProcessor implements CommandProcessorInterface
$command->prefixKeys($this->prefix);
}
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return $this->getPrefix();
}
}
+1 -3
View File
@@ -11,8 +11,6 @@
namespace Predis\Command;
use Predis\Helpers;
/**
* @link http://redis.io/commands/subscribe
* @author Daniele Alessandri <suppakilla@gmail.com>
@@ -32,7 +30,7 @@ class PubSubSubscribe extends AbstractCommand implements PrefixableCommandInterf
*/
protected function filterArguments(Array $arguments)
{
return Helpers::filterArrayArguments($arguments);
return self::normalizeArguments($arguments);
}
/**
@@ -11,8 +11,6 @@
namespace Predis\Command;
use Predis\Helpers;
/**
* @link http://redis.io/commands/psubscribe
* @author Daniele Alessandri <suppakilla@gmail.com>
+1 -3
View File
@@ -11,8 +11,6 @@
namespace Predis\Command;
use Predis\Helpers;
/**
* @link http://redis.io/commands/unsubscribe
* @author Daniele Alessandri <suppakilla@gmail.com>
@@ -32,7 +30,7 @@ class PubSubUnsubscribe extends AbstractCommand implements PrefixableCommandInte
*/
protected function filterArguments(Array $arguments)
{
return Helpers::filterArrayArguments($arguments);
return self::normalizeArguments($arguments);
}
/**
+6 -12
View File
@@ -30,19 +30,15 @@ abstract class ScriptedCommand extends ServerEvalSHA
/**
* Specifies the number of arguments that should be considered as keys.
*
* The default behaviour for the base class is to return FALSE to indicate that
* The default behaviour for the base class is to return 0 to indicate that
* all the elements of the arguments array should be considered as keys, but
* subclasses can enforce a static number of keys.
*
* @todo How about returning 1 by default to make scripted commands act like
* variadic ones where the first argument is the key (KEYS[1]) and the
* rest are values (ARGV)?
*
* @return int|Boolean
* @return int
*/
protected function getKeysCount()
{
return false;
return 0;
}
/**
@@ -60,13 +56,11 @@ abstract class ScriptedCommand extends ServerEvalSHA
*/
protected function filterArguments(Array $arguments)
{
if (false !== $numkeys = $this->getKeysCount()) {
$numkeys = $numkeys >= 0 ? $numkeys : count($arguments) + $numkeys;
} else {
$numkeys = count($arguments);
if (($numkeys = $this->getKeysCount()) && $numkeys < 0) {
$numkeys = count($arguments) + $numkeys;
}
return array_merge(array(sha1($this->getScript()), $numkeys), $arguments);
return array_merge(array(sha1($this->getScript()), (int) $numkeys), $arguments);
}
/**
+2 -1
View File
@@ -35,8 +35,9 @@ class ServerClient extends AbstractCommand
switch (strtoupper($args[0])) {
case 'LIST':
return $this->parseClientList($data);
case 'KILL':
case 'GETNAME':
case 'SETNAME':
default:
return $data;
}
-2
View File
@@ -11,8 +11,6 @@
namespace Predis\Command;
use Predis\Helpers;
/**
* @link http://redis.io/commands/object
* @author Daniele Alessandri <suppakilla@gmail.com>
+1 -3
View File
@@ -11,8 +11,6 @@
namespace Predis\Command;
use Predis\Helpers;
/**
* @link http://redis.io/commands/sadd
* @author Daniele Alessandri <suppakilla@gmail.com>
@@ -32,6 +30,6 @@ class SetAdd extends PrefixableCommand
*/
protected function filterArguments(Array $arguments)
{
return Helpers::filterVariadicValues($arguments);
return self::normalizeVariadic($arguments);
}
}
+1 -3
View File
@@ -11,8 +11,6 @@
namespace Predis\Command;
use Predis\Helpers;
/**
* @link http://redis.io/commands/sinter
* @author Daniele Alessandri <suppakilla@gmail.com>
@@ -32,7 +30,7 @@ class SetIntersection extends AbstractCommand implements PrefixableCommandInterf
*/
protected function filterArguments(Array $arguments)
{
return Helpers::filterArrayArguments($arguments);
return self::normalizeArguments($arguments);
}
/**
+1 -3
View File
@@ -11,8 +11,6 @@
namespace Predis\Command;
use Predis\Helpers;
/**
* @link http://redis.io/commands/srem
* @author Daniele Alessandri <suppakilla@gmail.com>
@@ -32,6 +30,6 @@ class SetRemove extends PrefixableCommand
*/
protected function filterArguments(Array $arguments)
{
return Helpers::filterVariadicValues($arguments);
return self::normalizeVariadic($arguments);
}
}
+1 -3
View File
@@ -11,8 +11,6 @@
namespace Predis\Command;
use Predis\Helpers;
/**
* @link http://redis.io/commands/mget
* @author Daniele Alessandri <suppakilla@gmail.com>
@@ -32,7 +30,7 @@ class StringGetMultiple extends AbstractCommand implements PrefixableCommandInte
*/
protected function filterArguments(Array $arguments)
{
return Helpers::filterArrayArguments($arguments);
return self::normalizeArguments($arguments);
}
/**
-2
View File
@@ -11,8 +11,6 @@
namespace Predis\Command;
use Predis\Helpers;
/**
* @link http://redis.io/commands/zadd
* @author Daniele Alessandri <suppakilla@gmail.com>
+1 -3
View File
@@ -11,8 +11,6 @@
namespace Predis\Command;
use Predis\Helpers;
/**
* @link http://redis.io/commands/zrem
* @author Daniele Alessandri <suppakilla@gmail.com>
@@ -32,6 +30,6 @@ class ZSetRemove extends PrefixableCommand
*/
protected function filterArguments(Array $arguments)
{
return Helpers::filterVariadicValues($arguments);
return self::normalizeVariadic($arguments);
}
}
+19
View File
@@ -54,4 +54,23 @@ abstract class CommunicationException extends PredisException
{
return true;
}
/**
* Offers a generic and reusable method to handle exceptions generated by
* a connection object.
*
* @param CommunicationException $exception Exception.
*/
public static function handle(CommunicationException $exception)
{
if ($exception->shouldResetConnection()) {
$connection = $exception->getConnection();
if ($connection->isConnected()) {
$connection->disconnect();
}
}
throw $exception;
}
}
+3 -3
View File
@@ -12,7 +12,7 @@
namespace Predis\Connection;
use Predis\ClientException;
use Predis\Helpers;
use Predis\CommunicationException;
use Predis\NotSupportedException;
use Predis\Command\CommandInterface;
use Predis\Protocol\ProtocolException;
@@ -136,7 +136,7 @@ abstract class AbstractConnection implements SingleConnectionInterface
*/
protected function onConnectionError($message, $code = null)
{
Helpers::onCommunicationException(new ConnectionException($this, $message, $code));
CommunicationException::handle(new ConnectionException($this, $message, $code));
}
/**
@@ -146,7 +146,7 @@ abstract class AbstractConnection implements SingleConnectionInterface
*/
protected function onProtocolError($message)
{
Helpers::onCommunicationException(new ProtocolException($this, $message));
CommunicationException::handle(new ProtocolException($this, $message));
}
/**
@@ -31,12 +31,8 @@ class ComposableStreamConnection extends StreamConnection implements ComposableC
*/
public function __construct(ConnectionParametersInterface $parameters, ProtocolInterface $protocol = null)
{
$protocol = $protocol ?: new TextProtocol();
$protocol->setOption('iterable_multibulk', $parameters->iterable_multibulk);
$this->mbiterable = null;
$this->protocol = $protocol;
$this->parameters = $this->checkParameters($parameters);
$this->protocol = $protocol ?: new TextProtocol();
}
/**
@@ -134,6 +130,6 @@ class ComposableStreamConnection extends StreamConnection implements ComposableC
*/
public function __sleep()
{
return array_merge(parent::__sleep(), array('protocol'));
return array_diff(array_merge(parent::__sleep(), array('protocol')), array('mbiterable'));
}
}
@@ -28,7 +28,6 @@ class ConnectionParameters implements ConnectionParametersInterface
'host' => '127.0.0.1',
'port' => 6379,
'timeout' => 5.0,
'iterable_multibulk' => false,
);
/**
@@ -37,7 +36,7 @@ class ConnectionParameters implements ConnectionParametersInterface
public function __construct($parameters = array())
{
if (!is_array($parameters)) {
$parameters = $this->parseURI($parameters);
$parameters = self::parseURI($parameters);
}
$this->parameters = $this->filter($parameters) + $this->getDefaults();
@@ -109,14 +108,14 @@ class ConnectionParameters implements ConnectionParametersInterface
* @param string $uri Connection string.
* @return array
*/
private function parseURI($uri)
public static function parseURI($uri)
{
if (stripos($uri, 'unix') === 0) {
// Hack to support URIs for UNIX sockets with minimal effort.
$uri = str_ireplace('unix:///', 'unix://localhost/', $uri);
}
if (($parsed = @parse_url($uri)) === false || !isset($parsed['host'])) {
if (!($parsed = @parse_url($uri)) || !isset($parsed['host'])) {
throw new ClientException("Invalid URI: $uri");
}
@@ -140,7 +139,7 @@ class ConnectionParameters implements ConnectionParametersInterface
*/
private function filter(Array $parameters)
{
if (count($parameters) > 0) {
if ($parameters) {
$casters = array_intersect_key($this->getValueCasters(), $parameters);
foreach ($casters as $parameter => $caster) {
@@ -41,7 +41,7 @@ use Predis\Command\CommandInterface;
* - timeout: timeout to perform the connection.
* - read_write_timeout: timeout of read / write operations.
*
* @link http://github.com/seppo0010/phpiredis
* @link http://github.com/nrk/phpiredis
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class PhpiredisConnection extends AbstractConnection
@@ -90,10 +90,10 @@ class PhpiredisConnection extends AbstractConnection
*/
protected function checkParameters(ConnectionParametersInterface $parameters)
{
if ($parameters->iterable_multibulk === true) {
if (isset($parameters->iterable_multibulk)) {
$this->onInvalidOption('iterable_multibulk', $parameters);
}
if ($parameters->persistent === true) {
if (isset($parameters->persistent)) {
$this->onInvalidOption('persistent', $parameters);
}
@@ -296,7 +296,7 @@ class PhpiredisConnection extends AbstractConnection
$this->connectWithTimeout($this->parameters);
if (count($this->initCmds) > 0) {
if ($this->initCmds) {
$this->sendInitializationCommands();
}
}
@@ -328,7 +328,7 @@ class PhpiredisConnection extends AbstractConnection
/**
* {@inheritdoc}
*/
private function write($buffer)
protected function write($buffer)
{
$socket = $this->getResource();
@@ -0,0 +1,197 @@
<?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\Connection;
use Predis\NotSupportedException;
use Predis\ResponseError;
use Predis\ResponseQueued;
use Predis\Command\CommandInterface;
/**
* This class provides the implementation of a Predis connection that uses PHP's
* streams for network communication and wraps the phpiredis C extension (PHP
* bindings for hiredis) to parse and serialize the Redis protocol. Everything
* is highly experimental (even the very same phpiredis since it is quite new),
* so use it at your own risk.
*
* This class is mainly intended to provide an optional low-overhead alternative
* for processing replies from Redis compared to the standard pure-PHP classes.
* Differences in speed when dealing with short inline replies are practically
* nonexistent, the actual speed boost is for long multibulk replies when this
* protocol processor can parse and return replies very fast.
*
* For instructions on how to build and install the phpiredis extension, please
* consult the repository of the project.
*
* The connection parameters supported by this class are:
*
* - scheme: it can be either 'tcp' or 'unix'.
* - host: hostname or IP address of the server.
* - port: TCP port of the server.
* - timeout: timeout to perform the connection.
* - read_write_timeout: timeout of read / write operations.
* - async_connect: performs the connection asynchronously.
* - tcp_nodelay: enables or disables Nagle's algorithm for coalescing.
* - persistent: the connection is left intact after a GC collection.
*
* @link https://github.com/nrk/phpiredis
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class PhpiredisStreamConnection extends StreamConnection
{
private $reader;
/**
* {@inheritdoc}
*/
public function __construct(ConnectionParametersInterface $parameters)
{
$this->checkExtensions();
$this->initializeReader();
parent::__construct($parameters);
}
/**
* {@inheritdoc}
*/
public function __destruct()
{
phpiredis_reader_destroy($this->reader);
parent::__destruct();
}
/**
* Checks if the phpiredis extension is loaded in PHP.
*/
protected function checkExtensions()
{
if (!function_exists('phpiredis_reader_create')) {
throw new NotSupportedException(
'The phpiredis extension must be loaded in order to be able to use this connection class'
);
}
}
/**
* {@inheritdoc}
*/
protected function checkParameters(ConnectionParametersInterface $parameters)
{
if (isset($parameters->iterable_multibulk)) {
$this->onInvalidOption('iterable_multibulk', $parameters);
}
return parent::checkParameters($parameters);
}
/**
* Initializes the protocol reader resource.
*/
protected function initializeReader()
{
$reader = phpiredis_reader_create();
phpiredis_reader_set_status_handler($reader, $this->getStatusHandler());
phpiredis_reader_set_error_handler($reader, $this->getErrorHandler());
$this->reader = $reader;
}
/**
* Gets the handler used by the protocol reader to handle status replies.
*
* @return \Closure
*/
protected function getStatusHandler()
{
return function ($payload) {
switch ($payload) {
case 'OK':
return true;
case 'QUEUED':
return new ResponseQueued();
default:
return $payload;
}
};
}
/**
* Gets the handler used by the protocol reader to handle Redis errors.
*
* @param Boolean $throw_errors Specify if Redis errors throw exceptions.
* @return \Closure
*/
protected function getErrorHandler()
{
return function ($errorMessage) {
return new ResponseError($errorMessage);
};
}
/**
* {@inheritdoc}
*/
public function read()
{
$socket = $this->getResource();
$reader = $this->reader;
while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) {
$buffer = fread($socket, 4096);
if ($buffer === false || $buffer === '') {
$this->onConnectionError('Error while reading bytes from the server');
return;
}
phpiredis_reader_feed($reader, $buffer);
}
if ($state === PHPIREDIS_READER_STATE_COMPLETE) {
return phpiredis_reader_get_reply($reader);
} else {
$this->onProtocolError(phpiredis_reader_get_error($reader));
}
}
/**
* {@inheritdoc}
*/
public function writeCommand(CommandInterface $command)
{
$cmdargs = $command->getArguments();
array_unshift($cmdargs, $command->getId());
$this->writeBytes(phpiredis_format_command($cmdargs));
}
/**
* {@inheritdoc}
*/
public function __sleep()
{
return array_diff(parent::__sleep(), array('mbiterable'));
}
/**
* {@inheritdoc}
*/
public function __wakeup()
{
$this->checkExtensions();
$this->initializeReader();
}
}
+4 -4
View File
@@ -135,7 +135,7 @@ class RedisCluster implements ClusterConnectionInterface, \IteratorAggregate, \C
public function buildSlotsMap()
{
$this->slotsMap = array();
$this->slotsPerNode = (int) (4096 / count($this->pool));
$this->slotsPerNode = (int) (16384 / count($this->pool));
foreach ($this->pool as $connectionID => $connection) {
$parameters = $connection->getParameters();
@@ -177,7 +177,7 @@ class RedisCluster implements ClusterConnectionInterface, \IteratorAggregate, \C
*/
public function setSlots($first, $last, $connection)
{
if ($first < 0 || $first > 4095 || $last < 0 || $last > 4095 || $last < $first) {
if ($first < 0x0000 || $first > 0x3FFF || $last < 0x0000 || $last > 0x3FFF || $last < $first) {
throw new \OutOfBoundsException("Invalid slot values for $connection: [$first-$last]");
}
@@ -195,7 +195,7 @@ class RedisCluster implements ClusterConnectionInterface, \IteratorAggregate, \C
throw new NotSupportedException("Cannot use {$command->getId()} with redis-cluster");
}
$slot = $hash & 4095; // 0x0FFF
$slot = $hash & 0x3FFF;
if (isset($this->slots[$slot])) {
return $this->slots[$slot];
@@ -214,7 +214,7 @@ class RedisCluster implements ClusterConnectionInterface, \IteratorAggregate, \C
*/
public function getConnectionBySlot($slot)
{
if ($slot < 0 || $slot > 4095) {
if ($slot < 0x0000 || $slot > 0x3FFF) {
throw new \OutOfBoundsException("Invalid slot value [$slot]");
}
+12 -6
View File
@@ -27,6 +27,7 @@ use Predis\Iterator\MultiBulkResponseSimple;
* - timeout: timeout to perform the connection.
* - read_write_timeout: timeout of read / write operations.
* - async_connect: performs the connection asynchronously.
* - tcp_nodelay: enables or disables Nagle's algorithm for coalescing.
* - persistent: the connection is left intact after a GC collection.
* - iterable_multibulk: multibulk replies treated as iterable objects.
*
@@ -53,7 +54,7 @@ class StreamConnection extends AbstractConnection
*/
public function __destruct()
{
if (!$this->parameters->persistent) {
if (isset($this->parameters) && !$this->parameters->persistent) {
$this->disconnect();
}
}
@@ -80,10 +81,10 @@ class StreamConnection extends AbstractConnection
$uri = "tcp://{$parameters->host}:{$parameters->port}/";
$flags = STREAM_CLIENT_CONNECT;
if (isset($parameters->async_connect) && $parameters->async_connect === true) {
if (isset($parameters->async_connect) && $parameters->async_connect) {
$flags |= STREAM_CLIENT_ASYNC_CONNECT;
}
if (isset($parameters->persistent) && $parameters->persistent === true) {
if (isset($parameters->persistent) && $parameters->persistent) {
$flags |= STREAM_CLIENT_PERSISTENT;
}
@@ -101,6 +102,11 @@ class StreamConnection extends AbstractConnection
stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds);
}
if (isset($parameters->tcp_nodelay) && version_compare(PHP_VERSION, '5.4.0') >= 0) {
$socket = socket_import_stream($resource);
socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay);
}
return $resource;
}
@@ -115,7 +121,7 @@ class StreamConnection extends AbstractConnection
$uri = "unix://{$parameters->path}";
$flags = STREAM_CLIENT_CONNECT;
if ($parameters->persistent === true) {
if ($parameters->persistent) {
$flags |= STREAM_CLIENT_PERSISTENT;
}
@@ -135,7 +141,7 @@ class StreamConnection extends AbstractConnection
{
parent::connect();
if (count($this->initCmds) > 0){
if ($this->initCmds) {
$this->sendInitializationCommands();
}
}
@@ -244,7 +250,7 @@ class StreamConnection extends AbstractConnection
if ($count === -1) {
return null;
}
if ($this->mbiterable === true) {
if ($this->mbiterable) {
return new MultiBulkResponseSimple($this, $count);
}
+4
View File
@@ -19,6 +19,7 @@ use Predis\Connection\ConnectionInterface;
* Defines a few helper methods.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
* @deprecated Deprecated since v0.8.3.
*/
class Helpers
{
@@ -26,6 +27,7 @@ class Helpers
* Offers a generic and reusable method to handle exceptions generated by
* a connection object.
*
* @deprecated Deprecated since v0.8.3 - moved in Predis\CommunicationException::handle()
* @param CommunicationException $exception Exception.
*/
public static function onCommunicationException(CommunicationException $exception)
@@ -44,6 +46,7 @@ class Helpers
/**
* Normalizes the arguments array passed to a Redis command.
*
* @deprecated Deprecated since v0.8.3 - moved in Predis\Command\AbstractCommand::normalizeArguments()
* @param array $arguments Arguments for a command.
* @return array
*/
@@ -59,6 +62,7 @@ class Helpers
/**
* Normalizes the arguments array passed to a variadic Redis command.
*
* @deprecated Deprecated since v0.8.3 - moved in Predis\Command\AbstractCommand::normalizeVariadic()
* @param array $arguments Arguments for a command.
* @return array
*/
+1 -1
View File
@@ -43,7 +43,7 @@ class ClientCluster extends AbstractOption
public function filter(ClientOptionsInterface $options, $value)
{
if (is_callable($value)) {
return $this->checkInstance(call_user_func($value, $options));
return $this->checkInstance(call_user_func($value, $options, $this));
}
$initializer = $this->getInitializer($options, $value);
+14 -2
View File
@@ -40,8 +40,20 @@ class ClientConnectionFactory extends AbstractOption
return $factory;
}
if (is_string($value) && class_exists($value)) {
if (!($factory = new $value()) && !$factory instanceof ConnectionFactoryInterface) {
if (is_callable($value)) {
$factory = call_user_func($value, $options, $this);
if (!$factory instanceof ConnectionFactoryInterface) {
throw new \InvalidArgumentException('Instance of Predis\Connection\ConnectionFactoryInterface expected');
}
return $factory;
}
if (@class_exists($value)) {
$factory = new $value();
if (!$factory instanceof ConnectionFactoryInterface) {
throw new \InvalidArgumentException("Class $value must be an instance of Predis\Connection\ConnectionFactoryInterface");
}
+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);
}
}
}
+1 -1
View File
@@ -35,7 +35,7 @@ class ClientProfile extends AbstractOption
}
if (is_callable($value)) {
$value = call_user_func($value, $options);
$value = call_user_func($value, $options, $this);
}
if (!$value instanceof ServerProfileInterface) {
+1 -1
View File
@@ -42,7 +42,7 @@ class ClientReplication extends AbstractOption
public function filter(ClientOptionsInterface $options, $value)
{
if (is_callable($value)) {
$connection = call_user_func($value, $options);
$connection = call_user_func($value, $options, $this);
if (!$connection instanceof ReplicationConnectionInterface) {
throw new \InvalidArgumentException('Instance of Predis\Connection\ReplicationConnectionInterface expected');
-1
View File
@@ -16,7 +16,6 @@ use Predis\BasicClientInterface;
use Predis\ClientException;
use Predis\ClientInterface;
use Predis\ExecutableContextInterface;
use Predis\Helpers;
use Predis\Command\CommandInterface;
/**
+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;
+1 -1
View File
@@ -130,7 +130,7 @@ abstract class ServerProfile implements ServerProfileInterface, CommandProcessin
public function supportsCommands(Array $commands)
{
foreach ($commands as $command) {
if ($this->supportsCommand($command) === false) {
if (!$this->supportsCommand($command)) {
return false;
}
}
@@ -11,7 +11,7 @@
namespace Predis\Protocol\Text;
use Predis\Helpers;
use Predis\CommunicationException;
use Predis\Connection\ComposableConnectionInterface;
use Predis\Protocol\ProtocolException;
use Predis\Protocol\ResponseHandlerInterface;
@@ -37,7 +37,7 @@ class ResponseBulkHandler implements ResponseHandlerInterface
$length = (int) $lengthString;
if ("$length" !== $lengthString) {
Helpers::onCommunicationException(new ProtocolException(
CommunicationException::handle(new ProtocolException(
$connection, "Cannot parse '$lengthString' as bulk length"
));
}
@@ -11,7 +11,7 @@
namespace Predis\Protocol\Text;
use Predis\Helpers;
use Predis\CommunicationException;
use Predis\Connection\ComposableConnectionInterface;
use Predis\Protocol\ProtocolException;
use Predis\Protocol\ResponseHandlerInterface;
@@ -39,7 +39,7 @@ class ResponseIntegerHandler implements ResponseHandlerInterface
}
if ($number !== 'nil') {
Helpers::onCommunicationException(new ProtocolException(
CommunicationException::handle(new ProtocolException(
$connection, "Cannot parse '$number' as numeric response"
));
}
@@ -11,7 +11,7 @@
namespace Predis\Protocol\Text;
use Predis\Helpers;
use Predis\CommunicationException;
use Predis\Connection\ComposableConnectionInterface;
use Predis\Protocol\ProtocolException;
use Predis\Protocol\ResponseHandlerInterface;
@@ -37,7 +37,7 @@ class ResponseMultiBulkHandler implements ResponseHandlerInterface
$length = (int) $lengthString;
if ("$length" !== $lengthString) {
Helpers::onCommunicationException(new ProtocolException(
CommunicationException::handle(new ProtocolException(
$connection, "Cannot parse '$lengthString' as multi-bulk length"
));
}
@@ -11,7 +11,7 @@
namespace Predis\Protocol\Text;
use Predis\Helpers;
use Predis\CommunicationException;
use Predis\Connection\ComposableConnectionInterface;
use Predis\Iterator\MultiBulkResponseSimple;
use Predis\Protocol\ProtocolException;
@@ -38,7 +38,7 @@ class ResponseMultiBulkStreamHandler implements ResponseHandlerInterface
$length = (int) $lengthString;
if ("$length" != $lengthString) {
Helpers::onCommunicationException(new ProtocolException(
CommunicationException::handle(new ProtocolException(
$connection, "Cannot parse '$lengthString' as multi-bulk length"
));
}
+3 -3
View File
@@ -11,7 +11,7 @@
namespace Predis\Protocol\Text;
use Predis\Helpers;
use Predis\CommunicationException;
use Predis\ResponseError;
use Predis\ResponseQueued;
use Predis\ServerException;
@@ -98,7 +98,7 @@ class TextProtocol implements ProtocolInterface
if ($count === -1) {
return null;
}
if ($this->mbiterable == true) {
if ($this->mbiterable) {
return new MultiBulkResponseSimple($connection, $count);
}
@@ -117,7 +117,7 @@ class TextProtocol implements ProtocolInterface
return new ResponseError($payload);
default:
Helpers::onCommunicationException(new ProtocolException(
CommunicationException::handle(new ProtocolException(
$connection, "Unknown prefix: '$prefix'"
));
}
@@ -11,7 +11,7 @@
namespace Predis\Protocol\Text;
use Predis\Helpers;
use Predis\CommunicationException;
use Predis\Connection\ComposableConnectionInterface;
use Predis\Protocol\ProtocolException;
use Predis\Protocol\ResponseHandlerInterface;
@@ -108,6 +108,6 @@ class TextResponseReader implements ResponseReaderInterface
*/
private function protocolError(ComposableConnectionInterface $connection, $message)
{
Helpers::onCommunicationException(new ProtocolException($connection, $message));
CommunicationException::handle(new ProtocolException($connection, $message));
}
}
@@ -13,7 +13,6 @@ namespace Predis\PubSub;
use Predis\ClientException;
use Predis\ClientInterface;
use Predis\Helpers;
use Predis\NotSupportedException;
/**
+2 -2
View File
@@ -13,7 +13,7 @@ namespace Predis\PubSub;
use Predis\ClientException;
use Predis\ClientInterface;
use Predis\Helpers;
use Predis\Command\AbstractCommand as Command;
use Predis\NotSupportedException;
use Predis\Connection\AggregatedConnectionInterface;
@@ -77,7 +77,7 @@ class PubSubContext extends AbstractPubSubContext
*/
protected function writeCommand($method, $arguments)
{
$arguments = Helpers::filterArrayArguments($arguments);
$arguments = Command::normalizeArguments($arguments);
$command = $this->client->createCommand($method, $arguments);
$this->client->getConnection()->writeCommand($command);
}
+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
@@ -442,7 +442,7 @@ class MultiExecContext implements BasicClientInterface, ExecutableContextInterfa
// Since a MULTI/EXEC block cannot be initialized when using aggregated
// connections, we can safely assume that Predis\Client::getConnection()
// will always return an instance of Predis\Connection\SingleConnectionInterface.
Helpers::onCommunicationException(new ProtocolException(
CommunicationException::handle(new ProtocolException(
$this->client->getConnection(), $message
));
}
+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.0"
version = "0.8.3"
stability = "stable"
channel = "pear.nrk.io"
+1 -1
View File
@@ -31,7 +31,7 @@
<php>
<!-- Redis -->
<const name="REDIS_SERVER_VERSION" value="2.4" />
<const name="REDIS_SERVER_VERSION" value="2.6" />
<const name="REDIS_SERVER_HOST" value="127.0.0.1" />
<const name="REDIS_SERVER_PORT" value="6379" />
<const name="REDIS_SERVER_DBNUM" value="15" />
+26
View File
@@ -159,6 +159,32 @@ abstract class CommandTestCase extends StandardTestCase
$this->assertEquals($this->getExpectedId(), $command->getId());
}
/**
* @param string $expectedVersion
* @param string $message Optional message.
* @throws \RuntimeException when unable to retrieve server info or redis version
* @throws \PHPUnit_Framework_SkippedTestError when expected redis version is not met
*/
protected function markTestSkippedOnRedisVersionBelow($expectedVersion, $message = '')
{
$client = $this->getClient();
$info = array_change_key_case($client->info());
if (isset($info['server']['redis_version'])) {
// Redis >= 2.6
$version = $info['server']['redis_version'];
} else if (isset($info['redis_version'])) {
// Redis < 2.6
$version = $info['redis_version'];
} else {
throw new \RuntimeException('Unable to retrieve server info');
}
if (version_compare($version, $expectedVersion) <= -1) {
$this->markTestSkipped($message ?: "Test requires Redis $expectedVersion, current is $version.");
}
}
/**
* @group disconnected
*/
+1 -1
View File
@@ -262,7 +262,7 @@ abstract class ConnectionTestCase extends StandardTestCase
$connection->writeCommand($profile->createCommand('rpush', array('foo', 'baz')));
$this->assertInstanceOf('Predis\ResponseError', $error = $connection->read());
$this->assertSame('ERR Operation against a key holding the wrong kind of value', $error->getMessage());
$this->assertRegExp('/[ERR|WRONGTYPE] Operation against a key holding the wrong kind of value/', $error->getMessage());
}
/**
+56 -4
View File
@@ -197,6 +197,42 @@ class ClientTest extends StandardTestCase
$this->assertSame($replication, $client->getConnection());
}
/**
* @group disconnected
*/
public function testConstructorWithCallableArgument()
{
$connection = $this->getMock('Predis\Connection\ConnectionInterface');
$callable = $this->getMock('stdClass', array('__invoke'));
$callable->expects($this->once())
->method('__invoke')
->with($this->isInstanceOf('Predis\Option\ClientOptions'))
->will($this->returnValue($connection));
$client = new Client($callable);
$this->assertSame($connection, $client->getConnection());
}
/**
* @group disconnected
* @expectedException InvalidArgumentException
* @expectedExceptionMessage Callable parameters must return instances of Predis\Connection\ConnectionInterface
*/
public function testConstructorWithCallableArgumentButInvalidReturnType()
{
$wrongType = $this->getMock('stdClass');
$callable = $this->getMock('stdClass', array('__invoke'));
$callable->expects($this->once())
->method('__invoke')
->with($this->isInstanceOf('Predis\Option\ClientOptions'))
->will($this->returnValue($wrongType));
$client = new Client($callable);
}
/**
* @group disconnected
*/
@@ -313,7 +349,7 @@ class ClientTest extends StandardTestCase
/**
* @group disconnected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testExecuteCommandThrowsExceptionOnRedisError()
{
@@ -376,7 +412,7 @@ class ClientTest extends StandardTestCase
/**
* @group disconnected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testCallingRedisCommandThrowsExceptionOnServerError()
{
@@ -462,10 +498,22 @@ class ClientTest extends StandardTestCase
$clientNode02 = $client->getClientFor('node02');
$this->assertInstanceOf('Predis\Client', $clientNode02);
$this->assertSame($node02, $clientNode02->getConnection());
$this->assertSame($client->getOptions(), $clientNode02->getOptions());
}
/**
* @group disconnected
*/
public function testGetClientForReturnsInstanceOfSubclass()
{
$nodes = array('tcp://host1?alias=node01', 'tcp://host2?alias=node02');
$client = $this->getMock('Predis\Client', array('dummy'), array($nodes), 'SubclassedClient');
$this->assertInstanceOf('SubclassedClient', $client->getClientFor('node02'));
}
/**
* @group disconnected
*/
@@ -656,10 +704,14 @@ class ClientTest extends StandardTestCase
*/
public function testClientResendScriptedCommandUsingEvalOnNoScriptErrors()
{
$command = $this->getMockForAbstractClass('Predis\Command\ScriptedCommand');
$command = $this->getMockForAbstractClass('Predis\Command\ScriptedCommand', array(), '', true, true, true, array('parseResponse'));
$command->expects($this->once())
->method('getScript')
->will($this->returnValue('return redis.call(\'exists\', KEYS[1])'));
$command->expects($this->once())
->method('parseResponse')
->with('OK')
->will($this->returnValue(true));
$connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
$connection->expects($this->at(0))
@@ -669,7 +721,7 @@ class ClientTest extends StandardTestCase
$connection->expects($this->at(1))
->method('executeCommand')
->with($this->isInstanceOf('Predis\Command\ServerEval'))
->will($this->returnValue(true));
->will($this->returnValue('OK'));
$client = new Client($connection);
@@ -129,4 +129,25 @@ class HashRingTest extends DistributionStrategyTestCase
$this->assertSame($expected2, $actual2);
$this->assertSame($expected3, $actual3);
}
/**
* @todo This tests should be moved in Predis\Cluster\Distribution\DistributionStrategyTestCase
* @group disconnected
*/
public function testCallbackToGetNodeHash()
{
$node = '127.0.0.1:7000';
$replicas = HashRing::DEFAULT_REPLICAS;
$callable = $this->getMock('stdClass', array('__invoke'));
$callable->expects($this->once())
->method('__invoke')
->with($node)
->will($this->returnValue($node));
$ring = new HashRing($replicas, $callable);
$ring->add($node);
$this->getNodes($ring);
}
}
@@ -130,4 +130,24 @@ class KetamaPureRingTest extends DistributionStrategyTestCase
$this->assertSame($expected2, $actual2);
$this->assertSame($expected3, $actual3);
}
/**
* @todo This tests should be moved in Predis\Cluster\Distribution\DistributionStrategyTestCase
* @group disconnected
*/
public function testCallbackToGetNodeHash()
{
$node = '127.0.0.1:7000';
$callable = $this->getMock('stdClass', array('__invoke'));
$callable->expects($this->once())
->method('__invoke')
->with($node)
->will($this->returnValue($node));
$ring = new KetamaPureRing($callable);
$ring->add($node);
$this->getNodes($ring);
}
}
@@ -26,7 +26,9 @@ class PredisClusterHashStrategyTest extends StandardTestCase
*/
public function testSupportsKeyTags()
{
$expected = -1938594527;
// NOTE: 32 and 64 bits PHP runtimes can produce different hash values.
$expected = PHP_INT_SIZE == 4 ? -1938594527 : 2356372769;
$strategy = $this->getHashStrategy();
$this->assertSame($expected, $strategy->getKeyHash('{foo}'));
+31
View File
@@ -140,4 +140,35 @@ class CommandTest extends StandardTestCase
$this->assertEquals($expected, (string) $command);
}
/**
* @group disconnected
*/
public function testNormalizeArguments()
{
$arguments = array('arg1', 'arg2', 'arg3', 'arg4');
$this->assertSame($arguments, AbstractCommand::normalizeArguments($arguments));
$this->assertSame($arguments, AbstractCommand::normalizeArguments(array($arguments)));
$arguments = array(array(), array());
$this->assertSame($arguments, AbstractCommand::normalizeArguments($arguments));
$arguments = array(new \stdClass());
$this->assertSame($arguments, AbstractCommand::normalizeArguments($arguments));
}
/**
* @group disconnected
*/
public function testNormalizeVariadic()
{
$arguments = array('key', 'value1', 'value2', 'value3');
$this->assertSame($arguments, AbstractCommand::normalizeVariadic($arguments));
$this->assertSame($arguments, AbstractCommand::normalizeVariadic(array('key', array('value1', 'value2', 'value3'))));
$arguments = array(new \stdClass());
$this->assertSame($arguments, AbstractCommand::normalizeVariadic($arguments));
}
}
+1 -1
View File
@@ -102,7 +102,7 @@ class HashDeleteTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -91,7 +91,7 @@ class HashExistsTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -92,7 +92,7 @@ class HashGetAllTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -108,7 +108,7 @@ class HashGetMultipleTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -88,7 +88,7 @@ class HashGetTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
@@ -113,7 +113,7 @@ class HashIncrementByFloatTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -113,7 +113,7 @@ class HashIncrementByTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -92,7 +92,7 @@ class HashKeysTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -87,7 +87,7 @@ class HashLengthTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -114,7 +114,7 @@ class HashSetMultipleTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -91,7 +91,7 @@ class HashSetPreserveTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -90,7 +90,7 @@ class HashSetTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -92,7 +92,7 @@ class HashValuesTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
@@ -86,16 +86,6 @@ class KeyPreciseTimeToLiveTest extends CommandTestCase
$this->assertLessThanOrEqual(10000, $redis->pttl('foo'));
}
/**
* @group connected
*/
public function testReturnsLessThanZeroOnNonExistingKeys()
{
$redis = $this->getClient();
$this->assertSame(-1, $redis->pttl('foo'));
}
/**
* @group connected
*/
@@ -106,4 +96,16 @@ class KeyPreciseTimeToLiveTest extends CommandTestCase
$redis->set('foo', 'bar');
$this->assertSame(-1, $redis->pttl('foo'));
}
/**
* @group connected
* @todo PTTL changed in Redis >= 2.8 to return -2 on non existing keys, we
* should handle this case with a better solution than the current one.
*/
public function testReturnsLessThanZeroOnNonExistingKeys()
{
$redis = $this->getClient();
$this->assertLessThanOrEqual(-1, $redis->pttl('foo'));
}
}
+1 -1
View File
@@ -258,7 +258,7 @@ class KeySortTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+12 -10
View File
@@ -86,16 +86,6 @@ class KeyTimeToLiveTest extends CommandTestCase
$this->assertSame(10, $redis->ttl('foo'));
}
/**
* @group connected
*/
public function testReturnsLessThanZeroOnNonExistingKeys()
{
$redis = $this->getClient();
$this->assertSame(-1, $redis->ttl('foo'));
}
/**
* @group connected
*/
@@ -106,4 +96,16 @@ class KeyTimeToLiveTest extends CommandTestCase
$redis->set('foo', 'bar');
$this->assertSame(-1, $redis->ttl('foo'));
}
/**
* @group connected
* @todo TTL changed in Redis >= 2.8 to return -2 on non existing keys, we
* should handle this case with a better solution than the current one.
*/
public function testReturnsLessThanZeroOnNonExistingKeys()
{
$redis = $this->getClient();
$this->assertLessThanOrEqual(-1, $redis->ttl('foo'));
}
}
+1 -1
View File
@@ -103,7 +103,7 @@ class ListIndexTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -111,7 +111,7 @@ class ListInsertTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -98,7 +98,7 @@ class ListLengthTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -98,7 +98,7 @@ class ListPopFirstTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
@@ -117,7 +117,7 @@ class ListPopLastPushHeadTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongTypeOfSourceKey()
{
@@ -130,7 +130,7 @@ class ListPopLastPushHeadTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongTypeOfDestinationKey()
{
+1 -1
View File
@@ -98,7 +98,7 @@ class ListPopLastTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -101,7 +101,7 @@ class ListPushHeadTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -99,7 +99,7 @@ class ListPushHeadXTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -101,7 +101,7 @@ class ListPushTailTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -99,7 +99,7 @@ class ListPushTailXTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -142,7 +142,7 @@ class ListRangeTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -126,7 +126,7 @@ class ListRemoveTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
+1 -1
View File
@@ -100,7 +100,7 @@ class ListSetTest extends CommandTestCase
/**
* @group connected
* @expectedException Predis\ServerException
* @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{

Some files were not shown because too many files have changed in this diff Show More