Compare commits

..

640 Commits

Author SHA1 Message Date
Daniele Alessandri d4be306d0a ... and here comes Predis v1.0.0! 2014-08-01 11:59:50 +02:00
Daniele Alessandri 25d0008b2f Apply minor fix in CHANGELOG. 2014-08-01 11:46:56 +02:00
Daniele Alessandri 30274aa242 Integrate CHANGELOG notes from v0.8.7. 2014-08-01 11:44:26 +02:00
Daniele Alessandri f5ba47ad74 Change response parser for ZSCAN to match changes applied to ZRANGE. 2014-07-30 16:45:00 +02:00
Daniele Alessandri fbb0236840 Apply minor code-styling changes. 2014-07-30 15:30:02 +02:00
Daniele Alessandri ba5e501346 Use arguments as defined by Redis when calling WATCH internally. 2014-07-30 14:18:20 +02:00
Daniele Alessandri ab9cbe712f Specify type hint in interface. 2014-07-30 11:31:31 +02:00
Daniele Alessandri 16f2d8a768 Stick to 2.8 as the default target for the test suite.
Even though tere are no changes between running the test suite against
3.0 and 2.8, we will stick with the current stable Redis version as
the default target and switch to 3.0 as soon as it will be released.

[ci skip]
2014-07-30 11:08:56 +02:00
Daniele Alessandri 7c9e4ab595 Update CHANGELOG.
[ci skip]
2014-07-28 20:50:01 +02:00
Daniele Alessandri b6389e4a03 Switch to "3.0" as the default server profile.
Redis 3.0 does not really change much in terms of commands (most of
the ones implemented in the "unstable" branch have been backported to
the "2.8" branch after all) aside from a few cluster-related ones, so
we can bump the default version without worries.
2014-07-28 20:36:12 +02:00
Daniele Alessandri dedc1ba0a9 Move command "COMMAND" to the server profile for Redis 2.8.
This command has been backported from the "unstable" branch of Redis
to the "2.8" one, and it is available since 2.8.13.
2014-07-28 20:30:09 +02:00
Daniele Alessandri df653a7c00 Minor code styling and phpdoc changes. 2014-07-28 12:04:07 +02:00
Daniele Alessandri 5c02e4e47b Update README.
[ci skip]
2014-07-28 10:40:01 +02:00
Daniele Alessandri c2b24bd873 Merge branch 'v1.0/inspection-fixes' 2014-07-28 00:02:54 +02:00
Daniele Alessandri 45e351be79 [phpdoc] Fix formatting of phpdoc headers. 2014-07-27 23:59:37 +02:00
Daniele Alessandri b5cdab35c7 Remove useless argument from method signatures in WebdisConnection. 2014-07-27 23:06:21 +02:00
Daniele Alessandri dbfc1a74ba More random fixes after inspection. 2014-07-27 23:06:17 +02:00
Daniele Alessandri 4e1186f845 [phpdoc] Fix undefined classes. 2014-07-27 21:57:50 +02:00
Daniele Alessandri aa5c893d5a [phpdoc] Fix undefined namespaces. 2014-07-27 21:57:40 +02:00
Daniele Alessandri fff303a03b Comply with PSR-2 on intentional fall-through in non-empty case. 2014-07-27 21:57:40 +02:00
Daniele Alessandri 78ec22563a The \Iterator interface returns void for next(). 2014-07-27 21:57:40 +02:00
Daniele Alessandri 30254a2594 [phpdoc] Various phpdoc fixes. 2014-07-27 21:57:13 +02:00
Daniele Alessandri f372029cfc Fix code smells. 2014-07-27 20:13:24 +02:00
Daniele Alessandri 87aba18002 Merge branch 'v1.0/phpdocs' 2014-07-27 19:19:44 +02:00
Daniele Alessandri dc33d62b77 Miscellaneous fixes for "use" directives. 2014-07-27 19:19:20 +02:00
Daniele Alessandri b230e243f5 Predis\PubSub\DispatcherLoop should take a PubSub consumer instance. 2014-07-27 16:53:46 +02:00
Daniele Alessandri 44c9232b13 Update README.
[ci skip]
2014-07-27 15:41:06 +02:00
Daniele Alessandri 776f5831a6 Merge branch 'v1.0/custom-parameters' 2014-07-27 14:19:30 +02:00
Daniele Alessandri 31e2516156 Makes it easier to use a custom parameters class.
Simply overriding one method of the standard connection factory class,
used internally by Predis, allows developers to use their own custom
connection parameters classes through the whole library.

For example, in order to support Heroku-style URIs one can do:

  class HerokuParameters extends Predis\Connection\Parameters
  {
    protected function filter(array $parameters)
    {
      if (
        isset($parameters['scheme']) &&
        $parameters['scheme'] === 'redis'
      ) {
        $parameters['scheme'] = 'tcp';
      }

      if (isset($parameters['pass'])) {
        $parameters['password'] = $parameters['pass'];
      }

      unset($parameters['user'], $parameters['pass']);

      return $parameters;
    }
  }

  class ConnectionFactory extends Predis\Connection\Factory
  {
    protected function createParameters($parameters)
    {
      return HerokuParameters::create($parameters);
    }
  }

  $client = new Predis\Client($_ENV['REDISCLOUD_URL'], [
    'connections' => new ConnectionFactory()
  ]);

This idea comes in response to #196, but since we do not want to bake
support for SaaS-specific URIs in Predis this is the best compromise
we can offer leave developers free to implement their own logic for
handling these kind of URIs with the additional benefit of having it
available through the whole library (think of cluster or replication
where you have multiple nodes, thus multiple instances of connection
parameters to create).
2014-07-27 14:13:30 +02:00
Daniele Alessandri b379d4f1b3 Merge pull request #194 from GrahamCampbell/gitstuff
Added a .gitattributes file
2014-07-27 11:46:51 +02:00
Graham Campbell 60a863f5fc Include docs 2014-07-27 10:40:38 +01:00
Daniele Alessandri f7d8088bba Added @method tags to phpdoc header of Predis\ClientContextInterface. 2014-07-27 10:53:40 +02:00
Daniele Alessandri 38710cd69b Added Predis\ClientContextInterface.
This interface replaces Predis\ExecutableContextInterface and it is
used by client-side contexts such as pipelines or transactions.
2014-07-27 10:46:11 +02:00
Graham Campbell 7fa17b2ebc Added a .gitattributes file 2014-07-26 15:30:23 +01:00
Daniele Alessandri dcb57ea08d Fix method visibility in Predis\Pipeline\ConnectionErrorProof. 2014-07-25 16:44:04 +02:00
Daniele Alessandri 872d953b50 Update README.
[ci skip]
2014-07-25 16:40:21 +02:00
Daniele Alessandri f630ff809f Update README.
[ci skip]
2014-07-25 16:30:38 +02:00
Daniele Alessandri 19bad41582 Update README. 2014-07-25 13:21:12 +02:00
Daniele Alessandri be6b7fe5f0 Update README.
[ci skip]
2014-07-25 13:16:09 +02:00
Daniele Alessandri 647c51a5b3 Update README.
[ci skip]
2014-07-25 13:11:33 +02:00
Daniele Alessandri dd1b63ae3b Update README.
[ci skip]
2014-07-25 13:09:22 +02:00
Daniele Alessandri 7146b69707 Update README.
[ci skip]
2014-07-25 13:08:10 +02:00
Daniele Alessandri 0bd7684d14 Add paragraphs in README on how to configure cluster and replication.
[ci skip]
2014-07-25 13:07:10 +02:00
Daniele Alessandri 558e10963c Merge branch 'v1.0/refactor-cluster-internals' 2014-07-25 10:58:03 +02:00
Daniele Alessandri 14a4303a54 Apply easy micro-optimization. 2014-07-23 17:07:03 +02:00
Daniele Alessandri 7d78600506 Add phpdocs for Redis commands in Predis\ClientInterface.
This commit addresses a long-standing feature request posted in #89.
2014-07-23 16:01:11 +02:00
Daniele Alessandri 784c555b67 Minor phpdoc fix. 2014-07-23 15:33:27 +02:00
Daniele Alessandri a67d35b799 Remove useless interface Predis\BasicClientInterface. 2014-07-23 15:30:18 +02:00
Daniele Alessandri 0ee5233f4f Promote __call() method to Predis\ClientInterface. 2014-07-23 15:18:09 +02:00
Daniele Alessandri 9a05d43737 Fix read/write timeouts with PhpiredisStreamConnection.
This is a regression of commit 4db0017, using stream_socket_recvfrom()
bypasses stream wrappers thus read/write timeouts handled by PHP. The
only way to make them work again is to fetch the raw socket from the
stream resource and use socket_import_stream() which is unfortunately
a PHP 5.4+ function.
2014-07-23 12:26:33 +02:00
Daniele Alessandri 6fe56985fc Simplify implementation of Predis\Connection\Parameters.
Casting values supplied by users should be done by the consumer since
we cannot cover any possible use case anyway. It is still possible to
subclass Predis\Connection\Parameters and override the filter() method
to convert certain values if deemed necessary.
2014-07-23 11:57:23 +02:00
Daniele Alessandri 57363f6d11 Update CHANGELOG.
[ci skip]
2014-07-23 11:33:01 +02:00
Daniele Alessandri fdf5894022 Make connection factory mandatory for RedisCluster constructor. 2014-07-23 11:13:09 +02:00
Daniele Alessandri 6147f34478 [tests] Fix wrong test. 2014-07-23 11:06:35 +02:00
Daniele Alessandri 2ff8c37104 Redesign the whole internals for clustering.
This change actually had a positive impact on the design of the whole
internals for clustering which is now cleaner and easier to maintain.
It is still far from perfect, but we also have to keep performances in
consideration so we can say that we ended up with a good compromise.

Previously Predis assigned an hash to each command instance which was
computed from its key, now we changed approach and the library caches
the slot assigned to each command. This works for both our client-side
sharding cluster and the upcoming redis-cluster, but the former is the
one that needed most changes.

The PredisCluster aggregate connection now only takes an instance of
StrategyInterface, which in turn wraps the chosen distributor. After
all, in order to be able to calculate the assigned slot for a command
or key, the cluster strategy must have access to the distributor that
manages the distribution of the whole keyspace. Nothing really changes
in terms of configurability as it is still possible to decide which
distributor to use for client-side sharding, it is simply different:

  $distributor = new Predis\Cluster\Distribution\KetamaRing();
  $strategy = new Predis\Cluster\PredisStrategy($distributor);
  $cluster = new Predis\Connection\Aggregate\PredisCluster($strategy);

As for the RedisCluster aggregate connection, the only change is that
the mathematical operation of calculating the assigned slot from a key
has been completely moved inside the cluster strategy instance.

The strategy for redis-cluster does not use external distributors so
trying to StrategyInterface::getDistributor() will throw an exception.
This may change in future releases, but this is not a priority since
redis-cluster relies on a fixed, well-defined distribution mechanism.
2014-07-22 18:13:12 +02:00
Daniele Alessandri 69bef7fd35 Update example to avoid breaking when run against Redis 3.0.
[ci skip]
2014-07-21 18:20:41 +02:00
Daniele Alessandri 346986099d Rename method "enableAutoSlotsMap()" to "useClusterSlots()". 2014-07-21 14:38:20 +02:00
Daniele Alessandri 9213f02da8 Fix phpdocs. 2014-07-21 14:37:29 +02:00
Daniele Alessandri 4ab2bcea64 Fetch updated slots map from node indicated by -MOVED response.
This optimization makes it possible to fetch the slots map directly
from the server indicated by the -MOVED response eliminating the need
to use a random node in the pool, which in turn could require Predis
to open a new and useless connection.
2014-07-21 14:02:48 +02:00
Daniele Alessandri 217bd69b7f Minor phpdocs fixes.
[ci skip]
2014-07-21 14:02:48 +02:00
Daniele Alessandri bd00b8cfe2 Fetch cluster slots from node upon first -MOVED response.
This is a more sane default as it allows users to indicate only a few
servers of the whole cluster composition, while it previously required
a more complex configuration of the client using client options.

This feature can be disabled using the "enableAutoSlotsMap()" method.
2014-07-21 14:02:43 +02:00
Daniele Alessandri a32eaf627b Remove useless method.
It is better to wrap explicitly a multibulk response iterator into a
multibulk tuple response iterator instead of hard-coding that into a
specialized method.
2014-07-21 11:18:25 +02:00
Daniele Alessandri e89f5cbc39 Add method to get connection factory from redis-cluster connection. 2014-07-21 11:11:52 +02:00
Daniele Alessandri 4188dc51bf Make it possible to specify a custom strategy for redis-cluster. 2014-07-21 10:56:32 +02:00
Daniele Alessandri 7b41029fd7 Light rewording and reorganization of README.
[ci skip]
2014-07-21 10:47:06 +02:00
Daniele Alessandri eb8de52269 New command: COMMAND (Redis 3.0.0).
The command returns the response as is, but it would probably be not a
bad idea to parse it to restructure returned info using named arrays,
making it easier to access single commands and their info.

We will keep it as is for now, and investigate this change later.
2014-07-18 12:56:34 +02:00
Daniele Alessandri 202c558103 Run php-cs against source code. 2014-07-17 15:59:43 +02:00
Daniele Alessandri 89e662dc89 Use same strategy for both client-side sharding and redis-cluster.
This change is possible because, after a few changes in redis-cluster,
our default cluster strategy used for client side sharding and the one
used for redis-cluster turned out to be exactly the same, except for
the hashing function used to calculate distribution.

Actually some checks used to enforce correctness are redundant in the
context of redis-cluster (e.g. the one used to make sure that keys in
requests performing cross-keys operations will hash to the same slot,
which is performed by the server) so we could also add a more dumb and
permissive cluster strategy that relies on checks performed by Redis.

Differently to v0.8, the strategy for client-side sharding now uses
the same rules for extracting hash tags from keys especially when
empty tags are found in the string.
2014-07-17 12:48:46 +02:00
Daniele Alessandri 356f22a062 Implement PING in PUB/SUB loop abstraction for Redis 3.0.0. 2014-07-17 12:16:54 +02:00
Daniele Alessandri 823f615699 Update CHANGELOG.
[ci skip]
2014-07-16 19:11:58 +02:00
Daniele Alessandri 7e58ded8dc Update CHANGELOG.
[ci skip]
2014-07-16 14:10:07 +02:00
Daniele Alessandri 2969982a4f Update README.
[ci skip]
2014-07-16 13:06:25 +02:00
Daniele Alessandri 88da187344 Add dedicated server profile for Redis 3.0. 2014-07-16 12:59:48 +02:00
Daniele Alessandri a33781e58c Merge branch 'v1.0/psr-4'
This merge closes #168.

Conflicts:
	src/Command/KeyKeysV12x.php
	src/Profile/RedisVersion120.php
2014-07-16 12:56:27 +02:00
Daniele Alessandri a074cdb79b Update CHANGELOG.
[ci skip]
2014-07-16 11:42:01 +02:00
Daniele Alessandri 63992a0a6e Remove parsing of allocation stats from responses to INFO.
The allocation stats section was most likely something available in
betas of some older release of Redis, it is not even reported on the
official documentation.
2014-07-16 11:21:12 +02:00
Daniele Alessandri ca4ec79575 Fix bug when checking for support for the UNWATCH command. 2014-07-16 11:17:21 +02:00
Daniele Alessandri 2e0c55ac57 Remove support for Redis 1.2.
Actually we have just removed the server profile so nothing stops you
from reimplementing it, but Redis 2.0 has been released 4 years ago so
we are speaking of ancient releases that should probably not even be
used anymore.
2014-07-16 11:14:36 +02:00
Daniele Alessandri 9edc11dc8e Switch to CLUSTER SLOTS instead of CLUSTER NODES for redis-cluster.
CLUSTER SLOTS returns a structured response easier to handle compared
to the bulk response of CLUSTER NODES which must be parsed making the
whole thing more fragile.

CLUSTER SLOTS has been added in Redis 3.0.0b7.
2014-07-15 18:52:09 +02:00
Daniele Alessandri 50334ed3f4 Update CHANGELOG.
[ci skip]
2014-07-15 12:39:46 +02:00
Daniele Alessandri 1319574b4e Rewrite pear packager script.
We still use Onion's package.ini for the configuration of the package
to keep things simple, but we might switch to a more compact solution
in the future since we do not really need much of its features.
2014-07-14 18:10:07 +02:00
Daniele Alessandri 029822fe22 Move source files from "lib/" to "src/". 2014-07-14 18:04:18 +02:00
Daniele Alessandri d226923484 Migrate from PSR-0 to PSR-4. 2014-07-14 18:02:42 +02:00
Daniele Alessandri 959734cf8c Porperly handle -ERR instead of +QUEUED inside MULTI ... EXEC.
Error responses such as -OOM or -ERR on invalid arguments in commands
are returned immediatly instead of +QUEUED when using a transaction,
which is a condition that had not been tested enough. This condition
led to a bug in which Predis was not invalidating the transaction, so
when trying to create a new transaction Redis returned a "-ERR MULTI
calls can not be nested".

This commit fixes #187.
2014-07-14 16:28:29 +02:00
Daniele Alessandri dd29068845 Merge branch 'github/pr/188' 2014-07-14 11:35:07 +02:00
Daniele Alessandri 36a04e84d7 Update CHANGELOG. 2014-07-14 11:34:50 +02:00
Ian Babrou 5b4a7970bb returning associative array from zrange command family 2014-07-14 13:16:26 +04:00
Ian Babrou 235427950f prevent uncaught broken pipe error 2014-07-07 11:50:37 +02:00
Daniele Alessandri 45da0fca55 Merge branch 'github/pr/182' 2014-07-07 10:52:58 +02:00
Daniele Alessandri 2d53db7b9b Rework PR to use "goto" instead of a "while ... do" loop. 2014-07-07 10:50:17 +02:00
Gwilym Evans f05cb1b46d use a loop instead of recursion in CursorBasedIterator
servers with large sets of keys will cause memory and stack exhaustion if recursion is used
2014-06-26 12:56:00 +10:00
Daniele Alessandri 4db00173f3 Use stream_socket_recvfrom() in PhpiredisStreamConnection.
Similarly to the socket-ext based connection using phpiredis, in our
stream based PhpiredisStreamConnection class we should read data from
the stream using stream_socket_recvfrom() instead of fread() because
the latter could block until a timeout is reached when the read buffer
contains less data then the specified length.

IMPORTANT: stream_socket_recvfrom() bypasses stream wrappers which
means that TLS/SSL, as requested by PR #158, won't ever work with
this connection class as the function returns the original encrypted
bytes.

This commit fixes issue #180.
2014-06-23 14:31:02 +02:00
Daniele Alessandri a2265b12b3 Remove implementation of Predis\Command\RawCommand::__toString().
See #151 and message of commit 5c5dd40, forgot to apply the same
change to the Predis\Command\RawCommand class.
2014-06-12 11:57:54 +02:00
Daniele Alessandri 0cd56406fe Just rename a couple of local variables. 2014-06-10 10:56:33 +02:00
Daniele Alessandri e575c32cb4 Rename RedisCluster::askClusterNodes() to askSlotsMap().
This is more consistent with the actual purpose of this method and
more in-line with a possible future change in the underlying command
used to retrieve the slots map if redis-cluster will implement the
CLUSTER SLOTS command.
2014-06-10 10:36:17 +02:00
Daniele Alessandri 42f237e306 [tests] Use annotations to specify expected Redis server version.
This change targets only method annotations and aims to make the body
of tests more readable using the @requiresRedisVersion annotation.

Tests using this annotation requires to be assigned the "connected"
group of tests because they create a connection to the Redis instance
specified in phpunit.xml to fetch its the version.

This is a quick example of how this annotation can be used:

    /**
     * @group connected
     * @requiresRedisVersion >= 2.8.9
     */
    public function testExecutedOnlyWithMatchingRedisVersion()
    {
    }

Future improvements (currently not needed) include:

  * Same annotation working on a class-level (but still applied only
    to test methods with an explicitly assigned @group connected).

  * Ability to specify a version range.

Meh

Meh
2014-06-09 17:31:18 +02:00
Daniele Alessandri 940326e5bd New command: ZREMRANGEBYLEX (Redis 2.8.9). 2014-06-09 15:35:28 +02:00
Daniele Alessandri ea32f74512 New command: ZRANGEBYLEX (Redis 2.8.9). 2014-06-09 15:26:17 +02:00
Daniele Alessandri 8dbac3dd73 New command: ZLEXCOUNT (Redis 2.8.9). 2014-06-09 15:17:36 +02:00
Daniele Alessandri 5a474c5a32 Rename alias used in Predis\Client for Predis\Transaction\MultiExec. 2014-06-03 18:56:48 +02:00
Daniele Alessandri 0148743a76 Fix parsing of the output of CLUSTER NODES to fetch slots map.
There was an error in presence of slaves in the cluster configuration,
see #165 for reference.
2014-06-03 16:24:27 +02:00
Daniele Alessandri 5c935d97bd Merge branch 'v1.0.0-dev/connection-namespace' 2014-06-03 15:45:57 +02:00
Daniele Alessandri 1fd113c570 Rename ComposableProtocolProcessor to CompositeProtocolProcessor. 2014-06-03 15:45:40 +02:00
Daniele Alessandri abd284c972 Complete reorganization of the Predis\Connection namespace.
* Renamed SingleConnectionInterface to NodeConnectionInterface since
  this name is better and makes even more sense in the context of
  cluster and replication scenarios.

* Moved specialized aggregate connections (the ones implementing both
  predis and redis cluster and master/slave replication) in a newly
  created Predis\Connection\Aggregate sub-namespace.

* Removed the "Connection" part from names of aggregate connection
  interfaces in the Predis\Connection\Aggregate sub-namespace.

* Changed "Composable" to "Composite" in the name of interfaces and
  classes that can use pluggable protocol processors.
2014-06-03 15:19:32 +02:00
Daniele Alessandri ff2e0823ec Merge remote-tracking branch 'github/pr/174' 2014-05-30 12:32:40 +02:00
Graham Campbell a6c6951bf8 Added 1.0 branch alias
Let's allow people to install predis using "1.0.x@dev", "~1.0@dev", or any other valid version constraint rather than typing "dev-master" which is really horrible.
2014-05-30 11:24:39 +01:00
Daniele Alessandri 87417f4d7e Skip integration tests for HyperLogLog commands on Redis < 2.8.9.
Caught this by running the test suite on TravisCI which still runs an
older version of Redis 2.8. We should find a more decent way to handle
differences in profiles when commands get added between patch releases
of Redis, our custom skip method will do for now.
2014-05-30 12:19:05 +02:00
Daniele Alessandri 2aa0b071fc New commands: PFADD, PFCOUNT, PFMERGE (Redis 2.8.9).
Many thanks to @rubensayshi for his initial commits on this in #163,
but I had to start from scratch for the master branch first.
2014-05-30 11:32:39 +02:00
Alexey Kupershtokh f784dfad84 Enable INCRBYFLOAT in cluster mode 2014-05-28 13:00:24 +02:00
Daniele Alessandri 10c2f321ff Merge branch 'pr170-master' 2014-05-28 12:54:16 +02:00
thbourlove 7da3da776f use phpunit 4.x instead of 3.x 2014-05-28 12:54:04 +02:00
thbourlove 90cb71ac4a isntall requirement with a better way 2014-05-28 12:53:58 +02:00
thbourlove 2d4383c891 add phpunit as dev requirement 2014-05-28 12:53:50 +02:00
Daniele Alessandri 8ea76f79c7 Minor rewording in CHANGELOG. 2014-03-25 15:33:27 +01:00
Daniele Alessandri 5c5dd40527 Remove implementation of Predis\Command\Command::__toString().
Issue #151 pointed to a flaw in how command instances were converted
to strings: we were simply truncating their arguments when exceeding
a certain size as this was mostly intended for logging or debugging,
but this approach breaks strings containing multibyte characters so
we decided to drop this feature altogether for the sake of simplicity.

It is still possible to replicate the same (and eventually improved)
behavior externally by fetching ID and arguments of a command out of
a command instance using the public methods made available by the
Predis\Command\CommandInterface.
2014-03-25 15:32:20 +01:00
Daniele Alessandri 4638e05865 Tweak sentence in CHANGELOG.
[ci skip]
2014-03-25 15:05:37 +01:00
Daniele Alessandri 4784291640 Failures for PHP 5.6 on Travis CI are not allowed anymore. 2014-03-25 14:49:49 +01:00
Daniele Alessandri a51ff2d6e5 Port #156 to the current master branch.
This fix the parsing of responses to INFO when the node is configured
to work with redis-sentinel.
2014-03-25 14:49:25 +01:00
Daniele Alessandri 451d28ee66 Let's set this in stone: next major version is going to be v1.0.0.
[ci skip]
2014-02-12 10:27:42 +01:00
Daniele Alessandri e23fd9bea8 Use redis-cluster rules for empty key tags for client-side sharding.
This commit represents a breaking change when the first occurrence of
"{}" is found in a key because it will produce a different hash than
previous versions of Predis, thus leading to a different partitioning.

If you really need to stick with the old behavior, you can subclass
Predis\Cluster\PredisStrategy and override extractKeyTag() using the
old implementation of this method and pass the strategy instance when
initializing the cluster connection via client options.
2014-02-11 19:06:47 +01:00
Daniele Alessandri 8fbe658ca8 Fix implementation for hash tags extraction from keys.
We now fully comply with the specifications defined by Redis.
2014-02-11 19:06:44 +01:00
Daniele Alessandri d89027d2da Add PHP 5.6 to the build matix of Travis-CI.
Builds are allowed to fail for now as PHP 5.6 is still in alpha stage.
2014-02-11 16:35:39 +01:00
Daniele Alessandri 26289f47ec Add support for key hash tags in redis-cluster (Redis 3.0.0b1).
Multi-keys operations are not allowed even when keys generate the same
hash but this will probably be supported in later betas of Redis which
means we will basically end up reusing the whole strategy used for
client-side sharding.

Ported from the v0.8 branch.
2014-02-11 15:44:37 +01:00
Daniele Alessandri ba0dc12d5b Merge pull request #146 from GrahamCampbell/master
Travis Tweaks
2014-02-10 21:00:02 +01:00
Graham Campbell 42a9489f11 Updated travis.yml 2014-02-10 19:49:26 +00:00
Daniele Alessandri 51febb32a9 Update CHANGELOG. 2014-01-16 15:26:30 +01:00
Daniele Alessandri 0a5363dddf Bump year in LICENSE.
[ci skip]
2014-01-10 17:47:32 +01:00
Daniele Alessandri b852eeed9f [bin] Add missing executable in the repository for new script. 2014-01-08 17:37:02 +01:00
Daniele Alessandri 10dcac8f05 [bin] Add helper script to automate PEAR package creation.
Note that this script takes care of customizing `phpunit.xml.dist` so
that tests can be run as soon as the PEAR package has been installed
as requested in #126.
2014-01-08 17:34:52 +01:00
Daniele Alessandri ae61a0fd2b [tests] Use Predis\Autoload from include_path when not in repository.
This is useful only for running the test suite after installing the
PEAR package, see #126 for reference.
2014-01-08 17:34:41 +01:00
Daniele Alessandri dc6c298960 Reword paragraph in README about HHVM compatibility. 2013-12-22 16:19:05 +01:00
Daniele Alessandri e04c7d51b8 Explicitly return null value. 2013-12-22 16:19:05 +01:00
Daniele Alessandri da8a203a75 Remove useless return statement. 2013-12-22 16:19:04 +01:00
Daniele Alessandri bf32f4b87c [tests] Remove unused "use" directives. 2013-12-22 16:19:04 +01:00
Daniele Alessandri 46671b9095 Run php-cs against test suite.
[ci skip]
2013-12-22 16:18:59 +01:00
Daniele Alessandri 9a38d2caec Move @property-read tags for parameters and options to interfaces.
This actually makes sense since whatever the actual implementation is,
classes implementing these interfaces should provide at least those
properties that are actively used through the library.
2013-12-22 15:03:31 +01:00
Daniele Alessandri cfa456fdd1 [tests] Fix a couple of innocuous glitches. 2013-12-22 15:00:51 +01:00
Daniele Alessandri 6a577a0eb5 Apply more phpdoc fixes. 2013-12-22 15:00:51 +01:00
Daniele Alessandri 8357af177e [test] Fix wrong variable. 2013-12-22 15:00:50 +01:00
Daniele Alessandri 7ebb8a8b42 [tests] Fix a couple of glitches in our base PHPUnit classes. 2013-12-22 15:00:50 +01:00
Daniele Alessandri 728d9dceb5 [tests] No need to reassign $exception. 2013-12-22 15:00:41 +01:00
Daniele Alessandri a092f6d4da Fix phpdocs for fluent interfaces.
[ci skip]
2013-12-22 13:52:57 +01:00
Daniele Alessandri 3befbb3ac2 Make Pipeline::executeCommand() return $this for fluent interface. 2013-12-22 13:40:36 +01:00
Daniele Alessandri 8d8a618d0e Add @property-read tags to client options class.
[ci skip]
2013-12-22 13:28:22 +01:00
Daniele Alessandri 5e1ea94f4a Add @property-read tags to parameters class.
[ci skip]
2013-12-22 12:59:40 +01:00
Daniele Alessandri b9b899eaf0 Fix incorrect usage of instance method in static method.
We also added a test to check that the socket-based connection backend
throws an exception when unable to resolve hostnames.
2013-12-22 12:38:54 +01:00
Daniele Alessandri 5065541cc6 Remove erroneous duplication of INFO in Redis profiles.
This oversight didn't end up in a blatant bug only because the correct
handler definition for INFO replaced the old class in the profile.
2013-12-22 12:23:55 +01:00
Daniele Alessandri f5a7d8ece8 Add a note about missing break in switch statement.
[ci skip]
2013-12-22 12:20:59 +01:00
Daniele Alessandri 25cd43033f Fix some errors in phpdocs.
[ci skip]
2013-12-22 12:12:49 +01:00
Daniele Alessandri 90d8c684f0 Use a more dynamic variant for caching common status responses. 2013-12-22 11:01:54 +01:00
Daniele Alessandri 09f9133df7 Fix small oversight.
Bug was not severe since +QUEUED status responses were returned anyway
but they were not cached as expected. We added a test in the suite.

This fixes #142.
2013-12-22 10:53:32 +01:00
Daniele Alessandri bff7906f68 Apply minor CS fixes.
[ci skip]
2013-12-21 18:24:51 +01:00
Daniele Alessandri b73606de68 Replace remaining occurrences of "boolean" with "bool" in phpdocs.
[ci skip]
2013-12-21 12:48:43 +01:00
Daniele Alessandri c127d02e92 Remove leftover "use" directive.
[ci skip]
2013-12-21 12:46:22 +01:00
Daniele Alessandri 6693980b14 Fix README after renaming of examples.
[ci skip]
2013-12-21 12:43:56 +01:00
Daniele Alessandri 7ba465048f Do not throw exception when connection has been already established.
The base abstract connection class now returns a bool to indicate when
the actual connect() operation has been performed on the underlying
resource. This return value is not part of the interface so extending
classes can decide to not return any value.
2013-12-21 12:32:15 +01:00
Daniele Alessandri 5f81bfcde3 Improve handling of -MOVED and -ASK responses.
This change improves code reusing and simplifies the internals of
our redis-cluster connection backend.
2013-12-21 11:56:40 +01:00
Daniele Alessandri 2aa97e3e61 Apply fixes for consistency.
I know that importing classes and interfaces for the root namespace
using the "use" directive is weird and totally uncommon...
2013-12-21 11:26:09 +01:00
Daniele Alessandri 131a5a4ef9 Fix a few exceptions. 2013-12-21 11:08:29 +01:00
Daniele Alessandri d1ec791ae9 Rename examples and update some comments.
[ci skip]
2013-12-21 10:50:08 +01:00
Daniele Alessandri 8916f9a9ee Fix a few exception messages. 2013-12-21 10:07:56 +01:00
Daniele Alessandri 3ac4141bcb [bin] Minor fix in comments.
[ci skip]
2013-12-21 10:02:26 +01:00
Daniele Alessandri 587bf0b1e5 [tests] Add $message as third parameter for our custom assertions. 2013-12-19 11:50:39 +01:00
Daniele Alessandri 7cf9cfc496 Update phpdocs.
[ci skip]
2013-12-19 11:20:27 +01:00
Daniele Alessandri f7fea2e430 Update README.
[ci skip]
2013-12-18 16:06:34 +01:00
Daniele Alessandri c51485b318 Update README.
[ci skip]
2013-12-18 16:04:22 +01:00
Daniele Alessandri e1fb9ed1d4 Update README.
[ci skip]
2013-12-18 11:52:19 +01:00
Daniele Alessandri 913bcb3b20 Merge branch 'v0.9/hhvm-compatibility' 2013-12-17 16:37:26 +01:00
Daniele Alessandri 2d23edc691 Add a note about Predis on HHVM in the README. 2013-12-17 16:36:03 +01:00
Daniele Alessandri af2548581e Add HHVM to the build matrix on Travis CI.
We allow failures on builds run against HHVM since it is still being
developed and things could break anytime, but at least this gives us
a decent indicator of the level of compatibility of Predis with this
runtime.
2013-12-17 16:20:48 +01:00
Daniele Alessandri fdf63bc03d Make Predis compatible with HHVM (at least 2.3.0).
Achieving compatibility actually required a few marginal changes:

  - HHVM still has some issues with re-entrant calls to __get(). The
    applied change is an hack simply because it is ugly, but it is not
    wrong and does not break the signature of the options interface.
  - Since we cannot rely on the PHP version to detect the availability
    of socket_import_stream(), we switched to function_exists(). As an
    added bonus, using function_exists() is twice faster.
  - In the test suite we removed an assertion for the message of an
    E_WARNING simply because HHVM emits a different message. Checking
    for the warning is actually enough in that context.

While the whole test suite passes on HHVM 2.3.0, please remember that
HHVM is still in development and things could break anytime especially
in some obscure corner cases.
2013-12-17 16:09:20 +01:00
Daniele Alessandri 2a6409590c Run php-cs against test suite. 2013-12-17 12:55:56 +01:00
Daniele Alessandri 02e43143d3 Run php-cs against codebase. 2013-12-17 12:53:36 +01:00
Daniele Alessandri 5529e33047 Rework classes and interfaces in Predis\Command\Processor namespace.
- Renamed `Predis\Command\Processor\CommandProcessorInterface`
  - Removed `Predis\Command\Processor\CommandProcessorChainInterface`
2013-12-17 10:57:43 +01:00
Daniele Alessandri 1125dc1bdd Apply minor change in command serialization. 2013-12-16 18:46:17 +01:00
Daniele Alessandri feb69fb5f2 Update README.
[ci skip]
2013-12-16 17:05:49 +01:00
Daniele Alessandri 1d31a47cf4 Apply some tweaks to FAQ.
[ci skip]
2013-12-16 16:58:28 +01:00
Daniele Alessandri 35b2c99186 Update FAQ.
[ci skip]
2013-12-16 16:52:04 +01:00
Daniele Alessandri 0f34f41ccf Fix and reword some exception messages. 2013-12-16 15:13:18 +01:00
Daniele Alessandri 43f278fb71 Update testing README.
[ci skip]
2013-12-16 11:47:22 +01:00
Daniele Alessandri b7a7b6838b Merge branch 'v0.9/fix-bin-scripts' 2013-12-15 17:06:05 +01:00
Daniele Alessandri 714f9ddc64 [bin] Drop extension from scripts in the bin directory. 2013-12-15 17:05:30 +01:00
Daniele Alessandri d465de7b42 [bin] Handle duplicate class names or aliases in same namespace.
This condition can occur when merging multiple class files (with their
own independent "use" directives) in one big file: when imported class
names or aliases clashes, the script now tries to automatically rename
them by defining a new alias in the "use" directive and renaming the
occurences in the buffer containing the class code.

This fix is naive at best, but seems to work fine so we will just live
with it for now.
2013-12-15 17:05:07 +01:00
Daniele Alessandri 405ae4af65 [bin] Fix broken rendering of "use ... as ..." in output. 2013-12-15 17:04:55 +01:00
Daniele Alessandri 7e89719ceb Revert usage of "short" namespaces in use directives.
We started using partially-qualified names as an experiment but we are
reverting to using fully-qualified names + aliases in use directives.
2013-12-15 17:00:45 +01:00
Daniele Alessandri aa9aafe926 Remove CHANGELOG.NAMING.md.
Will be most likely replaced by UPGRADING.md or something like that.

[ci skip]
2013-12-15 14:04:52 +01:00
Daniele Alessandri 0779c1c24a Trim some more "use" directives in Predis\Client. 2013-12-15 14:01:05 +01:00
Daniele Alessandri e66c33a444 Merge branch 'v0.9/connection-namespace-1' 2013-12-15 13:44:04 +01:00
Daniele Alessandri 001a7c0827 Update CHANGELOG. 2013-12-15 13:42:47 +01:00
Daniele Alessandri 58c5029574 Rename Predis\Connection\AggregatedConnectionInterface. 2013-12-15 12:38:21 +01:00
Daniele Alessandri 6ceebbfbec Rename Predis\Connection\SingleConnectionInterface::pushInitCommand().
The new name is more explicit as it makes obvious that the commands
added with it will be executed upon connect().
2013-12-15 11:25:38 +01:00
Daniele Alessandri c40ad5dd47 Rename Predis\Connection\ConnectionParameters. 2013-12-15 10:22:23 +01:00
Daniele Alessandri 2a7607f128 Rename Predis\Connection\ConnectionFactory. 2013-12-15 10:09:08 +01:00
Daniele Alessandri 278f89924c Set the test suite to run against Redis 2.8 on Travis CI. 2013-12-14 20:44:10 +01:00
Daniele Alessandri cbf015164c Rename Predis\Client::raw() to Predis\Client::executeRaw().
This is more consistent with Predis\Client::executeRaw() and its more
explicit since simply "raw" as a method name was a bit too vague even
despite being nicely short.
2013-12-14 20:37:28 +01:00
Daniele Alessandri fded76bca3 Merge branch 'v0.9/connection-persistent-path' 2013-12-14 14:16:54 +01:00
Daniele Alessandri e7f2d28f23 Use path to differentiate persistent TCP streams.
Closes #139.
2013-12-14 14:15:41 +01:00
Daniele Alessandri 1c05c878f4 Update CHANGELOG. 2013-12-11 13:11:39 +01:00
Daniele Alessandri 5eb975ef69 Improve URI parsing for connection parameters.
Using PHP's "parse_str()" to parse the query string is slightly more
efficient then our own code especially when the number of fields in
the query string grows, with the additional benefit of supporting
arrays for values when brackets are present in fieldnames.

So after this commit, providing this URI string:

  $string = 'tcp://127.0.0.1?metavars[]=foo&metavars[]=hoge';

Is equivalent to providing the following named array:

  $array = [
    'scheme' => 'tcp',
    'host' => '127.0.0.1',
    'metavars' => ['foo', 'hoge'],
  ];

Aside from this improvement, the URI parsing behavior has not changed.
2013-12-11 12:45:47 +01:00
Daniele Alessandri 3fbc012c03 Add "path" in phpdoc of connections supporting UNIX domain sockets. 2013-12-11 12:20:10 +01:00
Daniele Alessandri cebdf889a4 Rename connection to Predis\Connection\PhpiredisSocketConnection. 2013-12-10 22:14:34 +01:00
Daniele Alessandri 863718bd2b Apply another minor fix to README. 2013-12-10 22:11:40 +01:00
Daniele Alessandri b01ac201d6 Fix README.
[ci skip]
2013-12-10 22:00:53 +01:00
Daniele Alessandri d5bd28958a Minor code styling adjustments. 2013-12-10 21:58:53 +01:00
Daniele Alessandri 95b9f63cab Rename write and read methods of the composable connection interface. 2013-12-10 21:26:11 +01:00
Daniele Alessandri 59b5658cf5 Big batch of phpdoc improvements and minor code styling fixes. 2013-12-10 19:21:52 +01:00
Daniele Alessandri 8744285cb3 Ignoring the plural form of "reply" for renames is dumb...
[ci skip]
2013-12-10 18:08:14 +01:00
Daniele Alessandri 14fda643f7 Replace remaining occurrences of "reply" with "response". 2013-12-10 15:54:41 +01:00
Daniele Alessandri 57e778e098 Tweak CONTRIBUTING.md.
[ci skip]
2013-12-09 12:22:25 +01:00
Daniele Alessandri f9fd4525b5 More minor tweaks in the README.
[ci skip]
2013-12-09 12:07:11 +01:00
Daniele Alessandri ac34983635 Pad headers lines in CHANGELOG.
[ci skip]
2013-12-09 11:46:14 +01:00
Daniele Alessandri 978e7ac551 Update CHANGELOG.
[ci skip]
2013-12-09 11:44:42 +01:00
Daniele Alessandri ca11c522d6 Miscellaneous fixes to README after rewrite.
... because spotting errors always happens after committing changes.

[ci skip]
2013-12-08 21:12:15 +01:00
Daniele Alessandri 6eed3d7647 Commit long overdue rewrite of the README.
[ci skip]
2013-12-08 20:54:12 +01:00
Daniele Alessandri 73cb6bbf2a [tests] Fix class name of connection parameters test case.
[ci skip]
2013-12-08 16:09:22 +01:00
Daniele Alessandri 5acdab9176 Merge branch 'v0.9/redis-cluster-fix'
Conflicts:
	lib/Predis/Connection/RedisCluster.php
2013-12-08 15:24:52 +01:00
Daniele Alessandri 35023dd83f Use a more appropriate exception. 2013-12-08 15:12:35 +01:00
Daniele Alessandri 70df7ca64d Apply common parameters to connections created on the fly in cluster.
These parameters are applied only to connections being created on the
fly when not part of the current pool. This condition usually happens
upon -MOVE or -ASK responses returned by Redis to redirect client to
different nodes.
2013-12-08 15:12:28 +01:00
Daniele Alessandri f0549b5dad Rewrite phpdoc comments for the redis-cluster connection backend. 2013-12-08 12:43:24 +01:00
Daniele Alessandri b7ac7595c2 Change signature of the connection parameters class constructor.
Only named arrays are explicitly accepted now, but the old behaviour
of creating a connection parameters instance out of an URI string or
a named array is still available using the "create()" static method.

  $array = ['host' => '127.0.0.1', 'timeout' => 1];
  $uri = 'tcp://127.0.0.1?timeout=1';

  $parameters = new ConnectionParameters($array);      // Arrays only
  $parameters = ConnectionParameters::create($array);  // Arrays OK
  $parameters = ConnectionParameters::create($uri);    // Strings OK

The purpose of the change is to have a more concise constructor with a
well defined signature.
2013-12-08 11:35:53 +01:00
Daniele Alessandri 1f66f4e8f7 Various fixes and improvements to redis-cluster connection backend.
List of changes:

  - The cluster connection can be initialized with a partial list of
    nodes, the full slots map will be fetched from Redis itself using
    the CLUSTER NODES command.
  - The slots map can be optionally retrieved from Redis if the server
    returns a -MOVE response, otherwise only the interested slot will
    be permanently reassigned to the new target node.
  - $cluster->connect() connects to a random connection in the pool
    instead of forcing the connect operation on all the connections.
2013-12-07 21:19:52 +01:00
Daniele Alessandri 73eb09378e Remove obsolete comments.
The phpiredis-based connection backends are not experiments anymore.

[ci skip]
2013-12-07 15:59:28 +01:00
Daniele Alessandri 427309db69 Apply code styling fixes.
There is actually no need for such indentations.
2013-12-07 15:41:45 +01:00
Daniele Alessandri ee45de4f0e Rename base response interface to Predis\Response\ResponseInterface. 2013-12-07 15:22:42 +01:00
Daniele Alessandri 6ece8c40a0 Merge branch 'v0.9/response-status' 2013-12-07 15:16:20 +01:00
Daniele Alessandri 373d30b070 Use Predis\Response\Status to identify all kinds of status responses.
Status response objects are needed mostly to make it possible from the
client perspective to differentiate a status response with the payload
"OK" from a normale bulk reply containing "OK".

The biggest change is for commands returning +OK responses: these were
previously translated to TRUE (bool value), but they are now returned
as instances of Predis\Response\Status. Just to illustrate an example
of the possibilities with this change we will use SET since it is the
most widely used command returning +OK:

  $response = $client->set('foo', 'bar');

  echo $response;         // 'OK'
  $response == 'OK';      // TRUE
  isset($response->ok);   // TRUE
  $response == true;      // TRUE
  $response === true;     // FALSE
  $response instanceof Predis\Response\ObjectInterface;     // TRUE
  $response instanceof Predis\Response\Status;              // TRUE

For those checking responses returned by commands such as SET or PONG,
the breaking change basically lies in the usage of strict comparison:
doing $response === true will now evaluate to FALSE instead of TRUE.

By default Predis caches common status responses such as OK or QUEUED
to lower the memory usage when using pipelines or transactions.
2013-12-07 15:15:23 +01:00
Daniele Alessandri ac8d0c1f42 Fix example.
[ci skip]
2013-12-02 16:12:43 +01:00
Daniele Alessandri cf70d2f20e Minor code styling changes in Predis\Connection. 2013-12-02 16:05:05 +01:00
Daniele Alessandri ccf8822b4b Minor change in stream connection destructor. 2013-12-02 15:45:29 +01:00
Daniele Alessandri d27d3b0acb Cache value casters in static member variable.
This optimization is mostly useful since connection parameters can be
instantiated more than one time in a script when using clustering or
replication.
2013-12-02 14:34:02 +01:00
Daniele Alessandri 5b7504b069 Simplify connection factory class by removing dependency on profile.
A profile is not needed to create instances of basic initialization
commands such as AUTH (authentication) and SELECT (database selection)
since they do not need prefixing and other stuff.

If needed, developers can still extend the base connection factory to
make it use a server profile and configure the client to use it using
options.
2013-12-02 13:01:37 +01:00
Daniele Alessandri 6ef8e99822 Make sure raw commands IDs are normalized to uppercase. 2013-12-02 11:42:06 +01:00
Daniele Alessandri 4bf0ee4c6a Rename ConnectionInterface::writeCommand() to writeRequest().
This name is more consistent with its counterpart, readResponse().
2013-12-02 11:08:14 +01:00
Daniele Alessandri 59b9319bb8 [tests] Minor tweak for timeouts. 2013-12-02 10:46:44 +01:00
Daniele Alessandri bcf92a7548 [tests] Fix tests under PHP 5.3 due to another dumb mistake.
Seriously, I am startint to hate PHP 5.3...
2013-12-01 15:27:42 +01:00
Daniele Alessandri d2c001812f [tests] Rename test case classes. 2013-12-01 15:11:09 +01:00
Daniele Alessandri 3ae3414a0d [tests] Normalize casing of command identifiers in constraint.
We do not care much about the casing of command IDs in our constraint,
so it makes no difference for use if it is "SET" or "set".
2013-12-01 14:00:57 +01:00
Daniele Alessandri a00ffbf53f Rework examples for plain Redis commands. 2013-12-01 13:42:10 +01:00
Daniele Alessandri 583b924085 Add the ability for the client to send raw commands to Redis.
When sending raw commands their arguments are not filtered, responses
are not parsed and key prefixes are not applied. The client also does
not throw any exception on Redis errors regardless of its settings.

The first parameter takes the raw arguments of the command (included
its identifier) as defined by the Redis documentation while the second
optional parameter is always populated by reference to indicate when
Redis actually returned an error response.

  $client->raw(['PING']);                // "PONG"
  $client->raw(['SET','foo','bar']);     // "OK"
  $client->raw(['GET','foo'], $err);     // "bar", $err=FALSE
  $client->raw(['LPUSH','foo',1], $err); // "WRONGTYPE...", $err=TRUE

Internally, this method creates instances of Predis\Command\RawCommand
that get passed to the underlying connection instance for execution as
if they were usual commands defined by Predis.

Raw commands work in both cluster and replication scenarios since they
are recognized by their command ID, but key prefixing is not supported
since it is done by the profile instance when instantiating commands.
2013-12-01 13:42:03 +01:00
Daniele Alessandri 3c77d3b0c1 Update examples. 2013-12-01 11:28:56 +01:00
Daniele Alessandri e049db124c By convention all of the Redis command identifiers are uppercase. 2013-12-01 10:45:37 +01:00
Daniele Alessandri b2e0dc62fe [tests] Fix dumb mistake when running tests with older profiles. 2013-11-30 20:06:37 +01:00
Daniele Alessandri f7ff65e0fa Merge branch 'v0.9/test-suite-improvements' 2013-11-30 19:39:12 +01:00
Daniele Alessandri b253bdc41e [tests] Improve the basic framework of our test suite.
We now have a base test case class for Predis (namely PredisTestCase)
grouping various commonly used utility methods shared by all of the
tests in the suite, greatly improving reusability.
2013-11-30 19:38:27 +01:00
Daniele Alessandri 5d7f297110 Send ASKING command when redis-cluster returns a -ASK response.
We must always execute ASKING on the connection identified by the -ASK
response before executing the actual command because not doing so will
break the cluster specifications while redis-cluster is performing a
resharding operation.
2013-11-30 17:37:37 +01:00
Daniele Alessandri 7f690cb62e [tests] Add a PHPUnit constraint to verify commands.
Commands are verified by checking their IDs and optionally arguments.
Passing a command instance is only a shortcut to set the expected ID
and arguments, commands are never compared for identity but always for
equivalence.
2013-11-30 17:14:42 +01:00
Daniele Alessandri cb3cd050ab Update CHANGELOG.
Remove recently added lines as they are not entirely true, the client
currently does not apply any key prefixing for any command not created
by Redis profiles even though it is possible now, contrary to v0.8.

[ci skip]
2013-11-30 15:12:36 +01:00
Daniele Alessandri c4e0d9df92 Update CHANGELOG.
[ci skip]
2013-11-30 14:55:12 +01:00
Daniele Alessandri 41ea336175 [tests] Update tests for Predis\Command\Command. 2013-11-30 14:45:51 +01:00
Daniele Alessandri 5d40b85f10 Update CHANGELOG. 2013-11-30 14:42:02 +01:00
Daniele Alessandri b7ac108d43 Merge branch 'v0.9/commands' 2013-11-30 14:35:45 +01:00
Daniele Alessandri cf581a3b49 Add new command class useful to execute "raw" Redis commands.
By raw we mean that input arguments are not filtered and responses are
not parsed, which means arguments must follow the command signature as
defined by Redis and complex responses are left untouched.

When instantiating an instance of `Predis\Command\RawCommand` you must
pass at least the command ID. You can pass further arguments in the
array or you can just set them later with `RawCommand::setArguments()`
but you cannot modify the command ID once instantiated.

  $command = new Predis\Command\RawCommand(['SET', 'foo', 'bar']);
  $response = $client->executeCommand($command);

While higher level abstractions built upon `Predis\Client` should just
use commands created by the profile in use, inner parts of the library
might use raw commands to provide certain functionalities making sure
that input and output of commands are always consistent, independent
of the profile.
2013-11-30 14:30:33 +01:00
Daniele Alessandri bb4b8c3c35 Fix phpdoc. 2013-11-30 13:08:31 +01:00
Daniele Alessandri 09895f27bf Rename command class for scripting to Predis\Command\ScriptCommand.
We also changed our wording to indentify this kind of abstraction so
instead of using "scripted commands" (kind of broken English) we now
use "scriptable commands".
2013-11-30 12:43:21 +01:00
Daniele Alessandri 980edf2f7b Rename base command class to Predis\Command\Command. 2013-11-30 12:24:43 +01:00
Daniele Alessandri c8e20e428a Remove interface defining objects aware of command processing.
It was too broad in concept and not really that useful, so for now we
assume that only our base Predis\Profile\RedisProfile class can handle
command processing and we will decide later if we want to add a couple
of more methods to Predis\Profile\ProfileInterface.
2013-11-30 10:27:51 +01:00
Daniele Alessandri 7a56856d46 Make option "prefix" accept command processor instances. 2013-11-30 10:00:30 +01:00
Daniele Alessandri fc4e279b9a [tests] Fix tests on PHP 5.3.
Stupid PHP 5.3.
2013-11-29 23:39:50 +01:00
Daniele Alessandri 09b0750cb7 [tests] Improve tests of TTL. 2013-11-29 23:27:59 +01:00
Daniele Alessandri d52daacba9 [tests] Improve base command test case. 2013-11-29 23:27:56 +01:00
Daniele Alessandri 00247abc50 [tests] Add missing tests for SETNX.
For some obscure reason Predis/Command/StringSetPreserve had no tests.
2013-11-29 22:50:09 +01:00
Daniele Alessandri 862da9a3b2 [tests] Add "count" argument to SRANDMEMBER (since Redis >= 2.6). 2013-11-29 22:45:35 +01:00
Daniele Alessandri d6173bb33b Merge branch 'v0.9/external-prefixer' 2013-11-29 22:40:46 +01:00
Daniele Alessandri d0b431016d Move key prefixing logic from command classes to prefix processor.
While command classes define how the client should filter arguments or
parse responses, key prefixing depends on the actual command signature
as defined by Redis so it really is something that should be handled
separately as the norm.

Developers can define new handlers or override existing ones, but they
can still define the key prefixing logic inside their command classes
by implementing Predis\Command\PrefixableCommandInterface: the key
prefix processor will just use that by overriding any defined handler.
2013-11-29 22:36:32 +01:00
Daniele Alessandri 5856bde198 Fix phpdoc.
[ci skip]
2013-11-29 11:14:56 +01:00
Daniele Alessandri 4840736503 Apply some minor internal changes to concrete connection classes. 2013-11-23 11:08:38 +01:00
Daniele Alessandri 2d39408c0f Add a method to the pipeline to fetch the connection from the client.
In this method we can put the logic needed to prepare the connection
right before sending the queued commands to the server (e.g. switching
to the master server when connected in replication mode).
2013-11-22 15:41:50 +01:00
Daniele Alessandri 374101cfc3 Apply minor change. 2013-11-22 12:17:57 +01:00
Daniele Alessandri cec7c24236 Merge branch 'v0.9/server-profile' 2013-11-22 12:15:08 +01:00
Daniele Alessandri 2a5483df90 Reorganize the Predis\Profile namespace.
The profile factory code has been extrapolated into a the new class
Predis\Profile\Factory (final and with only static methods).
2013-11-22 12:01:29 +01:00
Daniele Alessandri 0a60a156f0 Merge branch 'v0.9/transaction-multiexec-state' 2013-11-17 16:20:29 +01:00
Daniele Alessandri 1e0ef2a64d Move check for WATCH and UNWATCH support. 2013-11-17 16:17:24 +01:00
Daniele Alessandri 30e74e36aa Update CHANGELOG. 2013-11-17 16:05:12 +01:00
Daniele Alessandri 1125809de9 Force exceptions on error responses for transaction control commands.
Commands such as MULTI, EXEC, DISCARD, WATCH and UNWATCH disregard the
the "exceptions" client option and always result in an exception being
thrown on error responses even when the client is configured to return
those errors ("exceptions" set to FALSE).
2013-11-17 15:58:26 +01:00
Daniele Alessandri 712e6420e9 Rewrite parts of Predis\Transaction\MultiExec to make them less ugly.
We also changed some options for this class, the accepted ones are:

  - "keys": string or array of strings for automatic WATCH.
  - "cas": sets the check-and-set mode.
  - "retry": number of attempts before giving up aborted transactions.
  - "exceptions": sets whether exceptions should be thrown on error
    responses (overrides the "exceptions" client option).

The "on_retry" option has been removed.
2013-11-17 15:57:43 +01:00
Daniele Alessandri 84e0f5af7a Extract the tracking of transaction states to a separate class.
The Predis\Transaction\MultiExecState class is actually quite dummy as
it does not work as a finite-state machine, but is used to track state
using flags. It basically replicates the same behaviour of when it was
part of the Predis\Transaction\MultiExec code and it will do for now.
2013-11-17 13:15:36 +01:00
Daniele Alessandri c047c4992a Merge branch 'v0.9/pipeline-refactoring' 2013-11-17 11:07:24 +01:00
Daniele Alessandri ce706a375c Merge branch 'v0.9/add-commands' 2013-11-17 11:06:22 +01:00
Daniele Alessandri 66f22899a1 Update CHANGELOG. 2013-11-17 11:04:03 +01:00
Daniele Alessandri 620adb2fe6 New command: PUBSUB (Redis 2.8). 2013-11-17 11:02:37 +01:00
Daniele Alessandri 1b5e2d0d0d New command: SENTINEL (Redis 2.6). 2013-11-17 11:02:33 +01:00
Daniele Alessandri 3d499e82a3 Add some supported options to Predis\Client::pipeline().
Only two options available for now, used to specify which kind of
pipeline object the client should use or return:

  - "atomic": returns a pipeline wrapped in a MULTI / EXEC transaction
    (class: Predis\Pipeline\Atomic).
  - "fire-and-forget": returns a pipeline that does not read back
    responses from the server (class: Predis\Pipeline\FireAndForget).

We might add more options in the future.
2013-11-16 21:54:17 +01:00
Daniele Alessandri 8068c87e47 Rewrite a good chunk of the classes in the Predis\Pipeline namespace.
First of all we completely removed the concept of pipeline executors.
Now pipelines can be easily customized by extending our default class
Predis\Pipeline\Pipeline.

Tests coverage for the Predis\Pipeline namespace is decent but can be
definitely improved while test cases can be beautified.
2013-11-16 21:23:49 +01:00
Daniele Alessandri c6f51e82bf Rename "Array" type-hint to to "array". 2013-11-16 17:01:58 +01:00
Daniele Alessandri 944da8c9b8 Rename Predis\Cluster\Hash\CRC16HashGenerator. 2013-11-16 16:41:00 +01:00
Daniele Alessandri f01ef33735 Merge branch 'v0.9/rename-transaction-class' 2013-11-16 16:35:07 +01:00
Daniele Alessandri aaac082324 Rename classes in Predis\Transaction. 2013-11-16 16:34:34 +01:00
Daniele Alessandri aa210aeb03 Merge branch 'v0.9/rename-cluster-classes' 2013-11-16 16:22:37 +01:00
Daniele Alessandri e6f1788e27 Rename interfaces and classes in the Predis\Cluster namespace.
A few methods were renamed accordingly for consistency
2013-11-16 16:21:27 +01:00
Daniele Alessandri c26b6763f9 Merge branch 'v0.9/rename-monitorcontext' 2013-11-16 15:43:16 +01:00
Daniele Alessandri 9d2cb975eb Rename classes in Predis\Monitor namespace. 2013-11-16 15:42:56 +01:00
Daniele Alessandri 2e6a76d83e Fix phpdocs. 2013-11-16 15:33:39 +01:00
Daniele Alessandri 046715a455 Merge branch 'v0.9/rename-pubsubcontext' 2013-11-16 15:28:54 +01:00
Daniele Alessandri c3a58dffdf Rename classes in the Predis\PubSub namespace. 2013-11-16 15:27:58 +01:00
Daniele Alessandri 7d60e995f8 Rename session handler class. 2013-11-16 15:03:05 +01:00
Daniele Alessandri f18d7103f5 [tests] Remove useless backslash in "use" directive. 2013-11-16 14:57:00 +01:00
Daniele Alessandri e42c72c043 Merge branch 'v0.9/rename-response-classes' 2013-11-16 14:04:25 +01:00
Daniele Alessandri 621c3d7fd3 Update CHANGELOG. 2013-11-16 14:03:17 +01:00
Daniele Alessandri b71c798f9b Rename classes in Predis\Response and Predis\Response\Iterator.
This change aims to make class names shorter with less redundant fully
qualified names.

Merge!
2013-11-16 14:01:11 +01:00
Daniele Alessandri 1022be6010 [tests] Fix tests. 2013-11-16 12:54:09 +01:00
Daniele Alessandri e41db42b01 Merge branch 'v0.9/response-namespace' 2013-11-16 12:50:45 +01:00
Daniele Alessandri ecd4c6281c Update CHANGELOG. 2013-11-16 12:48:37 +01:00
Daniele Alessandri 35ce43d526 Update CHANGELOG. 2013-11-16 12:44:56 +01:00
Daniele Alessandri 7f9d06bb83 Move the Predis\Iterator namespace into Predis\Response. 2013-11-16 12:07:35 +01:00
Daniele Alessandri a8edc02eb6 Create the Predis\Response namespace.
All of the response interface, classes and exceptions have been moved
into this namespace.
2013-11-16 11:59:33 +01:00
Daniele Alessandri 205ffbbd73 Update composer.json. 2013-11-16 11:27:47 +01:00
Daniele Alessandri b0f955b437 Fix phpdocs.
[ci skip]
2013-11-12 19:49:55 +01:00
Daniele Alessandri 8d15e5ec08 Do not pipeline initialization commands. 2013-11-12 18:16:54 +01:00
Daniele Alessandri 6fbc421b96 Fix URI parsing on multiple equal signs in pair value of query string. 2013-11-12 12:46:45 +01:00
Daniele Alessandri 298e7a14fa [tests] Add @medium since this could take a little bit more than 1sec. 2013-11-12 12:34:51 +01:00
Daniele Alessandri 22c2230d00 [tests] Rename just a couple of test methods. 2013-11-12 12:31:49 +01:00
Daniele Alessandri a1c7889584 Add option "aggregate" to customize multiple connections aggregation.
This option must return a callable object that is used to override how
the client aggregates connections when passing an array of parameters
to its constructor.

When specified, this option overrides both "cluster" and "replication"
as it allows to make use of your own code to aggregate multiple nodes.

This is, for example, how you can mimic the standard initialization of
a cluster that relies on client-side sharding:

  $parameters = ['tcp://127.0.0.1:6380', 'tcp://127.0.0.1:6381'];

  $options = [
    'aggregate' => function () {
      return function ($parameters, $options) {
          $connection = new Predis\Connection\PredisCluster();
          $options->connections->aggregate($connection, $parameters);

          return $connection;
      };
    },
  ];

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

When invoked by the client, the specified callable must always return
a Predis\Connection\ConnectionInterface instance or the client will
throw an UnexpectedValueException.
2013-11-12 12:31:45 +01:00
Daniele Alessandri 5e90e615b1 Minor optimizations for the parsing of URI parameters.
Using list() with the warning suppressor is slower than using isset()
to check the presence of the first two elements of the array returned
by explode(). This also allow us to skip incomplete query string pairs
when parsing the URI string.

We have also changed the exception being thrown on invalid URIs to a
more appropriate one.
2013-11-11 18:59:21 +01:00
Daniele Alessandri 321d2624ab Rename URI parsing method of Predis\Connection\ConnectionParameters. 2013-11-11 18:30:42 +01:00
Daniele Alessandri acd29ec076 Slightly change interface for connection factories. 2013-11-11 18:24:20 +01:00
Daniele Alessandri 3c6389f4dd Rework how connection parameters are handled.
These changes do not affect the actual functionalities of the client,
but make the code more explicit and less error-prone.
2013-11-11 18:17:09 +01:00
Daniele Alessandri 1d53f8a988 Minor code styling changes. 2013-11-11 15:59:21 +01:00
Daniele Alessandri dd679661dd Address #133 by reusing our own methods.
The main reason behind that code duplication was performance related
as we tried to reduce method calls when possible, even at the cost of
falling into the realm of early optimizations. Apparently we just lose
~400 req/sec on a 21000 req/sec basis ("SET foo bar") using PHP 5.5.3
(packaged by Ubuntu 13.10) on an Intel Q6600, so we will most likely
stick with this change for the sake of best practices.
2013-11-11 15:49:16 +01:00
Daniele Alessandri f36138c0b7 Minor internal change.
Check directly the private parameters array instead of passing through
method dispatching for the outer __isset() method.
2013-11-11 15:28:42 +01:00
Daniele Alessandri 31e08f0d5d Tweak phpdocs. 2013-11-11 12:29:43 +01:00
Daniele Alessandri cdb91284f1 Clean up ugly code bits. 2013-11-10 12:30:24 +01:00
Daniele Alessandri 38c7697881 Remove useless method. 2013-11-10 12:27:15 +01:00
Daniele Alessandri b45ba55f46 Switch to protected visibility for some members of Predis\Client.
The "profile" member is actually used for caching purposes as fetching
its value from the options instance would add noticeable overhead in a
part of the client where every bit of optimization matters, for this
we decided to keep it private.
2013-11-10 12:20:20 +01:00
Daniele Alessandri b50b90aa66 Rename method. 2013-11-10 12:17:58 +01:00
Daniele Alessandri 0128ecc623 Fix strict notice for undefined property during tests. 2013-11-09 20:37:30 +01:00
Daniele Alessandri bf991a24f3 Merge branch 'v0.9/protocol-processor-api' into integration 2013-11-09 20:02:21 +01:00
Daniele Alessandri bab0cd999c Rename Predis\Client::multiExec() to Predis\Client::transaction().
Method was deprecated since Predis v0.8.5.
2013-11-09 19:56:01 +01:00
Daniele Alessandri 30d81c942a Update CHANGELOG. 2013-11-09 19:32:38 +01:00
Daniele Alessandri f066356858 Slightly change the internal context factory method of Predis\Client. 2013-11-09 19:16:09 +01:00
Daniele Alessandri 7649533141 Remove some obsoleted or useless methods from Predis\Client. 2013-11-09 19:07:24 +01:00
Daniele Alessandri a4d0abc734 Minor tweaks to code.
Also removed a useless method, most likely a leftover from the past.
2013-11-09 19:02:01 +01:00
Daniele Alessandri b38f640ac9 Minor tweaks to phpdoc descriptions. 2013-11-09 18:57:35 +01:00
Daniele Alessandri d431ea6dba Change visibility of some members.
There's no actual need for these ones to be private.
2013-11-09 18:47:30 +01:00
Daniele Alessandri 715b1badcb Remove useless class constants. 2013-11-09 18:46:05 +01:00
Daniele Alessandri d79aadc38f Update CHANGELOG. 2013-11-09 18:44:22 +01:00
Daniele Alessandri 9136033eea Rationalize API renaming or moving stuff in Predis\Protocol namespace. 2013-11-09 18:36:28 +01:00
Daniele Alessandri 16d17af577 Rework the API for external protocol processors. 2013-11-09 17:10:07 +01:00
Daniele Alessandri 1a7b604c1b Tweak phpdocs and API of streamable multibulk iterators.
Despite not being a globally supported feature of Predis anymore, they
are still optionally supported by our default text protocol processors
and they can be used to build custom stuff for specific needs.
2013-11-09 15:49:11 +01:00
Daniele Alessandri 70a84e1d5a Merge branch 'v0.9/drop-multibulk-iterators' 2013-11-09 12:23:31 +01:00
Daniele Alessandri fb2d8a37c1 Drop support for streamable multibulk responses.
Supporting this feature has been problematic and leaded to some ugly
code to make abstractions such as pipelines and transactions aware of
these kind of response objects. Furthermore, it was not possible to
add them to all the connection classes due to implementation limits.

For such reasons Predis do not support them globally anymore, but the
actual classes are still shipped within the library so that they can
be used to build custom stuff at a level lower than client (that is,
unless we decide to remove them for good before going stable).
2013-11-09 12:07:36 +01:00
Daniele Alessandri 7c5aba08cd Make "2.8" the default Redis server profile. 2013-11-08 16:04:07 +01:00
Daniele Alessandri 1a769e3cc3 Fix different behaviour of filter_var() with NULL on PHP 5.3.
Seriously, they could not possibly screw bool validation up any more
than they already had with filter_var().
2013-11-08 15:31:14 +01:00
Daniele Alessandri 9a72b98c4a Remove previously deprecated Predis\Helpers class. 2013-11-08 12:13:37 +01:00
Daniele Alessandri 089b972def Merge branch 'v0.9/client-options' 2013-11-08 12:08:26 +01:00
Daniele Alessandri b1ebc8df2f Reimplement from scratch client configuration.
This commit is a complete rewrite of the classes previously contained
in the Predis\Option namespace aimed at lowering the initialization
overhead while bringing in more consistency. The overall idea is still
the same with a mini DI container, Predis\Configuration\Options, which
carries options with values that can be initialized lazily.

The first difference with our previous implementation is that now even
user-defined options can be initialized lazily, everything needed is
an object responding to the __invoke() magic method such as a closure.
Other kind of callable arguments (strings, arrays) will be treated as
plain values. The only drawback is that we cannot pass any instance of
classes implementing __invoke() as an option value, but considered the
limited scope of our use case we can say it's more of an acceptable
compromise. Callbacks used for lazy initialization will receive two
arguments upon invokation:

  - The current instance of Predis\Configuration\Option ($options)
  - A string containing the name of the option ($option)

This is an example in actual code:

  $options = new Predis\Configuration\Options([
    'exceptions' => true,
    'profile' => '2.8',
    'distributor' => function () {
      return new Predis\Cluster\Distribution\KetamaPureRing();
    },
    'cluster' => function ($options) {
      $distr    = $options->distributor;
      $strategy = new Predis\Cluster\PredisClusterHashStrategy($distr);
      $cluster  = new Predis\Connection\PredisCluster();

      return $cluster;
    },
    'connections' => function ($options, $option) {
      $factory = $options->getDefault($option);
      $factory->define('tcp', 'Predis\Connection\PhpiredisConnection');

      return $factory;
    },
  ]);

As you can see there's very little difference compared to before in
the actual usage as most changes are under the hood. Some options such
as "exceptions" and "replication" can now correctly parse bool values
from strings (so the string "false" is not evaluated as boolean true).

While options were initially conceived to configure the client and its
behavior, the concept has matured and it's perfectly fine to consider
the use of Predis\Configuration\Options to propagate configurations to
inner parts of the library.
2013-11-08 12:08:11 +01:00
Daniele Alessandri 153758019c Diverging from the v0.8 branch (current stable).
This commit marks the start of works for the next major release of
Predis which will bring various breaking changes needed to polish the
internal design making the library even more flexible to use or extend
and, more importantly, almost stable in terms of API.

The Redis commands API exposed by Predis\Client is not going to change
much if not at all which is a good news. The most immediate changes
affecting developers will involve the renaming of a few namespaces and
classes, the removal of some previously deprecated classes and methods
and some tweaks to the current abstractions.

Right now the plan is to have a fast paced development to release this
version as soon as possible, ideally a few weeks later than Redis 2.8,
then wait to see the final definition of redis-cluster so that we can
tweak our code if needed and finally hit the v1.0.0 milestone with as
few changes as possible. Furthermore, v1.x will most likely be the
last version of Predis supporting PHP 5.3 as we will start migrating
to PHP 5.4 (or even 5.5) with v2.x, which is not going to happen soon
anyway.
2013-11-08 11:17:50 +01:00
Daniele Alessandri 62f8730f1a Merge branch 'feature/list-iterator' into v0.8 2013-11-07 18:48:53 +01:00
Daniele Alessandri 9b8b362747 Implement PHP iterator for lists based on the LRANGE command.
This iterator tries to mimic the same behaviour of the cursor-based
iterators implemented upon the SCAN family of commands and offering
only limited guarantees on the returned elements, but uses LRANGE to
fetch items from a list incrementally.
2013-11-07 18:47:58 +01:00
Daniele Alessandri ee9e09aa07 Rename protected member. 2013-11-07 14:45:09 +01:00
Daniele Alessandri 66f98186f0 [tests] Do not check exception message for connection timeouts.
Given the IP address we use for this test (the standard IP assigned by
operating systems when unable to get one lease from a DHCP server), on
certain kernels or network stacks we can get different messages such
as "No route to host". Since we just need to make sure that our test
does not take more than 1 seconds abiding by the sub-second timeout,
we can just live with that.
2013-11-07 12:19:23 +01:00
Daniele Alessandri 463f2d655c Merge branch 'feature/scan-iterators' into v0.8 2013-11-07 12:10:43 +01:00
Daniele Alessandri b2db97d983 Tweak phpdocs for the abstract cursor-based iterator. 2013-11-07 12:10:04 +01:00
Daniele Alessandri 9da1648145 Mostly useless tweak for iterators example. 2013-11-07 12:10:04 +01:00
Daniele Alessandri 5ad00774e1 Rename for the last time all the iterator classes.
We are experimenting with a new approach at naming classes using less
redundant names by leveraging the containing namespace. The PHP "use"
directive is not limited to class names but can be used to import the
whole namespace, which means you can do something like this:

  use Predis\Collection\Iterator;
  // ...
  foreach (new Iterator\Keyspace($client) as $key) {
  	// ...
  }

Alternatively you can always rely on "use ... as ..." to import one of
the classes by giving it a more meaningful name in the context of the
root namespace:

  use Predis\Collection\Iterator\Keyspace as KeyspaceIterator;
  // ...
  foreach (new KeyspaceIterator($client) as $key) {
  	// ...
  }

In this specific case we chose to apply the -Key postfix to classes
iterating Redis keys to be more explicit about the fact that those
iterators does not work on local in-memory collections, but fetch
items from a key stored on a remote Redis server.
2013-11-07 12:09:59 +01:00
Daniele Alessandri ff004aeaaa Reenable skipped tests since bug in Redis has been fixed.
See commit antirez/redis@54a5a7dff8
2013-11-06 16:40:36 +01:00
Daniele Alessandri df2c9eb7ff Move Redis collections iterators in a different namespace.
The base iterator class is now known as RedisCollectionIterator and
we've also renamed a few methods to make their naming more generic.
2013-11-06 16:19:12 +01:00
Daniele Alessandri 4d8348158f [tests] Add more tests for SCAN-based iterators. 2013-11-04 18:01:29 +01:00
Daniele Alessandri 0ab0aa14fb [tests] Fix execution order of iterator methods.
We were testing iterators following a broken assumption about the
order in which the Zend engine executes the various methods during
the iteration.
2013-11-04 17:39:27 +01:00
Daniele Alessandri 85c978fdad Fix SCAN-based iterators following failing tests. 2013-11-04 16:53:56 +01:00
Daniele Alessandri b7f411a252 [ŧests] Add tests for new SCAN, SSCAN, ZSCAN and HSCAN iterators.
Right now we don't have integration tests since the returned values from
Redis may not always be predictable, but the current tests should suffice.
2013-11-04 16:53:24 +01:00
Daniele Alessandri 9866500ad6 Add example of iterators based on the various SCAN commands. 2013-11-03 15:05:05 +01:00
Daniele Alessandri 73ee5aabad Update CHANGELOG and README. 2013-11-03 14:21:31 +01:00
Daniele Alessandri e88938cdfd Implement PHP iterator based on the HSCAN command (Redis 2.8).
This iterator allows to perform full iterations over fields and values of a
hash by wrapping the incremental nature of HSCAN just like we did for SCAN:

  $client = new Predis\Client('tcp://127.0.0.1', ['profile' => '2.8']);
  $iterator = new Predis\Iterator\Scan\HashIterator($client, "hash_key");

  foreach ($iterator as $field => $value) {
      echo "$field => $value" . PHP_EOL;
  }

Being HSCAN closely related to SCAN, it is subject to the same behaviour,
see http://redis.io/commands/scan for reference.
2013-11-03 14:19:12 +01:00
Daniele Alessandri 93ae376618 Implement PHP iterator based on the ZSCAN command (Redis 2.8).
This iterator allows to perform full iterations over the members of a sorted
set by wrapping the incremental nature of ZSCAN just like we did for SCAN:

  $client = new Predis\Client('tcp://127.0.0.1', ['profile' => '2.8']);
  $iterator = new Predis\Iterator\Scan\SortedSetIterator($client, "zset_key");

  foreach ($iterator as $member => $rank) {
      echo "$rank => $member" . PHP_EOL;
  }

Being ZSCAN closely related to SCAN, it is subject to the same behaviour,
see http://redis.io/commands/scan for reference.

This iterator implementation returns the member as key and the rank as value
since the rank is a float value which would be truncated when transforming
the iteration to an array (e.g. using iterator_to_array()). Luckily PHP
preserves the insertion order for named arrays members still result sorted.
2013-11-03 14:19:07 +01:00
Daniele Alessandri 7028082b7f Implement PHP iterator based on the SSCAN command (Redis 2.8).
This iterator allows to perform full iterations over the members of a set
by wrapping the incremental nature of SSCAN just like we did for SCAN:

  $client = new Predis\Client('tcp://127.0.0.1', ['profile' => '2.8']);
  $iterator = new Predis\Iterator\Scan\SetIterator($client, "set_key");

  foreach ($iterator as $member) {
      echo $member . PHP_EOL;
  }

Being SSCAN closely related to SCAN, it is subject to the same behaviour,
see http://redis.io/commands/scan for reference.

Meh
2013-11-03 14:19:06 +01:00
Daniele Alessandri d09d8edc7d Extract base class for iterators based on the SCAN family of commands.
The iterators based on SCAN, SSCAN, ZSCAN and HSCAN will extend this abstract
class to share most of the logic which is common for all of the Redis commands
in the SCAN family.
2013-11-03 14:19:02 +01:00
Daniele Alessandri 70e42f8fca Implement PHP iterator based on the SCAN command (Redis 2.8).
This iterator allows to perform full iterations over the keyspace of a Redis
instance by wrapping the incremental nature of SCAN using an abstraction that
fits perfectly in userland code:

  $client = new Predis\Client('tcp://127.0.0.1', ['profile' => '2.8']);
  $iterator = new Predis\Iterator\Scan\KeyspaceIterator($client);

  foreach ($iterator as $key) {
      echo $key . PHP_EOL;
  }

Memory consumption during an iteration is limited because elements are fetched
incrementally, on the other hand SCAN gives limited guarantees about returned
elements since the underlying collection (the keyspace in this case) can change
during the whole iteration process. The most immediate drawback is that the same
element may be returned multiple times.

See http://redis.io/commands/scan to fully understand the inner workings,
and particularly the "Scan guarantees" paragraph.
2013-11-03 14:06:50 +01:00
Daniele Alessandri 695b6dd7b3 Update README. 2013-11-02 18:52:43 +01:00
Daniele Alessandri 6ce09a2a8b [tests] Handle failing test on OS X. 2013-11-02 18:50:24 +01:00
Daniele Alessandri bb03602326 Update CHANGELOG. 2013-11-02 18:19:32 +01:00
Daniele Alessandri 5c9adbacd7 [tests] Minor tweaks, no actual changes. 2013-11-02 18:19:32 +01:00
Daniele Alessandri 8533dbdb0b Deprecate Client::pubSub() in favor of Client::pubSubLoop().
Client::pubSub() still works like usual by returning a new pub/sub
context, but it is now considered an alias of Client::pubSubLoop().

This change is necessary in preparation for the next major version
of Predis where Client::pubSub() will be used for the new PUBSUB
command introduced in Redis 2.8.
2013-11-02 18:19:32 +01:00
Daniele Alessandri d78e1b6ab7 New command: HSCAN (Redis 2.8).
One test is currently marked as skipped because it makes
Redis crash when the specified MATCH pattern returns one
or more elements.

See http://redis.io/commands/scan for reference.
2013-11-02 18:19:32 +01:00
Daniele Alessandri d5aec78086 New command: ZSCAN (Redis 2.8).
One test is currently marked as skipped because it makes
Redis crash when the specified MATCH pattern returns one
or more elements.

See http://redis.io/commands/scan for reference.
2013-11-02 18:19:32 +01:00
Daniele Alessandri 0faae20737 New command: SSCAN (Redis 2.8).
See http://redis.io/commands/scan for reference.
2013-11-02 18:19:32 +01:00
Daniele Alessandri 4eb4960b63 New command: SCAN (Redis 2.8).
See http://redis.io/commands/scan for reference.
2013-11-02 18:19:32 +01:00
Daniele Alessandri 75c64b7f2f Add link to CONFIG REWRITE (Redis 2.8) in phpdoc for CONFIG command. 2013-11-02 18:19:32 +01:00
Daniele Alessandri d78780382a Promote Redis 2.8 to a standalone server profile.
We still need to populate this new server profile with the
recently added new commands for Redis 2.8.

The next development version has been set to Redis 3.0.
2013-11-02 18:19:32 +01:00
Daniele Alessandri 98aacd8d9a Back to development for the next patch release. 2013-11-02 18:19:31 +01:00
toretto 4824af1563 Added "Latest Stable Version" and "Total Downloads" Badges 2013-08-31 10:51:10 +02:00
Daniele Alessandri cb18d67b6a Update CHANGELOG and bump VERSION. 2013-07-27 11:13:54 +02:00
Daniele Alessandri 4576dc029f Add LICENSE and phpunit.xml.dist in package.ini for Pear.
This commit addresses issues #124 and #126. LICENSE will be installed
with the "doc" role while phpunit.xml.dist will be installed with the
"test" role.

[ci skip]
2013-06-17 11:16:07 +02:00
Daniele Alessandri aa458a1922 Merge pull request #123 from drealecs/doctype_fixes
some PHPDoc fixes in lib
2013-06-03 03:04:10 -07:00
Alexandru Patranescu 2d2930d24f some PHPDoc fixes in lib
and removed some unused imports also
2013-06-03 00:06:41 +03:00
Daniele Alessandri 7e0ec1a265 Update CHANGELOG.
[ci skip]
2013-05-31 11:52:47 +02:00
Daniele Alessandri 04e597ebba Merge remote-tracking branch 'origin/pr/116' into v0.8 2013-05-31 11:30:30 +02:00
Daniele Alessandri d681bff4ab Merge branch 'github/pr/114' into v0.8 2013-05-31 10:47:58 +02:00
Daniele Alessandri 3dea41aa66 Add tests for prefixed Predis\PubSub\DispatcherLoop. 2013-05-31 10:39:46 +02:00
Daniele Alessandri 69c66e157f Make callbacks properties protected in the DispatcherLoop class.
This makes easier to extend the class. The underlying pub/sub iterator
property is still private, but can be accessed via public getter method.
2013-05-31 10:21:21 +02:00
Daniele Alessandri dd50cfe47e Slightly rework original pull request. 2013-05-31 10:17:54 +02:00
Daniele Alessandri 8451146d38 Expose underlying client object from pub/sub iterators. 2013-05-31 10:17:08 +02:00
Thomas Orozco d45ce35923 Use a random IP when a host has several IPs.
Using gethostbyname, we will reuse the same (first) IP address for
each request. Here, we choose the IP we use randomly.

This is practical in a case where we have multiple redis read-only
slaves that can't invidually support the full application load, but are
accessible through a single hostname.
2013-05-11 14:08:51 -07:00
Eloi Poch 6cd124cc59 Fix DispatcherLoop error with client prefix keys
DispatcherLoop works properly if a client have configured a
prefix for the keys or not
2013-04-20 13:02:17 +02:00
Daniele Alessandri d6fa4a3292 Augment exception message with basic server details on connection error.
Providing a basic hint in the exception message about the server that caused
a connection exception could be useful especially with aggregated connections.

This is in response to issue #110.
2013-03-30 16:16:13 +01:00
Daniele Alessandri 5b771704d3 Handle DUMP and RESTORE with Predis\Connection\PredisCluster. 2013-03-30 14:38:49 +01:00
Daniele Alessandri 68d972a1b1 Merge remote-tracking branch 'origin/pr/111' into v0.8 2013-03-27 15:11:00 +01:00
marcosQuesada 4bf035918d setting medium timeout on slow tests 2013-03-23 00:57:36 +01:00
Daniele Alessandri 94c4e2ffee Fix handling of scripted commands with redis-cluster hash strategy.
A missing "use" directive was preventing the hash strategy to properly
use the specific methods of Predis\Command\ScriptedCommand, falling back
to analyzing the raw arguments array of the command.
2013-03-16 16:24:59 +01:00
Daniele Alessandri 6e2fd181f1 Make sure key prefixing is skipped when command has no arguments.
Actually this was already the case for certain commands, but some of them
was left unguarded for such cases. This commit also fixes #109.

The behaviour of silently skipping key prefixing when a command has no
arguments may change in the future so we added explicit tests as guards
for future changes. Predis\Command\Processor\KeyPrefixProcessor will
continue to skip key prefixing on empty arguments, regardless.
2013-03-16 16:00:33 +01:00
Daniele Alessandri 1c21fcc5b3 Fix strict warnings running command test generator script. 2013-03-16 13:23:46 +01:00
Daniele Alessandri 531261b0fc Add DUMP and RESTORE to the server profile for Redis 2.6.
Closes issue #108.
2013-03-16 13:21:24 +01:00
Daniele Alessandri 7434a9bd0e Add the WITHSCORES modifier only when option value is true.
Fixes issue #107.
2013-03-16 12:37:00 +01:00
Daniele Alessandri c4123ce7ce Update CHANGELOG. 2013-03-16 12:16:52 +01:00
Daniele Alessandri 536992e277 Merge remote-tracking branch 'nitper/cluster_hmset' into v0.8 2013-03-16 12:11:44 +01:00
Daniele Alessandri 5a735aa670 Back to development for the next patch release. 2013-03-16 12:11:37 +01:00
nitper 15f2776d77 update cluster tests 2013-03-11 23:08:10 +00:00
nitper dc1103582e allow HMSET in clusters 2013-03-11 22:50:03 +00:00
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
Daniele Alessandri 0a622aee31 Bump VERSION and update CHANGELOG.
Guess what time is it? Yeah, once again... it is release time!
2012-10-23 01:46:05 +02:00
Daniele Alessandri 040dfebd9c Minimum required version of PHP is 5.3.2. 2012-10-23 01:35:22 +02:00
Daniele Alessandri 0e4604a9bb Tweak README. 2012-10-23 01:34:48 +02:00
Daniele Alessandri deb64c295a Add notes about coding standards in CONTRIBUTING.
[ci skip]
2012-10-12 22:43:45 +02:00
Daniele Alessandri aac06a4cd8 Move contributing section from README to CONTRIBUTING.
[ci skip]
2012-10-12 22:32:21 +02:00
Daniele Alessandri dfb5cf2c1b Remove TODO.
We are going to open a new issue on GitHub for a decent
documentation of the library.

[ci skip]
2012-10-12 21:57:50 +02:00
Daniele Alessandri a1024bba53 Merge FAQ files into a single markdown document.
[ci skip]
2012-10-12 21:51:54 +02:00
Daniele Alessandri f357cafb14 Update CHANGELOG.
[ci skip]
2012-10-12 21:39:05 +02:00
Daniele Alessandri 2b4cf80d90 Merge remote-tracking branch 'origin/pr/84' 2012-09-18 18:05:11 +02:00
Ondrej -onovy- Novy 6cd1f03834 Fixed typos. 2012-09-18 17:43:29 +02:00
Daniele Alessandri 732d7f96d1 Extract a strategy class from the replication connection. 2012-09-12 13:09:52 +02:00
Daniele Alessandri b4e1ab106c Update README.
[ci skip]
2012-09-10 14:17:00 +02:00
Daniele Alessandri 5a2c61c02b Update CHANGELOG.
[ci skip]
2012-09-04 18:32:16 +02:00
Daniele Alessandri 1b9e10bdd8 Move command response parsing to the client class.
Connection classes should just handle, convert and return simple Redis
types while parsing and transforming structured replies should be done
by consumers (see Predis\Client or Predis\Transaction\MultiExecContext).

This actually makes more sense considering that parsing a complex response
with the associated command parser may require different actions. As an
example, the result of EXEC is a multibulk that holds the actual responses,
so we really need to parse each one of its elements and we should also
make sure that iterable multibulks are consumed. We already did that
previously, but it was weird knowing that command parsers were applied
by the connection class.

This also moves some duplicated logic away from each connection class
implementation which is a nice bonus.
2012-09-04 14:53:05 +02:00
Daniele Alessandri 37e2b546b0 Fix MULTI / EXEC pipeline executor when using iterable multibulks. 2012-09-04 13:19:26 +02:00
Daniele Alessandri f163e1221b Describe scripted commands (EVAL / EVALSHA) in the README.
[ci skip]
2012-09-04 13:19:18 +02:00
Daniele Alessandri 1bb2ab95ef Use the proper service name.
[ci skip]
2012-09-03 19:52:25 +02:00
Daniele Alessandri ea18afc820 Update the configuration file for Travis CI.
Travis is upgrading their base image and will disable most of
the services at boot (Redis is one of these).
2012-09-03 14:39:11 +02:00
Daniele Alessandri baa91da35b Merge remote-tracking branch 'origin/pr/79' 2012-09-03 14:37:46 +02:00
Pascal Borreli 6d85a81da4 Fixed typo 2012-09-03 11:50:26 +00:00
Daniele Alessandri f913e81b9f Add a reference to Predis\Async in the README.
I think we can indeed start promoting Predis\Async now that it has
reached a fairly stable state (in terms of API and functionalites)
despite still being considered experimental.

[ci skip]
2012-09-03 12:27:45 +02:00
Daniele Alessandri 003b3d180d Add missing test for Predis\Client::getClientFor(). 2012-08-24 12:13:37 +02:00
Daniele Alessandri a6e1ac11d0 Implement separate Predis\Client::getConnectionById().
This method is not part of Predis\ClientInterface because it is being
considered mostly an helper / utility method.
2012-08-24 12:07:56 +02:00
Daniele Alessandri be46ab813c Remove useless default NULL value in method arguments. 2012-08-24 11:55:19 +02:00
Daniele Alessandri 6918de6e4b Restore 0x notation for CRC16 hash generator. 2012-08-24 11:51:00 +02:00
Daniele Alessandri cfa97dd9f5 Fix cs. 2012-08-24 11:49:43 +02:00
Daniele Alessandri 1e8f790ee4 Remove leftover method. 2012-08-24 11:48:42 +02:00
Daniele Alessandri c655affe5f Add Predis\Command\CommandInterface::setRawArguments(). 2012-08-24 11:47:44 +02:00
Daniele Alessandri 457fd6076d CS fixes.
[ci skip]
2012-08-22 11:54:49 +02:00
Daniele Alessandri 282287765b Fix phpdoc comment.
[ci skip]
2012-08-22 11:53:16 +02:00
Daniele Alessandri 00d6160549 Enable code coverage report on Travis CI. 2012-08-22 11:46:31 +02:00
Daniele Alessandri 576163724c Add an example for multibulk reply iterators.
[ci skip]
2012-08-21 18:10:49 +02:00
Daniele Alessandri 9df9dbdedc Promote multibulk response iterators as Predis response objects.
Multibulk response iterators will not be passed anymore to the response
parser method of the command that generated the response. Pipeline and
transaction abstractions still consume interators returned as response
items.
2012-08-21 17:15:14 +02:00
Daniele Alessandri 8d7f5099c4 Add tests for Predis\Connection\RedisCluster (redis-cluster). 2012-08-21 15:24:06 +02:00
Daniele Alessandri e8daf45269 Improve Predis\Pipeline\MultiExecExecutor. 2012-08-19 15:00:45 +02:00
Daniele Alessandri 8567b05459 Specify the server profile in constructor of the connection factory.
When no server profile is specified, the connection factory will not push
any initialization command to the newly created connection object.

This change is mainly useful when using redis-cluster and also makes it
possible to easily inject commands such as "SCRIPT LOAD" at initialization
time by grouping everything into one common place simply by extending the
connection factory class.
2012-08-19 13:57:58 +02:00
Daniele Alessandri 6e76ea6c7e Add tests for classes in the Predis\Protocol\Text namespace.
This was the only namespace containing classes with no tests.
2012-08-02 21:42:11 +02:00
Daniele Alessandri e774a301c4 Add tests for Predis\Connection\ComposableStreamConnection. 2012-08-02 17:25:13 +02:00
Daniele Alessandri 2b85824d74 Apply minor fixes to README.
[ci skip]
2012-08-02 16:15:31 +02:00
Daniele Alessandri 81422b9992 Partially rewrite README.
[ci skip]
2012-08-02 16:10:58 +02:00
Daniele Alessandri c230b6e831 Update TODO.
[ci skip]
2012-08-02 16:10:47 +02:00
Daniele Alessandri 69d4c013f7 Minor change (mostly micro-optimization). 2012-08-02 12:42:53 +02:00
Daniele Alessandri 1ac9cc9c71 Support EVAL and EVALSHA with redis-cluster.
We extract the keys from commands using the second argument of EVAL /
EVALSHA which specifies the number of arguments that must be treated
as keys (used to populate the KEYS table in the Lua script) and then
we check if there is only one key since redis-cluster right now does
not support multi-keys requests.

Our scripted command abstraction is also supported.
2012-08-02 12:08:56 +02:00
Daniele Alessandri b2f59a0c63 Support EVAL and EVALSHA with client-side sharding (Predis cluster).
We extract the keys from commands using the second argument of EVAL /
EVALSHA which specifies the number of arguments that must be treated
as keys (used to populate the KEYS table in the Lua script) and then
we check if all the keys generate the same hash using the usual method.

Our scripted command abstraction is also supported.

It comes without saying that accessing or setting keys from within the
Lua script is something that might not work as expected, so you should
be careful when using EVAL and EVALSHA in the context of client-side
sharding.
2012-08-02 11:38:20 +02:00
Daniele Alessandri 93971bcb45 Merge branch 'command-hash-strategy'
Conflicts:
	lib/Predis/Connection/RedisCluster.php
2012-07-30 20:29:13 +02:00
Daniele Alessandri e54bc31212 Create Predis\Cluster namespace and move related classes / interfaces. 2012-07-30 20:10:14 +02:00
Daniele Alessandri b9eddae96f Fix and optimize node guessing algorithm. 2012-07-30 16:40:53 +02:00
Daniele Alessandri 7683f97ff2 Move the hash generator inside the command hash strategy class.
This should make things a tiny bit faster (we are speaking about micro
optimizations anyway) but more  importantly it results in a better
encapsulation.
2012-07-30 15:42:45 +02:00
Daniele Alessandri f4f9d02551 Improve the slot-guessing algorithm.
This change makes it almost useless to pre-assign slots using connection
parameters, but the functionality will be kept for special cases.
2012-07-30 12:55:41 +02:00
Daniele Alessandri bacdb7ff62 Remove useless "use" directives. 2012-07-29 22:30:31 +02:00
Daniele Alessandri e0c6ad58be Optimize lazy initialization of the slots map.
This is twice faster than before but still kind of slow compared to the
initialization of a cluster with no slots pre-assigned to connections.
2012-07-29 22:07:00 +02:00
Daniele Alessandri febd99421f Pre-associate connections to slot ranges using connection parameters.
This makes it possible to easily assign a connection to a slot range
using the key "slots" in connection parameters like in the following
example:

    $parameters = array(
        'tcp://127.0.0.1:6379?slots=0-1364',
        'tcp://127.0.0.1:6380?slots=1365-2729',
        'tcp://127.0.0.1:6381?slots=2730-4095',
    );

    $options = array('cluster' => 'redis');
    $client = new Predis\Client($parameters, $options);

It is possible to get the full list of slot ranges for each node of
the cluster using redis-cli connected to one of the nodes:

    ./redis-cli -h 127.0.0.1 -p 6379 CLUSTER NODES

The last column in the resulting output contains the slots assigned
to each instance participating to the redis cluster.
2012-07-29 19:38:58 +02:00
Daniele Alessandri 8559c2cb30 Make it possible to pre-associate slots to a connection.
This is useful to avoid guessing the correct server instance at runtime
thus reducing the overhead of a guess-and-try approach since relying on
-ASK or -MOVED responses returned by a node is a costly operation.

For now, you can pre-associate slots using the "cluster" client option:

    $parameters = array(
        'tcp://127.0.0.1:6379',
        'tcp://127.0.0.1:6380',
        'tcp://127.0.0.1:6381',
    );

    $options = array(
        'cluster' => function ($options) {
            $cluster = new Predis\Connection\RedisCluster();

            $cluster->setSlots(0,    1364, '127.0.0.1:6379');
            $cluster->setSlots(1365, 2729, '127.0.0.1:6380');
            $cluster->setSlots(2730, 4095, '127.0.0.1:6381');

            return $cluster;
        },
    );

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

In the future we will make the slots configuration easier by fetching
the configuration from a node using the command "CLUSTER NODES" or by
pre-computing the slices of slots associated to a node when adding it
to the cluster connection object.
2012-07-29 14:14:11 +02:00
Daniele Alessandri 2ba54a0e1c Use specific command hash strategy for redis-cluster.
This is needed because redis-cluster does not support the same commands
or operations that can be performed with our client-side managed predis
cluster.

For example redis-cluster does not support key tagging (that is, parts
of a key enclosed by {...} to hash only that specific part of a key)
and multiple-key operations suchs as MGET, MSET, SDIFF, SUNION or SINTER.
Some multiple-key operations can be performed anyway if the command has
only one key (e.g. "MGET foo" and "MSET foo bar" will not fail).
2012-07-29 13:48:16 +02:00
Daniele Alessandri 024a31d571 Fix wrong binary notation.
Cannot use 0b yet since it is PHP 5.4 only.
2012-07-29 13:33:06 +02:00
Daniele Alessandri d0572b0efc Rename command hash strategy cass used by Predis cluster. 2012-07-29 12:07:48 +02:00
Daniele Alessandri 0f80cb55bb Add suggested extensions to composer.json.
[ci skip]
2012-07-27 16:58:51 +02:00
Daniele Alessandri 55a3fd65a2 More code styling changes. 2012-07-27 12:23:25 +02:00
Daniele Alessandri 6c732c737b Order "use" directives by namespace depth and name.
This is not really useful per se, but at least we use a fixed rule.
2012-07-26 23:04:20 +02:00
Daniele Alessandri 559853bdd5 Move some classes and interfaces in other namespaces. 2012-07-26 22:37:31 +02:00
Daniele Alessandri 28e1e7ad25 Reword some exception messages. 2012-07-26 16:31:09 +02:00
Daniele Alessandri 98112fdb39 Use instance methods to cast values for connection parameters. 2012-07-26 15:49:42 +02:00
Daniele Alessandri 0e8d978348 Minor code styling changes in client class.
[ci skip]
2012-07-26 15:18:09 +02:00
Daniele Alessandri 469f3cc68b Do not accept string or Predis\Profile\ServerProfileInstance in constructor.
To specify a server profile you can just set the "prefix" option in the
client options array passed to the constructor of Predis\Client.
2012-07-26 14:36:01 +02:00
Daniele Alessandri 36f8210864 Rename variable and field. 2012-07-26 12:18:28 +02:00
Daniele Alessandri 81be513eaa Change signature of Predis\Pipeline\PipelineContext constructor.
Now the second argument is a Predis\Pipeline\PipelineExecutorInterface.
2012-07-26 11:33:24 +02:00
Daniele Alessandri 4b73afc316 Add missing tests for Predis\Command\Hash\CommandHashStrategy. 2012-07-25 22:41:14 +02:00
Daniele Alessandri fd6a14d80b Make it possible to set/unset command handlers in CommandHashStrategy. 2012-07-25 22:22:55 +02:00
Daniele Alessandri f2d92f1e29 Expose the command hash strategy from the predis cluster class.
This will be useful to add, remove or change the handler used to
extract, validate and hash the keys of each command instance.
2012-07-25 21:25:50 +02:00
Daniele Alessandri c3cafaa9a6 Remove useless helpers. 2012-07-25 21:18:58 +02:00
Daniele Alessandri 89d6c0e9b6 Extract common PubSub handling logic into a separate abstract class. 2012-07-25 21:07:04 +02:00
Daniele Alessandri 9f51263136 Fix internal flags for MULTI / EXEC and PUB/SUB contexts.
Do not ask me why I was using 0x (the prefix for hexadecimal notation)
when what we really need here is 0b (the prefix for binary notation).
Really, everything was working by sheer coincidence.

Since 0b has been added in PHP 5.4, we rely on plain decimal numbers
to define our constants for now.
2012-07-21 13:48:32 +02:00
Daniele Alessandri a75e52cf5a Update CHANGELOG.
[ci skip]
2012-06-01 12:14:50 +02:00
Irakli Nadareishvili b865cee1f8 Make autoloader act nicer to autoloaders that may be running in parallel. 2012-05-27 21:09:38 +02:00
Daniele Alessandri 1f0be85a3d Add initial support for Redis cluster (Redis v3.0).
This implementation is capable of handling "ASK" and "MOVED" replies
returned by Redis when one of the nodes asks the client for temporary
or permanent redirects of a slot to a different node.

Performances look almost on par with our client-side sharding solution
and everything looks already relatively stable.

Tests are still missing.
2012-05-13 11:13:33 +02:00
Daniele Alessandri 2578e154d4 New commands: BITOP, BITCOUNT (Redis v2.6-dev). 2012-05-17 17:17:35 +02:00
Daniele Alessandri 81da6d2553 TIME can be considered a read-only command in MASTER / SLAVE. 2012-05-17 16:48:08 +02:00
Daniele Alessandri 188eaf035b Fix comment in package.ini.
[ci skip]
2012-05-17 09:53:45 +02:00
Daniele Alessandri 6638776d85 [test] Add tests for pipeline executors. 2012-05-17 09:43:48 +02:00
Daniele Alessandri 511fe0bbab Rewrite the logic used to handle Redis commands with clustering.
The reason for this change is to support the upcoming redis-cluster since
it has different behaviors compared to the client-side cluster implementation
provided by Predis. For example redis-cluster will not support key tags or
certain operations currently available with our client-side implementation.
2012-05-16 16:50:37 +02:00
Daniele Alessandri 56a3e327fc Add a pipeline executor that internally uses MULTI / EXEC.
The actual pipeline is wrapped between MULTI and EXEC to ensure that all the
commands are correctly sent and executed on the server. The whole pipeline is
discarded should it fail at a certain point during execution.

We do not use our Predis\Transaction\MultiExecContext abstraction internally
but rely on raw commands since this new executor does not really need full
support for Redis transactions.
2012-05-15 15:59:23 +02:00
Daniele Alessandri 63cf5048a7 Slightly optimize internals of MULTI / EXEC transactions.
We start using SplQueue to enqueue the commands executed inside a MULTI /
EXEC context so that we can dequeue them for parsing their replies later.

This change does not affect performances but makes the code more clean.
2012-05-15 15:03:42 +02:00
Daniele Alessandri 177d7e1c6a Fix CHANGELOG. 2012-05-15 12:44:49 +02:00
Daniele Alessandri 8a84f8b6cf Optimize command pipelines.
Using SplQueue instead of a plain PHP array to queue command instance in
the pipeline is faster and makes the underlying implementation definitely
more clean.

This change is not going to be backported to Predis v0.7 due to changes
in Predis\Pipeline\PipelineContextInterface.
2012-05-15 12:42:26 +02:00
Daniele Alessandri 09f15a9cb9 Fix README. 2012-05-15 10:38:53 +02:00
Daniele Alessandri 2b4885abc3 Update README.
[ci skip]
2012-04-30 19:56:08 +02:00
Daniele Alessandri 649e71c0ab Clean up leftovers. 2012-04-30 17:07:19 +02:00
Daniele Alessandri 14ac381aad Fix phpdoc. 2012-04-30 17:06:42 +02:00
Daniele Alessandri 78360ea27b Set raw arguments when switching from scripted command to EVAL. 2012-04-28 18:04:54 +02:00
Daniele Alessandri 214b7d2dc9 Remove almost useless method. 2012-04-28 09:23:41 +02:00
Daniele Alessandri 2d0b30dac5 Client handles -NOSCRIPT falling back to EVAL for scripted commands.
Now Predis\Command\ScriptedCommand uses EVALSHA instead of EVAL internally
so that performances should be better since the client do not resend the
Lua script body on each call.

Plain EVALSHA commands are not affected and will return or throw the error.
2012-04-28 08:56:11 +02:00
Daniele Alessandri 75afe831b0 Fix Predis\Command\ServerEvalSHA::getScriptHash().
"Predis\Command\ServerEvalSHA::getScriptHash()" was calculating the hash
while it just needs to return the first argument of the command.
2012-04-28 01:30:21 +02:00
Daniele Alessandri c36e6fd900 Pass also the command that generated the server error. 2012-04-27 23:59:51 +02:00
Daniele Alessandri 743ccc39ae Remove the "safe" pipeline option.
Just let users specify the appropriate pipeline executor.
2012-04-27 23:36:12 +02:00
Daniele Alessandri 41c29bed4e Change how Redis errors such as -ERR replies generates exceptions.
The "throw_errors" connection parameter has been removed and replaced by the
new "exceptions" client option since exceptions on -ERR replies returned by
Redis are not generated by connection classes anymore but are thrown by the
client class and other abstractions such as pipeline contexts.

This change does not affect much people using the Predis\Client class (aside
from the different configuration) but gives much more flexibility to those
building their own pieces of code around the internal classes of Predis.
2012-04-27 23:29:41 +02:00
Daniele Alessandri 90f37f8698 Do not assume that the position of a MultiBulkResponse iterator is 0. 2012-04-27 16:13:15 +02:00
Daniele Alessandri dc889be2a2 Switch to master inside executor when pipelining in replication mode.
The actual code to switch to master has been implemented only in the
standard and the fire-and-forget executor classes since it does not
make sense to have it in special executors used exclusively when in
client-side clustering mode.
2012-04-25 13:02:33 +02:00
Daniele Alessandri ca7bd8149f Scripting abstraction can use negatives to calculate number of keys.
With a negative number Predis will count from the end of the arguments list
to calculate the actual number of keys that will be interpreted as elements
for `KEYS` by the underlying `EVAL` command.
2012-04-25 11:35:06 +02:00
Daniele Alessandri 9426d78b90 Fix example for custom distribution strategy.
See ISSUE #63 for details.
2012-04-13 11:37:03 +02:00
Daniele Alessandri 6b6c91cd15 Update CHANGELOG. 2012-04-01 10:15:31 +02:00
Daniele Alessandri b511911ddb Update README.
[ci skip]
2012-03-31 12:00:10 +02:00
Daniele Alessandri eb450ae0b4 Make 2.6 the default server profile for the test suite too.
We leave 2.4 for Travis CI since their CI environment uses Redis 2.4.

[ci skip]
2012-03-31 11:49:46 +02:00
Daniele Alessandri ca033d15a9 Add a couple of paragraphs in the FAQ. 2012-03-31 11:45:32 +02:00
Daniele Alessandri 9443da1948 Update TODO. 2012-03-31 11:42:25 +02:00
Daniele Alessandri 298dc87613 Update CHANGELOG. 2012-03-31 11:41:43 +02:00
Daniele Alessandri 92697aa075 Make 2.6 the default server profile. 2012-03-31 11:39:42 +02:00
Daniele Alessandri 7c393cbc09 Apparently the next major version or Redis will be 2.8. 2012-03-31 11:36:35 +02:00
Daniele Alessandri 5b6e4dc3dd Make it possible to prepend the autoloader. 2012-03-19 11:31:55 +01:00
Daniele Alessandri b37d072b68 Make Predis\Autoloader faster. 2012-03-18 18:01:01 +01:00
Daniele Alessandri 608c48146f Fix helper method for invalid options in connection classes.
This fix is needed since connection parameters do not respond to __toString()
anymore after recent changes.
2012-03-18 15:23:25 +01:00
Daniele Alessandri bcba9e1009 Change how we check for required extensions in connection classes. 2012-03-18 15:22:24 +01:00
Daniele Alessandri 64a84d9020 Add notes about the parameters supported by each connection class. 2012-03-18 15:07:18 +01:00
Daniele Alessandri a3b4c2244a Remove some overkill stuff from Predis\ConnectionParameters.
It was basically some crazy and almost useless stuff I'd say.
2012-03-18 14:53:21 +01:00
Daniele Alessandri 4805a048ed Add missing tests for Predis\Profile\ServerProfile::defineCommand(). 2012-03-18 13:00:53 +01:00
Daniele Alessandri c394458b80 Remove an almost useless method from Predis\Profile\ServerProfile. 2012-03-18 13:00:28 +01:00
Daniele Alessandri 51d0b58cd3 Add new interfaces used to abstract the main client class and contexts.
Now developers can pass a client object or an executable context (see
Predis\Transaction\MultiExecContext or Predis\Pipeline\PipelineContext)
interchangeably as parameters to their methods using the new interface
Predis\BasicCliantInterface.

These new interfaces will also allow us to easily create new client
classes aside from the standard Predis\Client one.
2012-03-18 11:05:33 +01:00
Daniele Alessandri 890ace8565 Rename certain connection parameters usig shorter names.
- `connection_async` is now `async_connect`
  - `connection_timeout` is now `timeout`
  - `connection_persistent` is now `persistent`
2012-03-16 19:33:17 +01:00
Daniele Alessandri 8ba432e8f7 Move list of renamed namespaces/interfaces/classes to a separate file. 2012-03-16 19:20:15 +01:00
Daniele Alessandri d61bb3af07 Update CHANGELOG. 2012-03-11 17:50:48 +01:00
Daniele Alessandri da381fffa9 Promote 2.6 as an independent server profile for Redis 2.6.
The development server profile now targets Redis 3.0.
2012-03-11 17:49:18 +01:00
Daniele Alessandri 7b01bc0644 Fix MONITOR and Predis\MonitorContext with Redis 2.6.
Starting with 2.6, Redis uses a slightly different format for the payload
returned by MONITOR for each command. Predis can now adapt to this difference
and returns a new `client` field in the payload object. This new field is
always defined but is set to NULL when connected to Redis < 2.6.

Please note that Redis 2.6 now does not echo the `MONITOR` command as the
first payload when opening a new MONITOR context.
2012-03-11 17:39:57 +01:00
Daniele Alessandri 7893fd1faa New command: TIME (Redis v2.6-dev). 2012-03-11 17:34:06 +01:00
Daniele Alessandri 785670dcf7 Enable integration tests for Redis commands on Travis CI.
This was not possible before since our test suite requires Redis >= 2.4 but
their environment was running Redis 2.2 until a month ago or so.
2012-02-25 10:58:00 +01:00
Daniele Alessandri ec723fd4a9 Remove the ability to execute a command on all the nodes of a cluster.
The reason for this change is that not every cluster implementation can support
this behaviour, think of Redis cluster for example. We moved the implementation
of this method in Predis\Connection\PredisCluster since it can still be useful.
2012-02-07 15:40:19 +01:00
Daniele Alessandri b67af29eb6 Make cluster and replication classes extend from a common interface. 2012-02-07 11:42:34 +01:00
Daniele Alessandri dc14c29676 Rename certain namespaces, interfaces and classes.
Now we follow a Symfony2-like naming convention for namespaces, interfaces
and classes sticking with one clear rule.

- Renamed namespaces:

  - Predis\Network
  - Predis\Profiles
  - Predis\Iterators
  - Predis\Options
  - Predis\Commands
  - Predis\Commands\Processors

- Renamed interfaces:

  - Predis\IReplyObject
  - Predis\IRedisServerError
  - Predis\IConnectionFactory
  - Predis\IConnectionParameters
  - Predis\Options\IOption
  - Predis\Options\IClientOptions
  - Predis\Profile\IServerProfile
  - Predis\Pipeline\IPipelineExecutor
  - Predis\Distribution\INodeKeyGenerator
  - Predis\Distribution\IDistributionStrategy
  - Predis\Protocol\IProtocolProcessor
  - Predis\Protocol\IResponseReader
  - Predis\Protocol\IResponseHandler
  - Predis\Protocol\ICommandSerializer
  - Predis\Protocol\IComposableProtocolProcessor
  - Predis\Network\IConnection
  - Predis\Network\IConnectionSingle
  - Predis\Network\IConnectionComposable
  - Predis\Network\IConnectionCluster
  - Predis\Network\IConnectionReplication
  - Predis\Commands\ICommand
  - Predis\Commands\IPrefixable
  - Predis\Command\Processor\ICommandProcessor
  - Predis\Command\Processor\ICommandProcessorChain
  - Predis\Command\Processor\IProcessingSupport

- Renamed Classes:

  - Predis\Commands\Command
  - Predis\Network\ConnectionBase

- Classes moved to different namespaces:

  - Predis\MonitorContext

Meh
2012-01-31 18:19:18 +01:00
Daniele Alessandri ce625ca902 Start development of Predis v0.8. 2012-01-15 12:04:17 +01:00
Daniele Alessandri 46d293a716 Fix phpdocs.
[ci skip]
2012-01-14 23:35:39 +01:00
Daniele Alessandri fcd41a4787 Fix CHANGELOG. 2012-01-14 23:33:39 +01:00
Daniele Alessandri 8dc9450f39 Bump year in LICENSE.
[ci skip]
2012-01-14 15:29:12 +01:00
Daniele Alessandri 6ad522f492 Update CHANGELOG. 2012-01-14 15:11:27 +01:00
Daniele Alessandri 317fb6c398 Make Predis\Network\MasterSlaveReplication serializable.
Since PHP cannot serialize closures we switched to a private method to check
if a SORT command is a read-only operation, but closures are still supported
even if they will make the connection unserializable.
2012-01-14 14:53:52 +01:00
Daniele Alessandri 93163bdfb3 Make it possible to serialize and unserialize connection instances. 2012-01-14 14:53:47 +01:00
Daniele Alessandri 626520b836 Add specific server profile alias for Redis 2.6. 2012-01-14 13:01:38 +01:00
Daniele Alessandri aaea628c55 Back to development for the next patch release. 2012-01-14 12:51:01 +01:00
Daniele Alessandri 135fd5004b Bump VERSION and update CHANGELOG. 2011-12-27 16:19:23 +01:00
Daniele Alessandri c25a815317 Update README. 2011-12-27 15:57:12 +01:00
Daniele Alessandri 39a9fc2ddd Add a new example for complex replication configuration. 2011-12-27 12:34:44 +01:00
Daniele Alessandri f5bca54bba Fix CHANGELOG. 2011-12-27 10:40:29 +01:00
Daniele Alessandri 364aa180f9 Use Predis\NotSupportedException for unsupported connection parameters. 2011-12-27 10:38:06 +01:00
Daniele Alessandri 4da506cd9e Track changes in Onion. 2011-12-24 19:03:13 +01:00
Daniele Alessandri ef5034dc72 Update CHANGELOG.
[ci skip]
2011-12-24 12:17:03 +01:00
Daniele Alessandri 994f045a4a Add a method to execute instances of Redis commands in a transaction. 2011-12-24 12:08:34 +01:00
Daniele Alessandri 8cd8d4e580 Fix broken scripted commands when all arguments are considered keys. 2011-12-21 23:14:46 +01:00
Daniele Alessandri 22f1aafae8 Fix example of replication. 2011-12-21 21:41:51 +01:00
Daniele Alessandri c47b009b69 Update documentation for master / slave replication. 2011-12-21 21:33:30 +01:00
Daniele Alessandri 2cff6f0886 Merge branch 'replication' into v0.7
See ISSUE #21 for history and details about transparent master / slave
replication support in Predis.
2011-12-21 21:25:56 +01:00
Daniele Alessandri 63cb150fc2 Switch to the master server right before the execution of a pipeline.
This is actually a temporary solution since we should be using a dedicated
pipeline executor to handle the master/slave replication connection.

Ideally we should stick with a slave if no write operations are present in
the buffer, but forcing a switch to the master connection is the easiest
solution for now and users can still create a new client instance from one
of the slaves when they are sure that their pipelines contain read-only
operations.
2011-12-21 21:15:46 +01:00
Daniele Alessandri 466c666547 Add example for master / slave replication. 2011-12-21 21:01:48 +01:00
Daniele Alessandri 8d5c794efe [tests] Add more tests for Predis\Network\MasterSlaveReplication. 2011-12-21 18:34:53 +01:00
Daniele Alessandri e3fba5d4b7 [tests] Add more tests for Predis\Network\MasterSlaveReplication. 2011-12-21 16:50:03 +01:00
Daniele Alessandri a8400f2ed8 Rename default replication class to Predis\Network\MasterSlaveReplication. 2011-12-21 16:50:03 +01:00
Daniele Alessandri 44629a33db [tests] Add tests for Predis\Network\PredisReplication. 2011-12-21 16:50:01 +01:00
Daniele Alessandri d38f164d3f Minor fixes in documentation. 2011-12-21 11:26:26 +01:00
Daniele Alessandri 9bc5685959 Make the "profile" option accept a callable object as initializer.
This can be useful to set up the profile with additional commands,
e.g. when injecting new "virtual" commands based upon the EVAL and
the Predis\Commands\ScriptedCommand class.
2011-12-21 10:56:10 +01:00
Daniele Alessandri 565690a5b1 Disallow certain commands when in replication mode.
Some of these commands do not actually pose any threat, but their replies can
be misleading since users do not know on which server they are connected to.
We prefer to make things explicit so users can get a new client object out of
a specific connection in the replication pool to issue these commands.
2011-12-20 12:10:06 +01:00
Daniele Alessandri bdbbe18e6c Add the ability to get a connection by alias from aggregated connections.
Previously it was possible to create a new instance of Predis\Client using
the alias of a single connection in a cluster of connections. Now we added
the ability to do this also when using master/slave replication.
2011-12-18 15:07:21 +01:00
Daniele Alessandri 78027f0498 Use our own channel to distribute Predis via PEAR instead of PearHub.
The new PEAR channel is self-hosted using Pirum.
2011-12-18 13:28:45 +01:00
Daniele Alessandri 104cd1eae7 Use Onion to build PEAR packages of Predis.
Since Pearhub is currently broken (unfortunately the project does not seem to
be actively mantained anymore) but we still want to have PEAR packages for our
library, we need to automate the creation of a package.xml definition file to
build the actual packages that will be uploaded on a self-hosted PEAR channel.

Onion, while still in its alpha stage right now, works perfectly fine for our
needs and its own package.ini file looks simple enough to edit and maintain.

See http://c9s.github.com/Onion for more details about Onion and thank @c9s
for making it available.
2011-12-18 12:50:10 +01:00
Daniele Alessandri 5148ce16c6 Add the ability to mark specific commands or scripts for EVAL as read-only.
As suggested by @Seldaek in ISSUE #21, it would be desiderable to have the
ability to mark certain scripts for EVAL (and EVALSHA) as read-only to avoid
switching to the master server when this is not really needed.

When marking a script as read-only, only its SHA1 hash is saved to use less
memory and to make this work transparently for both EVAL and EVALSHA.
2011-12-17 10:58:37 +01:00
Daniele Alessandri 4b9ab0bcce Revert to development version for the next patch release.
Forgot to do this early. [ci skip]
2011-12-16 22:04:21 +01:00
Daniele Alessandri 39ed8e3e8f Fix wrong usage of single-quotes for a string expecting interpolation. 2011-12-16 20:36:01 +01:00
Daniele Alessandri 6f4347010d Add new "replication" client option for master / slave replication.
When the "replication" option is specified and evaluates to true, the array of
connection parameters passed to the client constructor os be used to initialize
a new instance of Predis\Network\IConnectionReplication. This option can also
accept a callable object used as an initializer or the fully-qualified name of
a class.
2011-12-16 19:30:07 +01:00
Daniele Alessandri c43278eceb Implement support for master / slave replication configurations.
We used a new kind of virtual connection that, just like with cluster, groups
multiple connection objects and handles the logic needed to switch among them
depending on the kind of operation performed by commands..

Our default implementation starts by picking up a random slave and switches to
the master as soon as a command performing a write operation against a key is
detected. The master server will then be used for all the subsequent requests
unless a manual switch to a different connection object is performed.

Redis transactions are always performed on the master server, which means that
the switch to master is done as soon as the client issues a WATCH, MULTI or
any other command related to transactions.

See also https://github.com/nrk/predis/issues/21 for more details.
2011-12-16 16:28:36 +01:00
627 changed files with 30776 additions and 15513 deletions
+8
View File
@@ -0,0 +1,8 @@
* text=auto
/tests export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/phpunit.xml.dist export-ignore
/phpunit.xml.travisci export-ignore
+5
View File
@@ -1,2 +1,7 @@
*.tgz
*.phar
phpunit.xml
package.xml
composer.lock
experiments/
vendor/
+18 -4
View File
@@ -2,8 +2,22 @@ language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- hhvm
branches:
only:
- master
- v0.7
script: phpunit -c phpunit.xml.travisci
except:
- v0.5
- v0.6
- php5.2_backport
- documentation
services: redis-server
before_script:
- composer self-update
- composer install --no-interaction --prefer-source --dev
script:
- vendor/bin/phpunit -c phpunit.xml.travisci
matrix:
allow_failures:
- php: hhvm
fast_finish: true
+481 -15
View File
@@ -1,5 +1,471 @@
v1.0.0 (2014-08-01)
================================================================================
- Switched to PSR-4 for autoloading.
- The default server profile for Redis is `3.0`.
- Removed server profile for Redis 1.2.
- Added `SENTINEL` to the profile for Redis 2.6 and `PUBSUB` to the profile for
Redis 2.8.
- `Predis\Client` can now send raw commands using `Predis\Client::executeRaw()`.
- Status responses are returned as instances of `Predis\Response\Status`, for
example +OK is not returned as boolean TRUE anymore which is a breaking change
for those using strict comparisons. Status responses can be casted to string
values carrying the original payload, so one can do `$response == 'OK'` which
is also more akin to how Redis replies to clients.
- Commands `ZRANGE`, `ZRANGEBYSCORE`, `ZREVRANGE` and `ZREVRANGEBYSCORE` using
`WITHSCORE` return a named array of member => score instead of using an array
of [member, score] elements. Insertion order is preserved anyway due to how
PHP works internally.
- The command `ZSCAN` returns a named array of member => score instead of using
an array of [member, score] elements. Insertion order is preserved anyway due
to how PHP works internally.
- The rules for redis-cluster are now leveraged for empty key tags when using
client-side sharding, which means that when one or the first occurrence of {}
is found in a key it will most likely produce a different hash than previous
versions of Predis thus leading to a different partitioning in these cases.
- Invoking `Predis\Client::connect()` when the underlying connection has been
already established does not throw any exception anymore, now the connection
simply does not attempt to perform any operation.
- Added the `aggregate` client option, useful to fully customize how the client
should aggregate multiple connections when an array of connection parameters
is passed to `Predis\Client::__construct()`.
- Dropped support for streamable multibulk responses. Actually we still ship the
iterator response classes just in case anyone would want to build custom stuff
at a level lower than the client abstraction (our standard and composable text
protocol processors still handle them and can be used as an example).
- Simplified the implementation of connection parameters by removing method used
to cast to int / bool / float certain parameters supplied by users. Casting
values, if deemed necessary, should be done by the consumer or you can just
subclass `Predis\Connection\Parameters` and override the `filter()` method.
- Changed a couple of options for our transaction abstraction:
- `exceptions`: overrides the value of the client option with the same name.
Please note that it does not affect all the transaction control commands
such as `MULTI`, `EXEC`, `DISCARD`, `WATCH` and `UNWATCH`.
- `on_retry`: this option has been removed.
- Removed pipeline executors, now command pipelines can be easily customized by
extending the standard `Predis\Pipeline\Pipeline` class. Accepted options when
creating a pipeline using `Predis\Client::pipeline()` are:
- `atomic`: returns a pipeline wrapped in a MULTI / EXEC transaction
(class: `Predis\Pipeline\Atomic`).
- `fire-and-forget`: returns a pipeline that does not read back responses
(class: `Predis\Pipeline\FireAndForget`).
- Renamed the two base abstract command classes:
- `Predis\Command\AbstractCommand` is now `Predis\Command\Command`
- `Predis\Command\ScriptedCommand` is now `Predis\Command\ScriptCommand`
- Dropped `Predis\Command\Command::__toString()` (see issue #151).
- The key prefixing logic has been moved from command classes to the key prefix
processor. Developers can define or override handlers used to prefix keys, but
they can also define the needed logic in their command classes by implementing
`Predis\Command\PrefixableCommandInterface` just like before.
- `Predis\PubSub\DispatcherLoop` now takes a `Predis\PubSub\Consumer` instance
as the sole argument of its constructor instead of `Predis\ClientInterface`.
- All of the interfaces and classes related to translated Redis response types
have been moved in the new `Predis\Response` namespace and most of them have
been renamed to make their fully-qualified name less redundant. Now the base
response interface is `Predis\Response\ResponseInterface`.
- Renamed interface `Predis\Command\Processor\CommandProcessorInterface` to a
shorter `Predis\Command\Processor\ProcessorInterface`. Also removed interface
for chain processors since it is basically useless.
- Renamed `Predis\ExecutableContextInterface` to `Predis\ClientContextInterface`
and augmented it with a couple of required methods since this interface is no
more comparable to a basic client as it could be misleading.
- The `Predis\Option` namespace is now known as `Predis\Configuration` and have
a fully-reworked `Options` class with the ability to lazily initialize values
using objects that responds to `__invoke()` (not all the kinds of callables)
even for custom options defined by the user.
- Renamed `Predis\Connection\ConnectionInterface::writeCommand()` into
`writeRequest()` for consistency with its counterpart, `readResponse()`.
- Renamed `Predis\Connection\SingleConnectionInterface::pushInitCommand()` into
`addConnectCommand()` which is more obvious.
- Renamed the connection class based on both ext-phpiredis and ext-socket into
`Predis\Connection\PhpiredisSocketConnection`. The one based on PHP's streams
is still named `Predis\Connection\PhpiredisStreamConnection`.
- Renamed the connection factory class to `Predis\Connection\Factory`. Now its
constructor does not require anymore a profile instance to create `AUTH` and
`SELECT` commands when parameters contain both `password` and `database`. Raw
commands will be used instead.
- Renamed the connection parameters class to `Predis\Connection\Parameters`. Now
its constructor accepts only named arrays, but instances can still be created
using both URIs or arrays using the static method `Parameters::create()`.
- The profile factory code has been extracted from the abstract Redis profile
class and now lives in `Predis\Profile\Factory`.
- The `Predis\Connection` namespace has been completely reorganized by renaming
a few classes and interfaces and adding some sub-namespaces.
- Most classes and interfaces in the `Predis\Protocol` namespace have been moved
or renamed while rationalizing the whole API for external protocol processors.
v0.8.7 (2014-08-01)
================================================================================
- Added `3.0` in the server profiles aliases list for Redis 3.0. `2.8` is still
the default server profile and `dev` still targets Redis 3.0.
- Added `COMMAND` to the server profile for Redis 2.8.
- Switched internally to the `CLUSTER SLOTS` command instead of `CLUSTER NODES`
to fetch the updated slots map from redis-cluster. This change requires users
to upgrade Redis nodes to >= 3.0.0b7.
- The updated slots map is now fetched automatically from redis-cluster upon the
first `-MOVED` response by default. This change makes it possible to feed the
client constructor with only a few nodes of the actual cluster composition,
without needing a more complex configuration.
- Implemented support for `PING` in PUB/SUB loop for Redis >= 3.0.0b8.
- The default client-side sharding strategy and the one for redis-cluster now
share the same implementations as they follow the same rules. One difference,
aside from the different hashing function used to calculate distribution, is
in how empty hash tags like {} are treated by redis-cluster.
- __FIX__: the patch applied to fix #180 introduced a regression affecting read/
write timeouts in `Predis\Connection\PhpiredisStreamConnection`. Unfortunately
the only possible solution requires PHP 5.4+. On PHP 5.3, read/write timeouts
will be ignored from now on.
v0.8.6 (2014-07-15)
================================================================================
- Redis 2.8 is now the default server profile as there are no changes that would
break compatibility with previous releases.
- Added `PFADD`, `PFCOUNT`, `PFMERGE` to the server profile for Redis 2.8 for
handling the HyperLogLog data structure introduced in Redis 2.8.9.
- Added `ZLEXCOUNT`, `ZRANGEBYLEX`, `ZREMRANGEBYLEX` to the server profile for
Redis 2.8 for handling lexicographic operations on members of sorted sets.
- Added support for key hash tags when using redis-cluster (Redis 3.0.0b1).
- __FIX__: minor tweaks to make Predis compatible with HHVM >= 2.4.0.
- __FIX__: responses to `INFO` are now properly parsed and will not break when
redis sentinel is being used (ISSUE #154).
- __FIX__: added missing support for `INCRBYFLOAT` in cluster and replication
configurations (ISSUE #159).
- __FIX__: fix parsing of the output of `CLUSTER NODES` to fetch the slots map
from a node when redis-cluster has slaves in its configuration (ISSUE #165).
- __FIX__: prevent a stack overflow when iterating over large Redis collections
using our abstraction for cursor-based iterators (ISSUE #182).
- __FIX__: properly discards transactions when the server immediately returns an
error response (e.g. -OOM or -ERR on invalid arguments for a command) instead
of a +QUEUED response (ISSUE #187).
- Upgraded to PHPUnit 4.* for the test suite.
v0.8.5 (2014-01-16)
================================================================================
- Added `2.8` in the server profiles aliases list for Redis 2.8. `2.6` is still
the default server profile and `dev` now targets Redis 3.0.
- Added `SCAN`, `SSCAN`, `ZSCAN`, `HSCAN` to the server profile for Redis 2.8.
- Implemented PHP iterators for incremental iterations over Redis collections:
- keyspace (cursor-based iterator using `SCAN`)
- sets (cursor-based iterator using `SSCAN`)
- sorted sets (cursor-based iterator using `ZSCAN`)
- hashes (cursor-based iterator using `HSCAN`)
- lists (plain iterator using `LRANGE`)
- It is now possible to execute "raw commands" using `Predis\Command\RawCommand`
and a variable list of command arguments. Input arguments are not filtered and
responses are not parsed, which means arguments must follow the signature of
the command as defined by Redis and complex responses are left untouched.
- URI parsing for connection parameters has been improved and has slightly less
overhead when the number of fields in the querystring grows. New features are:
- Parsing does not break when value of a field contains one or more "=".
- Repeated fieldnames using [] produce an array of values.
- Empty or incomplete "key=value" pairs result in an empty string for "key".
- Various improvements and fixes to the redis-cluster connection backend:
- __FIX__: the `ASKING` command is sent upon -ASK redirections.
- An updated slots-map can be fetched from nodes using the `CLUSTER NODES`
command. By default this is a manual operation but can be enabled to get
automatically done upon -MOVED redirections.
- It is possible to specify a common set of connection parameters that are
applied to connections created on the fly upon redirections to nodes not
part of the initial pool.
- List of deprecated methods:
- `Predis\Client::multiExec()`: superseded by `Predis\Client::transaction()`
and to be removed in the next major release.
- `Predis\Client::pubSub()`: superseded by `Predis\Client::pubSubLoop()` and
to be removed in the next major release. This change was needed due to the
recently introduced `PUBSUB` command in Redis 2.8.
v0.8.4 (2013-07-27)
================================================================================
- Added `DUMP` and `RESTORE` to the server profile for Redis 2.6.
- Connection exceptions now report basic host details in their messages.
- Allow `Predis\Connection\PhpiredisConnection` to use a random IP when a host
actually has several IPs (ISSUE #116).
- __FIX__: allow `HMSET` when using a cluster of Redis nodes with client-side
sharding or redis-cluster (ISSUE #106).
- __FIX__: set `WITHSCORES` modifer for `ZRANGE`, `ZREVRANGE`, `ZRANGEBYSCORE`
and `ZREVRANGEBYSCORE` only when the options array passed to these commands
has `WITHSCORES` set to `true` (ISSUE #107).
- __FIX__: scripted commands falling back from `EVALSHA` to `EVAL` resulted in
PHP errors when using a prefixed client (ISSUE #109).
- __FIX__: `Predis\PubSub\DispatcherLoop` now works properly when using key
prefixing (ISSUE #114).
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)
================================================================================
- The default server profile for Redis is now `2.6`.
- Certain connection parameters have been renamed:
- `connection_async` is now `async_connect`
- `connection_timeout` is now `timeout`
- `connection_persistent` is now `persistent`
- The `throw_errors` connection parameter has been removed and replaced by the
new `exceptions` client option since exceptions on `-ERR` replies returned by
Redis are not generated by connection classes anymore but instead are thrown
by the client class and other abstractions such as pipeline contexts.
- Added smart support for redis-cluster (Redis v3.0) in addition to the usual
cluster implementation that uses client-side sharding.
- Various namespaces and classes have been renamed to follow rules inspired by
the Symfony2 naming conventions.
- The second argument of the constructor of `Predis\Client` does not accept
strings or instances of `Predis\Profile\ServerProfileInterface` anymore.
To specify a server profile you must explicitly set `profile` in the array
of client options.
- `Predis\Command\ScriptedCommand` internally relies on `EVALSHA` instead of
`EVAL` thus avoiding to send Lua scripts bodies on each request. The client
automatically resends the command falling back to `EVAL` when Redis returns a
`-NOSCRIPT` error. Automatic fallback to `EVAL` does not work with pipelines,
inside a `MULTI / EXEC` context or with plain `EVALSHA` commands.
- Complex responses are no more parsed by connection classes as they must be
processed by consumer classes using the handler associated to the issued
command. This means that executing commands directly on connections only
returns simple Redis types, but nothing changes when using `Predis\Client`
or the provided abstractions for pipelines and transactions.
- Iterators for multi-bulk replies now skip the response parsing method of the
command that generated the response and are passed directly to user code.
Pipeline and transaction objects still consume automatically iterators.
- Cluster and replication connections now extend a new common interface,
`Predis\Connection\AggregatedConnectionInterface`.
- `Predis\Connection\MasterSlaveReplication` now uses an external strategy
class to handle the logic for checking readable / writable commands and Lua
scripts.
- Command pipelines have been optimized for both speed and code cleanness, but
at the cost of bringing a breaking change in the signature of the interface
for pipeline executors.
- Added a new pipeline executor that sends commands wrapped in a MULTI / EXEC
context to make the execution atomic: if a pipeline fails at a certain point
then the whole pipeline is discarded.
- The key-hashing mechanism for commands is now handled externally and is no
more a competence of each command class. This change is neeeded to support
both client-side sharding and Redis cluster.
- `Predis\Options\Option` is now abstract, see `Predis\Option\AbstractOption`.
v0.7.3 (2012-06-01)
================================================================================
- New commands available in the Redis v2.6 profile (dev): `BITOP`, `BITCOUNT`.
- When the number of keys `Predis\Commands\ScriptedCommand` is negative, Predis
will count from the end of the arguments list to calculate the actual number
of keys that will be interpreted as elements for `KEYS` by the underlying
`EVAL` command.
- __FIX__: `examples\CustomDistributionStrategy.php` had a mistyped constructor
call and produced a bad distribution due to an error as pointed in ISSUE #63.
This bug is limited to the above mentioned example and does not affect the
classes implemented in the `Predis\Distribution` namespace.
- __FIX__: `Predis\Commands\ServerEvalSHA::getScriptHash()` was calculating the
hash while it just needs to return the first argument of the command.
- __FIX__: `Predis\Autoloader` has been modified to allow cascading autoloaders
for the `Predis` namespace.
v0.7.2 (2012-04-01)
================================================================================
- Added `2.6` in the server profiles aliases list for the upcoming Redis 2.6.
`2.4` is still the default server profile. `dev` now targets Redis 2.8.
- Connection instances can be serialized and unserialized using `serialize()`
and `unserialize()`. This is handy in certain scenarios such as client-side
clustering or replication to lower the overhead of initializing a connection
object with many sub-connections since unserializing them can be up to 5x
times faster.
- Reworked the default autoloader to make it faster. It is also possible to
prepend it in PHP's autoload stack.
- __FIX__: fixed parsing of the payload returned by `MONITOR` with Redis 2.6.
v0.7.1 (2011-12-27)
================================================================================
- The PEAR channel on PearHub has been deprecated in favour of `pear.nrk.io`.
- Miscellaneous minor fixes.
- Added transparent support for master / slave replication configurations where
write operations are performed on the master server and read operations are
routed to one of the slaves. Please refer to ISSUE #21 for a bit of history
and more details about replication support in Predis.
- The `profile` client option now accepts a callable object used to initialize
a new instance of `Predis\Profiles\IServerProfile`.
- Exposed a method for MULTI / EXEC contexts that adds the ability to execute
instances of Redis commands against transaction objects.
v0.7.0 (2011-12-11)
===============================================================================
================================================================================
- Predis now adheres to the PSR-0 standard which means that there is no more a
single file holding all the classes of the library, but multiple files (one
@@ -66,19 +532,19 @@ v0.7.0 (2011-12-11)
thus removed. Serialization of commands is now a competence of connections.
- The `Predis\IConnection` interface has been splitted into two new interfaces:
`Predis\Network\IConnectionSingle and `Predis\Network\IConnectionCluster`.
`Predis\Network\IConnectionSingle` and `Predis\Network\IConnectionCluster`.
- The constructor of `Predis\Client` now accepts more type of arguments such as
instances of `Predis\IConnectionParameters` and `Predis\Network\IConnection`.
v0.6.6 (2011-04-01)
===============================================================================
================================================================================
- Switched to Redis 2.2 as the default server profile (there are no changes
that would break compatibility with previous releases). Long command names
are no more supported by default but if you need them you can still require
Predis_Compatibility.php to avoid breaking compatibility.
`Predis_Compatibility.php` to avoid breaking compatibility.
- Added a `VERSION` constant to `Predis\Client`.
@@ -88,7 +554,7 @@ v0.6.6 (2011-04-01)
- Predis now uses by default a new protocol reader, more lightweight and
faster than the default handler-based one. Users can revert to the old
protocol reader with the 'reader' client option set to `composable`.
protocol reader with the `reader` client option set to `composable`.
This client option can also accept custom reader classes implementing the
new `Predis\IResponseReader` interface.
@@ -112,7 +578,7 @@ v0.6.6 (2011-04-01)
v0.6.5 (2011-02-12)
===============================================================================
================================================================================
- __FIX__: due to an untested internal change introduced in v0.6.4, a wrong
handling of bulk reads of zero-length values was producing protocol
@@ -120,7 +586,7 @@ v0.6.5 (2011-02-12)
v0.6.4 (2011-02-12)
===============================================================================
================================================================================
- Various performance improvements (15% ~ 25%) especially when dealing with
long multibulk replies or when using clustered connections.
@@ -134,7 +600,7 @@ v0.6.4 (2011-02-12)
v0.6.3 (2011-01-01)
===============================================================================
================================================================================
- New commands available in the Redis v2.2 profile (dev):
- Strings: `SETRANGE`, `GETRANGE`, `SETBIT`, `GETBIT`
@@ -148,7 +614,7 @@ v0.6.3 (2011-01-01)
v0.6.2 (2010-11-28)
===============================================================================
================================================================================
- Minor internal improvements and clean ups.
@@ -173,7 +639,7 @@ v0.6.2 (2010-11-28)
v0.6.1 (2010-07-11)
===============================================================================
================================================================================
- Minor internal improvements and clean ups.
@@ -215,7 +681,7 @@ v0.6.1 (2010-07-11)
v0.6.0 (2010-05-24)
===============================================================================
================================================================================
- Switched to the new multi-bulk request protocol for all of the commands
in the Redis 1.2 and Redis 2.0 profiles. Inline and bulk requests are now
@@ -227,7 +693,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`
@@ -240,7 +706,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:
@@ -330,7 +796,7 @@ v0.6.0 (2010-05-24)
v0.5.1 (2010-01-23)
===============================================================================
================================================================================
* `RPOPLPUSH` has been changed from bulk command to inline command in Redis
1.2.1, so `ListPopLastPushHead` now extends `InlineCommand`. The old behavior
@@ -345,5 +811,5 @@ v0.5.1 (2010-01-23)
v0.5.0 (2010-01-09)
===============================================================================
================================================================================
* First versioned release of Predis
+44
View File
@@ -0,0 +1,44 @@
## Filing bug reports ##
Bugs or feature requests can be posted on the [GitHub issues](http://github.com/nrk/predis/issues)
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 (check the `VERSION` file or the `Predis\Client::VERSION` constant).
2. version of Redis (check `redis_version` 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 or peer into the unknown
so please provide as much details as possible to help us isolating issues and fix them.
__Never__ use GitHub issues to post generic questions about Predis! When you have questions about
how Predis works or how it can be used, please just hop me an email and I will get back to you as
soon as possible.
## Contributing code ##
If you want to work on Predis, it is highly recommended that you first run the test suite in order
to check that everything is OK and report strange behaviours or bugs. When modifying Predis please
make sure that no warnings or notices are emitted by PHP running the interpreter in your development
environment with the `error_reporting` variable set to `E_ALL | E_STRICT`.
The recommended way to contribute to Predis is to fork the project on GitHub, create topic branches
on your newly created repository to fix bugs or add new features (possibly with tests covering your
modifications) and then open a pull request with a description of the applied changes. Obviously you
can use any other Git hosting provider of your preference.
We always aim for consistency in our code base so you should follow basic coding rules as defined by
[PSR-1](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md)
and [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)
and stick with the conventions used in Predis to name classes and interfaces. Indentation should be
done with 4 spaces and code should be wrapped at 100 columns (please try to stay within this limit
even if the above mentioned official coding guidelines set the soft limit to 120 columns).
Please follow these [commit guidelines](http://git-scm.com/book/ch5-2.html#Commit-Guidelines) when
committing your code to Git and always write a meaningful (not necessarily extended) description of
your changes before opening pull requests.
-79
View File
@@ -1,79 +0,0 @@
# Some frequently asked questions about the performances of Predis #
____________________________________________
### 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):
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 *_.
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 *"".
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
going to use Redis in your application? Are you really going to send thousands of commands in a for-loop
for each page request using a single client instance? If so, well I guess you are probably doing something
wrong. Also, if you need to SET or GET multiple keys you should definitely use commands such as MSET and
MGET. You can also use pipelining to get more performances when this technique can be used.
There is one more thing. We have tested the overhead of Predis by connecting on a localhost instance of
Redis, but how these numbers change when we hit the network by connecting to instances of Redis that
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 *".
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 *".
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
remember that we are measuring the overhead of client libraries implementations and the effects of the
network round-trip time, we are not really measuring how fast Redis is. Redis shines the best with
thousands of concurrent clients doing requests! Also, actual performances should be measured according
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:
$client = new Predis\Client('tcp://127.0.0.1', array(
'connections' => array('tcp' => 'Predis\Network\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
_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
### If I need to install a C extension to get better performances, why not using phpredis? ###
Good question. Generically speaking, if you need absolute uber-speed using localhost instances of Redis
and you do not care about abstractions built around some Redis features such as MULTI / EXEC, or if you
do not need any kind of extensibility or guaranteed backwards compatibility with different versions of
Redis (Predis currently supports from 1.2 up to 2.2, and even the current development version), then
using __phpredis__ can make sense for you. Otherwise, Predis is for you. Using __phpiredis__ gives you
a nice speed bump, but it is not mandatory.
+159 -48
View File
@@ -1,58 +1,169 @@
# Some frequently asked questions about Predis #
____________________________________________
________________________________________________
### What is the point of Predis? ###
The main point of Predis is about offering a highly customizable client for Redis that can be easily
extended by developers while still being reasonabily fast. With Predis you can swap almost any class
used internally with your own custom implementation: you can build connection classes, or new
distribution strategies for client-side sharding, or class handlers to replace existing commands or
add new ones. All of this can be achieved without messing with the source code of the library and
directly in your own application. Given the fast pace at which Redis is developed and adds new
features, this can be a great asset that allows you to add new and still missing features or commands,
or change the behaviour of the library without the need to break your dependencies in production code
(well, at least to some degree).
### How about performances? ###
Please refer to the dedicated __FAQ.PERFORMANCES__ file.
### Why PHP 5.3? ###
Seriously, are you still using PHP 5.2 to build new applications? I assume that if you are throwing Redis
in the mix, then you are probably coding something new after all. PHP 5.3 is faster, less memory hungry
and has a few nice features such as namespaces and closures (kind of). More importantly, PHP 5.2 is not
even officially supported anymore (aside from security patches). PHP 5.3 is not the future of PHP, but
its current present. Furthermore, most of the existing frameworks out there are also making the switch
with their respective new major versions. If you still insist on using PHP 5.2, you can get any recent
backported release of Predis 0.6.x, or just use a different library.
### Why so many files for just one library? ###
Before v0.7, Predis used the one-big-file approach to distribute the library. As much as you prefer having
just one file for everything, this kind of solution is actually not that good. Predis now complies with the
[PSR-0](http://groups.google.com/group/php-standards/web/psr-0-final-proposal) standard to play nice with
the major recent frameworks and libraries, so it needs an autoloader function to be defined. If you still
want to have just one file grouping all the classes for whatever reason, then the __bin/create-single-file.php__
script in the repository can generate it for you. There is also the __bin/create-phar.php__ script that
generates a single [Phar archive](http://www.php.net/manual/en/intro.phar.php) of the whole library.
The main point of Predis is about offering a highly customizable and extensible client for Redis,
that can be easily extended by developers while still being reasonabily fast. With Predis you can
swap almost any class with your own custom implementation: you can have custom connection classes,
new distribution strategies for client-side sharding, or handlers to replace or add Redis commands.
All of this can be achieved without messing with the source code of the library and directly in your
own application. Given the fast pace at which Redis is developed and adds new features, this can be
a great asset since it allows developers to add new and still missing features or commands or change
the standard behaviour of the library without the need to break dependencies in production code (at
least to some degree).
### Does Predis support UNIX domain sockets and persistent connections? ###
Yes. Obviously, persistent connections actually work when using PHP configured as a persistent process that
gets recycled between requests (see [PHP-FPM](http://php-fpm.org/)).
Yes. Obviously persistent connections actually work only when using PHP configured as a persistent
process reused by the web server (see [PHP-FPM](http://php-fpm.org)).
### Does Predis support transparent (de)serialization of values? ###
No, and it will not ever do that for you by default. The reason behind this decision is that serialization
is usually something that developers prefer to customize depending on their needs and can not be easily
generalized when using Redis because of the many possible access patterns for the data. This does not
mean that it is impossible to have such a feature, you can leverage Predis' extensibility to define your
own serialization-aware commands. See [here](http://github.com/nrk/predis/issues/29#issuecomment-1202624)
for more details on how to implement such a feature with a practical example.
No and it will not ever do that by default. The reason behind this decision is that serialization is
usually something that developers prefer to customize depending on their needs and can not be easily
generalized when using Redis because of the many possible access patterns for your data. This does
not mean that it is impossible to have such a feature since you can leverage the extensibility of
this library to define your own serialization-aware commands. You can find more details about how to
do that [on this issue](http://github.com/nrk/predis/issues/29#issuecomment-1202624).
### How can I force Predis to connect to Redis before sending any command? ###
Explicitly connecting to Redis is usually not needed since the client initializes connections lazily
only when they are needed. Admittedly, this behavior can be inconvenient in certain scenarios when
you absolutely need to perform an upfront check to determine if the server is up and running and
eventually catch exceptions on failures. Forcing the client to open the underlying connection can be
done by invoking `Predis\Client::connect()`:
```php
$client = new Predis\Client();
try {
$client->connect();
} catch (Predis\Connection\ConnectionException $exception) {
// We could not connect to Redis! Your handling code goes here.
}
$client->info();
```
### How Predis abstracts Redis commands? ###
The approach used to implement Redis commands is quite simple: by default each command follows the
same signature as defined on the [Redis documentation](http://redis.io/commands) which makes things
pretty easy if you already know how Redis works or you need to look up how to use certain commands.
Alternatively, variadic commands can accept an array for keys or values (depending on the command)
instead of a list of arguments. Commands such as [`RPUSH`](http://redis.io/commands/rpush) and
[`HMSET`](http://redis.io/commands/hmset) are great examples:
```php
$client->rpush('my:list', 'value1', 'value2', 'value3'); // plain method arguments
$client->rpush('my:list', ['value1', 'value2', 'value3']); // single argument array
$client->hmset('my:hash', 'field1', 'value1', 'field2', 'value2'); // plain method arguments
$client->hmset('my:hash', ['field1'=>'value1', 'field2'=>'value2']); // single named array
```
An exception to this rule is [`SORT`](http://redis.io/commands/sort) for which modifiers are passed
[using a named array](tests/Predis/Command/KeySortTest.php#L54-L75).
# Speaking about performances... #
_________________________________________________
### 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 with a simple test that uses a single client and is executed by PHP 5.5.6
against a local instance of Redis 2.8 that runs under Ubuntu 13.10 on a Intel Q6600:
```
21000 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 [__phpredis__](http://github.com/nicolasff/phpredis), a nice C extension
providing an efficient client for Redis?
```
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__ seems 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
going to use Redis in your application? Are you really going to send thousands of commands using a
for-loop on each page request using a single client instance? If so... well I guess you are probably
doing something wrong. Also, if you need to `SET` or `GET` multiple keys you should definitely use
commands such as `MSET` and `MGET`. You can also use pipelining to get more performances when this
technique can be used.
There is one more thing: we have tested the overhead of Predis by connecting on a localhost instance
of Redis but how these numbers change when we hit the physical network by connecting to remote Redis
instances?
```
Using Predis:
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:
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 simple: network latency is a
real performance killer and you cannot do (almost) anything about that. As a disclaimer, remember
that we are measuring the overhead of client libraries implementations and the effects of network
round-trip times, so we are not really measuring how fast Redis is. Redis shines best with thousands
of concurrent clients doing requests! Also, actual performances should be measured according to how
your application will use Redis.
### I am convinced, but performances for multi-bulk responses are still worse ###
Fair enough, but there is an option available if you need even more speed and consists on installing
__[phpiredis](http://github.com/nrk/phpiredis)__ (note the additional _i_ in the name) and let the
client use it. __phpiredis__ is another C extension that wraps __hiredis__ (the official C client
library for Redis) with a thin layer exposing its features to PHP. You can then choose between two
different connection classes:
- `Predis\Connection\PhpiredisStreamConnection` (using native PHP streams).
- `Predis\Connection\PhpiredisSocketConnection` (requires `ext-socket`).
You will now get the benefits of a faster protocol serializer and parser just by adding a couple of
lines of code:
```php
$client = new Predis\Client('tcp://127.0.0.1', array(
'connections' => array(
'tcp' => 'Predis\Connection\PhpiredisStreamConnection',
'unix' => 'Predis\Connection\PhpiredisSocketConnection',
),
));
```
Dead simple. Nothing changes in the way you use the library in your application. So how fast is it
our basic benchmark script now? There are not much improvements for inline or short bulk responses
like the ones returned by `SET` and `GET`, but the speed for parsing multi-bulk responses is now on
par with phpredis:
```
Fatching 30000 keys with _KEYS *_ using Predis paired with phpiredis::
0.035 seconds from a local Redis instance
0.047 seconds from a remote Redis instance
```
### If I need an extension to get better performances, why not using phpredis? ###
Good question. Generically speaking if you need absolute uber-speed using Redis on the localhost and
you do not care about abstractions built around some Redis features such as MULTI / EXEC, or if you
do not need any kind of extensibility or guaranteed backwards compatibility with different versions
of Redis (Predis currently supports from 1.2 up to 2.8 and the current development version), then
using __phpredis__ makes absolutely sense. Otherwise, Predis is perfect for the job and by adding
__phpiredis__ you can get a nice speed bump almost for free.
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2009-2011 Daniele Alessandri
Copyright (c) 2009-2014 Daniele Alessandri
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
+313 -138
View File
@@ -1,147 +1,251 @@
# Predis #
Predis is a flexible and feature-complete PHP (>= 5.3) client library for the Redis key-value store.
[![Latest Stable Version](https://poser.pugx.org/predis/predis/v/stable.png)](https://packagist.org/packages/predis/predis)
[![Total Downloads](https://poser.pugx.org/predis/predis/downloads.png)](https://packagist.org/packages/predis/predis)
For a list of frequently asked questions about Predis, see the __FAQ__ file in the root of the repository.
For a version compatible with PHP 5.2 you must use the backported version from the latest release in the
0.6.x series. More details are available on the [official wiki](http://wiki.github.com/nrk/predis) of the
project,
Predis is a flexible and feature-complete [Redis](http://redis.io) client library for PHP >= 5.3.
By default Predis does not require any additional C extension, but it can be optionally paired with
[phpiredis](https://github.com/nrk/phpiredis) to lower the overhead of serializing and parsing the
Redis protocol. An asynchronous implementation of the client, albeit experimental, is also available
through [Predis\Async](https://github.com/nrk/predis-async).
Predis can be used with [HHVM](http://www.hhvm.com) >= 2.3.0, but there are no guarantees you will
not run into unexpected issues (especially when the JIT compiler is enabled via `Eval.Jit = true`)
due to HHVM being still under heavy development, thus unstable and not yet 100% compatible with PHP.
More details about the project can be found in our [frequently asked questions](FAQ.md) section or
on the online [wiki](https://github.com/nrk/predis/wiki).
## Main features ##
- Complete support for Redis from __1.2__ to __2.4__ and the current development versions using different
server profiles.
- Client-side sharding with support for consistent hashing or custom distribution strategies.
- Command pipelining on single and aggregated connections.
- Transparent key prefixing strategy capable of handling any command known that has keys in its arguments.
- Abstraction for Redis transactions (Redis >= 2.0) with support for CAS operations (Redis >= 2.2).
- Connections to Redis instances are automatically and lazily estabilished upon the first call to a command.
- Ability to connect to Redis using TCP/IP or UNIX domain sockets with support for persistent connections.
- Ability to use alternative connection classes to use different types of network or protocol backends.
- Flexible system to define and register your own set of commands or server profiles to client instances.
- Wide range of Redis versions supported (from __2.0__ to __3.0__ and __unstable__) using profiles.
- Cluster of nodes via client-side sharding using consistent hashing or custom distributors.
- Smart support for [redis-cluster](http://redis.io/topics/cluster-tutorial) (Redis >= 3.0).
- Support for master-slave replication configurations (write on master, read from slaves).
- Transparent key prefixing for all known Redis commands using a customizable prefixing strategy.
- Command pipelining (works on both single nodes and aggregate connections).
- Abstraction for Redis transactions (Redis >= 2.0) supporting CAS operations (Redis >= 2.2).
- Abstraction for Lua scripting (Redis >= 2.6) with automatic switching between `EVALSHA` or `EVAL`.
- Abstraction for `SCAN`, `SSCAN`, `ZSCAN` and `HSCAN` (Redis >= 2.8) based on PHP iterators.
- Connections to Redis are established lazily by the client upon the first command.
- Support for both TCP/IP and UNIX domain sockets and persistent connections.
- Support for [Webdis](http://webd.is) (both `ext-curl` and `ext-phpiredis` are needed).
- Support for custom connection classes for providing different network or protocol backends.
- Flexible system for defining and registering custom sets of supported commands or profiles.
## How to use Predis ##
Predis is available on [Packagist](http://packagist.org/packages/predis/predis) for an easy installation
using [Composer](http://packagist.org/about-composer). Composer helps you manage dependencies for your
projects and libraries without much hassle which makes it the preferred way to get up and running with
new applications. Alternatively, the library is available on [PearHub](http://pearhub.org/projects/predis)'s
channel for a more traditional installation via PEAR. Zip and tar.gz archives are also downloadable from
GitHub by browsing the list of [tagged releases](http://github.com/nrk/predis/tags).
Predis is available on [Packagist](http://packagist.org/packages/predis/predis) which allows a quick
installation using [Composer](http://packagist.org/about-composer). Alternatively, the library can
be found on our [own PEAR channel](http://pear.nrk.io) for a more traditional installation via PEAR.
Ultimately, archives of each release are [available on GitHub](https://github.com/nrk/predis/tags).
### Loading the library ###
To automatically load all of its files, Predis relies on the autoloading features of PHP and complies
with the [PSR-0 standard](http://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) for
interoperability with most of the major frameworks and libraries. Everything is transparently handled
for you when installing the library using 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:
Predis relies on the autoloading features of PHP to load its files when needed and complies with the
[PSR-4 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md).
Autoloading is handled automatically when dependencies are managed through Composer, but it is also
possible to leverage its own autoloader in projects or scripts not having any autoload facility:
``` php
<?php
require PREDIS_BASE_PATH . '/Autoloader.php';
```php
// Prepend a base path if Predis is not available in your "include_path".
require 'Predis/Autoloader.php';
Predis\Autoloader::register();
```
You can create a single [Phar](http://www.php.net/manual/en/intro.phar.php) archive from the repository
just by launching the `bin/create-phar.php` executable script. The generated Phar archive ships with a
stub defining an autoloader function for Predis, so you just need to require the Phar to be able to use
the library.
Alternatively you can generate a single PHP file that holds every class, just like older versions of
Predis, using the `bin/create-single-file.php` executable sript. In this way you can load Predis in your
scripts simply by using functions such as `require` and `include`, but this practice is not encouraged.
It is possible to easily create a [phar](http://www.php.net/manual/en/intro.phar.php) archive from
the repository just by launching `bin/create-phar`. The generated phar contains a stub defining an
autoloader function for Predis, so you just need to require the phar to start using the library.
Alternatively it is possible to generate one single PHP file holding every class like older versions
of Predis by launching `bin/create-single-file`, but this practice __is not__ encouraged.
### Connecting to a local instance of Redis ###
### Connecting to Redis ###
When connecting to local instance of Redis (`127.0.0.1` on port `6379`), you do not have to specify any
additional parameter to create a new client instance:
When not specifying any connection parameter to create a new client, Predis assumes `127.0.0.1` and
`6379` as the default host and port with a connection timeout of 5 seconds:
``` php
<?php
$redis = new Predis\Client();
$redis->set('foo', 'bar');
$value = $redis->get('foo');
```php
$client = new Predis\Client();
$client->set('foo', 'bar');
$value = $client->get('foo');
```
However you can use an URI string or a named array to specify the needed connection parameters:
Connection parameters can be supplied either in the form of URI strings or named arrays. While the
latter is the preferred way to supply parameters, URI strings can be useful for quick configurations
or when parameters are read from a non-structured source:
``` php
<?php
$redis = new Predis\Client('tcp://10.0.0.1:6379');
// is equivalent to:
$redis = new Predis\Client(array(
```php
// Named array of connection parameters:
$client = new Predis\Client([
'scheme' => 'tcp',
'host' => '10.0.0.1',
'port' => 6379,
));
]);
// Same set of parameters, but using an URI string:
$client = new Predis\Client('tcp://10.0.0.1:6379');
```
When an array of connection parameters is provided, Predis automatically works in cluster mode using
client-side sharding. Both named arrays and URI strings can be mixed when providing configurations
for each node:
### Pipelining multiple commands to multiple instances of Redis with client-side sharding ###
```php
$client = new Predis\Client([
'tcp://10.0.0.1?alias=first-node',
['host' => '10.0.0.2', 'alias' => 'second-node'],
]);
```
Pipelining helps with performances when there is the need to send many commands to a server in one go.
Furthermore, pipelining works transparently even on aggregated connections. To achieve this, Predis
supports client-side sharding using consistent-hashing on keys while clustered connections are supported
natively by the client class.
The actual list of supported connection parameters can vary depending on each connection backend so
it is recommended to refer to their specific documentation or implementation for details.
``` php
<?php
$redis = new Predis\Client(array(
array('host' => '10.0.0.1', 'port' => 6379),
array('host' => '10.0.0.2', 'port' => 6379)
));
$replies = $redis->pipeline(function($pipe) {
### Client configuration ###
Various aspects of the client can be configured simply by passing options to the second argument of
`Predis\Client::__construct()`. Options are managed using a mini DI-alike container and their values
are usually lazily initialized only when needed. Predis by default supports the following options:
- `profile`: which profile to use in order to match a specific version of Redis.
- `prefix`: a prefix string that is automatically applied to keys found in commands.
- `exceptions`: whether the client should throw or return responses upon Redis errors.
- `connections`: connection backends or a connection factory to be used by the client.
- `cluster`: which backend to use for clustering (`predis`, `redis` or custom configuration).
- `replication`: which backend to use for replication (predis or custom configuration).
- `aggregate`: custom connections aggregator (overrides both `cluster` and `replication`).
Users can provide custom options with their values or lazy callable initializers that are stored in
the options container for later use through the library.
### Aggregate connections ###
Predis is able to aggregate multiple connections which is the base for cluster and replication. By
default the client implements a cluster of nodes using either client-side sharding (default) or a
Redis-backed solution using [redis-cluster](http://redis.io/topics/cluster-tutorial).
As for replication, Predis can handle a single-master and multiple-slaves setup by executing read
operations on slaves and switching to the master for write operations. The replication behavior is
fully configurable.
#### Replication ####
The client can be configured to work in a master / slave replication setup by executing read-only
commands on slave nodes and automatically switch to the master node as soon as a command performing
a write operation is executed. This is the basic configuration needed to work with replication:
```php
// Parameters require one master node specifically marked with `alias=master`.
$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2?alias=slave-01'];
$options = ['replication' => true];
$client = new Predis\Client($parameters, $options);
```
While Predis is able to distinguish commands performing write and read-only operations, `EVAL` and
`EVALSHA` represent a corner case in which the client switches to the master node because it is not
able to tell when a Lua script is safe to be executed on slaves. While this is the default behavior,
when certain Lua scripts do not perform write operations it is possible to provide an hint to tell
the client to stick with slaves for their execution.
```php
$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2?alias=slave-01'];
$options = ['replication' => function () {
// Set scripts that won't trigger a switch from a slave to the master node.
$strategy = new Predis\Replication\ReplicationStrategy();
$strategy->setScriptReadOnly($LUA_SCRIPT);
return new Predis\Connection\Aggregate\MasterSlaveReplication($strategy);
}];
$client = new Predis\Client($parameters, $options);
$client->eval($LUA_SCRIPT, 0); // Sticks to slave using `eval`...
$client->evalsha(sha1($LUA_SCRIPT), 0); // ... and `evalsha`, too.
```
The `examples` directory contains two complete scripts showing how replication can be configured for
[simple](examples/replication_simple.php) or [complex](examples/replication_complex.php) scenarios.
#### Cluster ####
Simply passing an array of connection parameters to the client constructor configures Predis to work
in cluster mode using client-side sharding. If you, on the other hand, want to leverage Redis >= 3.0
nodes coordinated by redis-cluster, then the client must be initialized like this:
```php
$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2'];
$options = ['cluster' => 'redis'];
$client = new Predis\Client($parameters, $options);
```
When using redis-cluster, it is not necessary to pass all of the nodes that compose your cluster but
you can simply specify only a few nodes: Predis will automatically fetch the full and updated slots
map directly from Redis by contacting one of the servers.
__NOTE__: our support for redis-cluster does not currently consider master / slave replication but
this feature will be added in a future release of this library.
### Command pipelines ###
Pipelining can help with performances when many commands need to be sent to a server by reducing the
latency introduced by network round-trip timings. Pipelining also works with aggregate connections.
The client can execute the pipeline inside a callable block or return a pipeline instance with the
ability to chain commands thanks to its fluent interface:
```php
// Executes a pipeline inside a given callable block:
$responses = $client->pipeline(function ($pipe) {
for ($i = 0; $i < 1000; $i++) {
$pipe->set("key:$i", str_pad($i, 4, '0', 0));
$pipe->get("key:$i");
}
});
// Returns a pipeline instance with fluent interface:
$responses = $client->pipeline()->set('foo', 'bar')->get('foo')->execute();
```
### Overriding standard connection classes with custom ones ###
### Transactions ###
Predis allows developers to create new connection classes to add support for new protocols or override
the existing ones and provide a different implementation compared to the default classes. This can be
obtained by subclassing the `Predis\Network\IConnectionSingle` interface.
The client provides an abstraction for Redis transactions based on `MULTI` and `EXEC` with a similar
interface to command pipelines:
``` php
<?php
class MyConnectionClass implements Predis\Network\IConnectionSingle
{
// implementation goes here
}
```php
// Executes a transaction inside a given callable block:
$responses = $client->transaction(function ($tx) {
$tx->set('foo', 'bar');
$tx->get('foo');
});
// Let Predis automatically use your own class to handle connections identified by the tcp scheme.
$client = new Predis\Client('tcp://127.0.0.1', array(
'connections' => array('tcp' => 'MyConnectionClass')
));
// Returns a transaction instance with fluent interface:
$responses = $client->transaction()->set('foo', 'bar')->get('foo')->execute();
```
The classes contained in the `Predis\Network` namespace give you a better insight with actual code on
how to create new connection classes.
This abstraction can perform check-and-set operations thanks to `WATCH` and `UNWATCH` and provides
automatic retries of transactions aborted by Redis when `WATCH`ed keys are touched. For an example
of a transaction using CAS you can see [the following example](examples/transaction_using_cas.php).
### Defining and registering new commands on the client at runtime ###
### Adding new commands ###
Let's suppose Redis just added the support for a brand new feature associated with a new command. If
you want to start using the above mentioned new feature right away without messing with Predis source
code or waiting for it to find its way into a stable Predis release, then you can start off by creating
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:
While we try to update Predis to stay up to date with all the commands available in Redis, you might
prefer to stick with an older version of the library or provide a different way to filter arguments
or parse responses for specific commands. To achieve that, Predis provides the ability to implement
new command classes to define or override commands in the server profiles used by the client:
``` php
<?php
class BrandNewRedisCommand extends Predis\Commands\Command
```php
// Define a new command by extending Predis\Command\Command:
class BrandNewRedisCommand extends Predis\Command\Command
{
public function getId()
{
@@ -149,72 +253,143 @@ class BrandNewRedisCommand extends Predis\Commands\Command
}
}
$redis = new Predis\Client();
$redis->getProfile()->defineCommand('newcmd', 'BrandNewRedisCommand');
$redis->newcmd();
// Inject your command in the current profile:
$client = new Predis\Client();
$client->getProfile()->defineCommand('newcmd', 'BrandNewRedisCommand');
$response = $client->newcmd();
```
There is also a method to send raw commands without filtering their arguments or parsing responses.
Users must provide the arguments list as an array, following the command signatures as defined by
the [Redis documentation for commands](http://redis.io/commands):
```php
$response = $client->executeRaw(['SET', 'foo', 'bar']);
```
## Test suite ##
### Script commands ###
__ATTENTION__: Do not ever run the test suite shipped with Predis against instances of Redis running in
production environments or containing data you are interested in!
While it is possible to leverage [Lua scripting](http://redis.io/commands/eval) on Redis 2.6+ using
[`EVAL`](http://redis.io/commands/eval) and [`EVALSHA`](http://redis.io/commands/evalsha), Predis
offers script commands as an higher level abstraction aiming to make things simple. Script commands
can be registered in the server profile used by the client and are accessible as if they were plain
Redis commands, but they define Lua scripts that get transmitted to the server for remote execution.
Internally they use [`EVALSHA`](http://redis.io/commands/evalsha) by default and identify a script
by its SHA1 hash to save bandwidth, but [`EVAL`](http://redis.io/commands/eval) is used as a fall
back when needed:
Predis has a comprehensive test suite covering every aspect of the library. The suite performs integration
tests against a running instance of Redis (>= 2.4.0 is required) to verify the correct behaviour of the
implementation of each command and automatically skips commands not defined in the selected version of
Redis. If you do not have Redis up and running, integration tests can be disabled. By default, the test
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`.
```php
// Define a new scriptable command by extending Predis\Command\ScriptCommand:
class ListPushRandomValue extends Predis\Command\ScriptCommand
{
public function getKeysCount()
{
return 1;
}
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).
public function getScript()
{
return <<<LUA
math.randomseed(ARGV[1])
local rnd = tostring(math.random())
redis.call('lpush', KEYS[1], rnd)
return rnd
LUA;
}
}
// Inject your script command in the current profile:
$client = new Predis\Client();
$client->getProfile()->defineCommand('lpushrand', 'ListPushRandomValue');
$response = $client->lpushrand('random_values', $seed = mt_rand());
```
## Contributing ##
### Customizable connection backends ###
If you want to work on Predis, it is highly recommended that you first run the test suite in order to
check that everything is OK, and report strange behaviours or bugs. When modifying Predis please make
sure that no warnings or notices are emitted by PHP by running the interpreter in your development
environment with the `error_reporting` variable set to `E_ALL | E_STRICT`.
Predis can use different connection backends to connect to Redis. Two of them leverage a third party
extension such as [phpiredis](https://github.com/nrk/phpiredis) resulting in major performance gains
especially when dealing with big multibulk responses. While one is based on PHP streams, the other
is based on socket resources provided by `ext-socket`. Both support TCP/IP or UNIX domain sockets:
The recommended way to contribute to Predis is to fork the project on GitHub, create new topic branches
on your newly created repository to fix or add features (possibly with tests covering your modifications)
and then open a new pull request with a description of the applied changes. Obviously you can use any
other Git hosting provider of your preference.
```php
$client = new Predis\Client('tcp://127.0.0.1', [
'connections' => [
'tcp' => 'Predis\Connection\PhpiredisStreamConnection', // PHP streams
'unix' => 'Predis\Connection\PhpiredisSocketConnection', // ext-socket
],
]);
```
Developers can create their own connection classes to support whole new network backends, extend
existing ones or provide completely different implementations. Connection classes must implement
`Predis\Connection\NodeConnectionInterface` or extend `Predis\Connection\AbstractConnection`:
```php
class MyConnectionClass implements Predis\Connection\NodeConnectionInterface
{
// Implementation goes here...
}
// Use MyConnectionClass to handle connections for the `tcp` scheme:
$client = new Predis\Client('tcp://127.0.0.1', [
'connections' => ['tcp' => 'MyConnectionClass'],
]);
```
For a more in-depth insight on how to create new connection backends you can refer to the actual
implementation of the standard connection classes available in the `Predis\Connection` namespace.
## Dependencies ##
## Development ##
- PHP >= 5.3.2
- PHPUnit >= 3.5.0 (needed to run the test suite)
## Links ##
### Reporting bugs and contributing code ###
### Project ###
- [Source code](http://github.com/nrk/predis/)
- [Wiki](http://wiki.github.com/nrk/predis/)
- [Issue tracker](http://github.com/nrk/predis/issues)
Contributions to Predis are highly appreciated either in the form of pull requests for new features,
bug fixes, or just bug reports. We only ask you to adhere to a [basic set of rules](CONTRIBUTING.md)
before submitting your changes or filing bugs on the issue tracker to make it easier for everyone to
stay consistent while working on the project.
### Related ###
- [Redis](http://redis.io/)
- [PHP](http://php.net/)
- [PHPUnit](http://www.phpunit.de/)
- [Git](http://git-scm.com/)
## Author ##
### Test suite ###
__ATTENTION__: Do not ever run the test suite shipped with Predis against instances of Redis running
in production environments or containing data you are interested in!
Predis has a comprehensive test suite covering every aspect of the library. This test suite performs
integration tests against a running instance of Redis (>= 2.4.0 is required) to verify the correct
behavior of the implementation of each command and automatically skips commands not defined in the
specified Redis profile. If you do not have Redis up and running, integration tests can be disabled.
By default the test suite is configured to execute integration tests using the profile for Redis 2.8
(which is the current stable version of Redis) but can optionally target a Redis instance built from
the `unstable` branch by modifying `phpunit.xml` and setting `REDIS_SERVER_VERSION` to `dev` so that
the development server profile will be used. You can refer to [the tests README](tests/README.md)
for more detailed information about testing Predis.
Predis uses Travis CI for continuous integration and the history for past and current builds can be
found [on its project page](http://travis-ci.org/nrk/predis).
## Other ##
### Project related links ###
- [Source code](https://github.com/nrk/predis)
- [Wiki](https://wiki.github.com/nrk/predis)
- [Issue tracker](https://github.com/nrk/predis/issues)
- [PEAR channel](http://pear.nrk.io)
### Author ###
- [Daniele Alessandri](mailto:suppakilla@gmail.com) ([twitter](http://twitter.com/JoL1hAHN))
## Contributors ##
- [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
### License ###
## 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)).
-4
View File
@@ -1,4 +0,0 @@
* Documentation! The README is obviously not enought to show how to use Predis
as it does not cover all of its features.
* [v0.8] Implement smart and transparent support for redis-cluster.
+1 -1
View File
@@ -1 +1 @@
0.7.0
1.0.0
+1 -1
View File
@@ -9,6 +9,6 @@
* file that was distributed with this source code.
*/
require __DIR__.'/lib/Predis/Autoloader.php';
require __DIR__.'/src/Autoloader.php';
Predis\Autoloader::register();
@@ -13,11 +13,11 @@
// -------------------------------------------------------------------------- //
// This script can be used to automatically generate a file with the scheleton
// of a test case to test a Redis command by specifying the name of the class
// in the Predis\Commands namespace (only classes in this namespace are valid).
// in the Predis\Command namespace (only classes in this namespace are valid).
// For example, to generate a test case for SET (which is represented by the
// Predis\Commands\StringSet class):
// Predis\Command\StringSet class):
//
// $ ./bin/generate-command-test.php --class=StringSet
// $ ./bin/generate-command-test --class=StringSet
//
// Here is a list of optional arguments:
//
@@ -33,14 +33,14 @@
// is explicitly specified.
// -------------------------------------------------------------------------- //
use Predis\Commands\ICommand;
use Predis\Commands\IPrefixable;
use Predis\Command\CommandInterface;
use Predis\Command\PrefixableCommandInterface;
class CommandTestCaseGenerator
{
private $options;
public function __construct(Array $options)
public function __construct(array $options)
{
if (!isset($options['class'])) {
throw new RuntimeException("Missing 'class' option.");
@@ -61,7 +61,7 @@ class CommandTestCaseGenerator
$options = array(
'overwrite' => false,
'tests' => __DIR__.'/../tests',
'tests' => __DIR__.'/../tests/Predis',
);
foreach ($getops as $option => $value) {
@@ -92,10 +92,10 @@ class CommandTestCaseGenerator
throw new RuntimeException("Missing 'class' option.");
}
$options['fqn'] = "Predis\\Commands\\{$options['class']}";
$options['path'] = "Predis/Commands/{$options['class']}.php";
$options['fqn'] = "Predis\\Command\\{$options['class']}";
$options['path'] = "Command/{$options['class']}.php";
$source = __DIR__.'/../lib/'.$options['path'];
$source = __DIR__.'/../src/'.$options['path'];
if (!file_exists($source)) {
throw new RuntimeException("Cannot find class file for {$options['fqn']} in $source.");
}
@@ -116,7 +116,8 @@ class CommandTestCaseGenerator
return $this->options['realm'];
}
$class = array_pop(explode('\\', $this->options['fqn']));
$fqnParts = explode('\\', $this->options['fqn']);
$class = array_pop($fqnParts);
list($realm,) = preg_split('/([[:upper:]][[:lower:]]+)/', $class, 2, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
return strtolower($realm);
@@ -129,11 +130,15 @@ class CommandTestCaseGenerator
if (!$reflection->isInstantiable()) {
throw new RuntimeException("Class $class must be instantiable, abstract classes or interfaces are not allowed.");
}
if (!$reflection->implementsInterface('Predis\Commands\ICommand')) {
throw new RuntimeException("Class $class must implement the Predis\Commands\ICommand interface.");
if (!$reflection->implementsInterface('Predis\Command\CommandInterface')) {
throw new RuntimeException("Class $class must implement Predis\Command\CommandInterface.");
}
/*
* @var CommandInterface
*/
$instance = $reflection->newInstance();
$buffer = $this->getTestCaseBuffer($instance);
return $buffer;
@@ -148,11 +153,12 @@ class CommandTestCaseGenerator
file_put_contents($options['output'], $this->generate());
}
protected function getTestCaseBuffer(ICommand $instance)
protected function getTestCaseBuffer(CommandInterface $instance)
{
$id = $instance->getId();
$fqn = get_class($instance);
$class = array_pop(explode('\\', $fqn)) . "Test";
$fqnParts = explode('\\', $fqn);
$class = array_pop($fqnParts) . "Test";
$realm = $this->getTestRealm();
$buffer =<<<PHP
@@ -167,15 +173,13 @@ class CommandTestCaseGenerator
* file that was distributed with this source code.
*/
namespace Predis\Commands;
use \PHPUnit_Framework_TestCase as StandardTestCase;
namespace Predis\Command;
/**
* @group commands
* @group realm-$realm
*/
class $class extends CommandTestCase
class $class extends PredisCommandTestCase
{
/**
* {@inheritdoc}
@@ -226,7 +230,7 @@ class $class extends CommandTestCase
PHP;
if ($instance instanceof IPrefixable) {
if ($instance instanceof PrefixableCommandInterface) {
$buffer .=<<<PHP
/**
@@ -245,6 +249,17 @@ PHP;
\$this->assertSame(\$expected, \$command->getArguments());
}
/**
* @group disconnected
*/
public function testPrefixKeysIgnoredOnEmptyArguments()
{
\$command = \$this->getCommand();
\$command->prefixKeys('prefix:');
\$this->assertSame(array(), \$command->getArguments());
}
PHP;
}
+233
View File
@@ -0,0 +1,233 @@
#!/usr/bin/env php
<?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.
*/
// -------------------------------------------------------------------------- //
// In order to be able to execute this script to create a PEAR package of Predis
// the `pear` binary must be available and executable in your $PATH.
// The parts used to parse author and version strings are taken from Onion (used
// by this library in the past) just to keep on relying on the package.ini file
// to simplify things. We might consider to switch to using the PEAR classes
// directly in the future.
// -------------------------------------------------------------------------- //
function executeWithBackup($file, $callback)
{
$exception = null;
$backup = "$file.backup";
copy($file, $backup);
try {
call_user_func($callback, $file);
} catch (Exception $exception) {
// NOOP
}
unlink($file);
rename($backup, $file);
if ($exception) {
throw $exception;
}
}
function parseAuthor($string)
{
$author = array();
if (preg_match('/^\s*(.+?)\s*(?:"(\S+)"\s*)?<(\S+)>\s*$/x', $string , $regs)) {
if (count($regs) == 4) {
list($_,$name,$user,$email) = $regs;
$author['name'] = $name;
$author['user'] = $user;
$author['email'] = $email;
} elseif (count($regs) == 3) {
list($_,$name,$email) = $regs;
$author['name'] = $name;
$author['email'] = $email;
}
} else {
$author['name'] = $string;
}
return $author;
}
function parseVersion($string)
{
$version_pattern = '([0-9.]+)';
if (preg_match("/^\s*$version_pattern\s*\$/x", $string, $regs)) {
return array('min' => $regs[1] ?: '0.0.0');
} elseif (preg_match("/^\s*[>=]+\s*$version_pattern\s*\$/x", $string, $regs)) {
return array('min' => $regs[1] ?: '0.0.0');
} elseif (preg_match("/^\s*[<=]+\s*$version_pattern\s*\$/x", $string, $regs)) {
return array('max' => $regs[1]);
} elseif (preg_match("/^\s*$version_pattern\s*<=>\s*$version_pattern\s*\$/x", $string, $regs)) {
return array(
'min' => $regs[1] ?: '0.0.0',
'max' => $regs[2],
);
}
return null;
}
function addRolePath($pkg, $path, $role)
{
if (is_dir($path)) {
$dirRoot = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$dirTree = new RecursiveIteratorIterator($dirRoot, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($dirTree as $fileinfo) {
if ($fileinfo->isFile()) {
addPackageFile($pkg, $fileinfo, $role, $path);
}
}
} else {
foreach (glob($path) as $filename) {
addPackageFile($pkg, new SplFileInfo($filename), $role);
}
}
}
function addPackageFile($pkg, $fileinfo, $role, $baseDir = '')
{
$fileNode = $pkg->contents->dir->addChild('file');
$fileNode->addAttribute('name', $filepath = $fileinfo->getPathname());
$fileNode->addAttribute('role', $role);
$fileNode->addAttribute('md5sum', md5_file($filepath));
$installNode = $pkg->phprelease->filelist->addChild('install');
$installNode->addAttribute('name', $filepath);
$installNode->addAttribute('as', !$baseDir ? basename($filepath) : substr($filepath, strlen($baseDir) + 1));
}
function generatePackageXml($packageINI)
{
$XML = <<<XML
<?xml version="1.0"?>
<package packagerversion="1.4.10" version="2.0"
xmlns="http://pear.php.net/dtd/package-2.0"
xmlns:tasks="http://pear.php.net/dtd/tasks-1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
http://pear.php.net/dtd/tasks-1.0.xsd
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd" />
XML;
$cfg = parse_ini_file($packageINI, true);
$pkg = new SimpleXMLElement($XML);
$pkg->name = $cfg['package']['name'];
$pkg->channel = $cfg['package']['channel'];
$pkg->summary = $cfg['package']['desc'];
$pkg->description = $cfg['package']['desc'];
$author = parseAuthor($cfg['package']['author']);
$pkg->addChild('lead');
$pkg->lead->name = $author['name'];
$pkg->lead->user = $author['user'];
$pkg->lead->email = $author['email'];
$pkg->lead->active = 'yes';
$datetime = new DateTime('now');
$pkg->date = $datetime->format('Y-m-d');
$pkg->time = $datetime->format('H:i:s');
$pkg->addChild('version');
$pkg->version->release = $cfg['package']['version'];
$pkg->version->api = $cfg['package']['version'];
$pkg->addChild('stability');
$pkg->stability->release = $cfg['package']['stability'];
$pkg->stability->api = $cfg['package']['stability'];
$pkg->license = $cfg['package']['license'];
$pkg->notes = '-';
$pkg->addChild('contents')->addChild('dir')->addAttribute('name', '/');
$pkg->addChild('dependencies')->addChild('required');
foreach ($cfg['require'] as $required => $version) {
$version = parseVersion($version);
$pkg->dependencies->required->addChild($required);
if (isset($version['min'])) {
$pkg->dependencies->required->$required->min = $version['min'];
}
if (isset($version['max'])) {
$pkg->dependencies->required->$required->min = $version['max'];
}
}
$pkg->addChild('phprelease')->addChild('filelist');
$pathToRole = array(
'doc' => 'doc', 'docs' => 'doc', 'examples' => 'doc',
'lib' => 'php', 'src' => 'php',
'test' => 'test', 'tests' => 'test',
);
foreach (array_merge($pathToRole, $cfg['roles'] ?: array()) as $path => $role) {
addRolePath($pkg, $path, $role);
}
return $pkg;
}
function rewritePackageInstallAs($pkg)
{
foreach ($pkg->phprelease->filelist->install as $file) {
if (preg_match('/^src\//', $file['name'])) {
$file['as'] = "Predis/{$file['as']}";
}
}
}
function savePackageXml($xml)
{
$dom = new DOMDocument("1.0");
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($xml->asXML());
file_put_contents('package.xml', $dom->saveXML());
}
function buildPackage()
{
passthru('pear -q package && rm package.xml');
}
function modifyPhpunitXml($file)
{
$cfg = new SimpleXMLElement($file, null, true);
$cfg[0]['bootstrap'] = str_replace('tests/', '', $cfg[0]['bootstrap']);
$cfg->testsuites->testsuite->directory = str_replace('tests/', '', $cfg->testsuites->testsuite->directory);
$cfg->saveXml($file);
}
// -------------------------------------------------------------------------- //
executeWithBackup(__DIR__.'/../phpunit.xml.dist', function ($file) {
modifyPhpunitXml($file);
$pkg = generatePackageXml('package.ini');
rewritePackageInstallAs($pkg);
savePackageXml($pkg);
buildPackage();
});
+3 -3
View File
@@ -19,7 +19,7 @@
$options = array(
'name' => 'predis',
'project_path' => __DIR__ . '/../lib/',
'project_path' => __DIR__ . '/../src',
'compression' => Phar::NONE,
'append_version' => true,
);
@@ -50,9 +50,9 @@ function getPharStub($options)
return <<<EOSTUB
<?php
Phar::mapPhar('predis.phar');
spl_autoload_register(function(\$class) {
spl_autoload_register(function (\$class) {
if (strpos(\$class, 'Predis\\\\') === 0) {
\$file = 'phar://predis.phar/'.strtr(\$class, '\\\', '/').'.php';
\$file = 'phar://predis.phar/'.strtr(substr(\$class, 7), '\\\', '/').'.php';
if (file_exists(\$file)) {
require \$file;
return true;
@@ -36,7 +36,7 @@ class CommandLine
$getops = getopt(implode(array_keys($parameters)), $parameters);
$options = array(
'source' => __DIR__ . "/../lib/",
'source' => __DIR__ . "/../src",
'output' => PredisFile::NS_ROOT . '.php',
'exclude' => array(),
);
@@ -80,11 +80,10 @@ class PredisFile
$this->namespaces = array();
}
public static function from($libraryPath, Array $exclude = array())
public static function from($libraryPath, array $exclude = array())
{
$nsroot = self::NS_ROOT;
$predisFile = new PredisFile();
$libIterator = new RecursiveDirectoryIterator("$libraryPath$nsroot");
$libIterator = new RecursiveDirectoryIterator($libraryPath);
foreach (new RecursiveIteratorIterator($libIterator) as $classFile)
{
@@ -92,7 +91,7 @@ class PredisFile
continue;
}
$namespace = strtr(str_replace($libraryPath, '', $classFile->getPath()), '/', '\\');
$namespace = self::NS_ROOT.strtr(str_replace($libraryPath, '', $classFile->getPath()), '/', '\\');
if (in_array(sprintf('%s\\%s', $namespace, $classFile->getBasename('.php')), $exclude)) {
continue;
@@ -184,7 +183,7 @@ class PredisFile
{
$namespaces = array_fill_keys(array_unique(
array_map(
function($fqn) { return PhpNamespace::extractName($fqn); },
function ($fqn) { return PhpNamespace::extractName($fqn); },
array_keys($dependencyScores)
)
), 0);
@@ -200,7 +199,7 @@ class PredisFile
private function getOrderedClasses(PhpNamespace $phpNamespace, $classes)
{
$nsClassesFQNs = array_map(function($cl) { return $cl->getFQN(); }, $phpNamespace->getClasses());
$nsClassesFQNs = array_map(function ($cl) { return $cl->getFQN(); }, $phpNamespace->getClasses());
$nsOrderedClasses = array();
foreach ($nsClassesFQNs as $nsClassFQN) {
@@ -321,13 +320,15 @@ class PhpUseDirectives implements Countable, IteratorAggregate
{
private $use;
private $aliases;
private $reverseAliases;
private $namespace;
public function __construct(PhpNamespace $namespace)
{
$this->namespace = $namespace;
$this->use = array();
$this->aliases = array();
$this->namespace = $namespace;
$this->reverseAliases = array();
}
public function add($use, $as = null)
@@ -336,8 +337,35 @@ class PhpUseDirectives implements Countable, IteratorAggregate
return;
}
$rename = null;
$this->use[] = $use;
$this->aliases[$as ?: PhpClass::extractName($use)] = $use;
$aliasedClassName = $as ?: PhpClass::extractName($use);
if (isset($this->aliases[$aliasedClassName])) {
$parentNs = $this->getParentNamespace();
if ($parentNs && false !== $pos = strrpos($parentNs, '\\')) {
$parentNs = substr($parentNs, $pos);
}
$newAlias = "{$parentNs}_{$aliasedClassName}";
$rename = (object) array(
'namespace' => $this->namespace,
'from' => $aliasedClassName,
'to' => $newAlias,
);
$this->aliases[$newAlias] = $use;
$as = $newAlias;
} else {
$this->aliases[$aliasedClassName] = $use;
}
if ($as !== null) {
$this->reverseAliases[$use] = $as;
}
return $rename;
}
public function getList()
@@ -352,7 +380,15 @@ class PhpUseDirectives implements Countable, IteratorAggregate
public function getPhpCode()
{
$reducer = function($str, $use) { return $str .= "use $use;\n"; };
$reverseAliases = $this->reverseAliases;
$reducer = function ($str, $use) use ($reverseAliases) {
if (isset($reverseAliases[$use])) {
return $str .= "use $use as {$reverseAliases[$use]};\n";
} else {
return $str .= "use $use;\n";
}
};
return array_reduce($this->getList(), $reducer, '');
}
@@ -362,6 +398,15 @@ class PhpUseDirectives implements Countable, IteratorAggregate
return $this->namespace;
}
public function getParentNamespace()
{
if (false !== $pos = strrpos($this->namespace, '\\')) {
return substr($this->namespace, 0, $pos);
}
return '';
}
public function getFQN($className)
{
if (($nsSepFirst = strpos($className, '\\')) === false) {
@@ -428,10 +473,19 @@ LICENSE;
private function extractData()
{
$renames = array();
$useDirectives = $this->getNamespace()->getUseDirectives();
$useExtractor = function($m) use($useDirectives) {
$useDirectives->add(($namespacedPath = $m[1]));
$useExtractor = function ($m) use ($useDirectives, &$renames) {
array_shift($m);
if (isset($m[1])) {
$m[1] = str_replace(" as ", '', $m[1]);
}
if ($rename = call_user_func_array(array($useDirectives, 'add'), $m)) {
$renames[] = $rename;
}
};
$classBuffer = stream_get_contents(fopen($this->getFile()->getPathname(), 'r'));
@@ -443,6 +497,10 @@ LICENSE;
$classBuffer = preg_replace('/namespace\s+[\w\d_\\\\]+;\s?/', '', $classBuffer);
$classBuffer = preg_replace_callback('/use\s+([\w\d_\\\\]+)(\s+as\s+.*)?;\s?\n?/', $useExtractor, $classBuffer);
foreach ($renames as $rename) {
$classBuffer = str_replace($rename->from, $rename->to, $classBuffer);
}
$this->body = trim($classBuffer);
$this->extractHierarchy();
@@ -453,7 +511,7 @@ LICENSE;
$implements = array();
$extends = array();
$extractor = function($iterator, $callback) {
$extractor = function ($iterator, $callback) {
$className = '';
$iterator->seek($iterator->key() + 1);
@@ -464,8 +522,7 @@ LICENSE;
if (preg_match('/\s?,\s?/', $token)) {
$callback(trim($className));
$className = '';
}
else if ($token == '{') {
} else if ($token == '{') {
$callback(trim($className));
return;
}
@@ -510,13 +567,13 @@ LICENSE;
break;
case T_IMPLEMENTS:
$extractor($iterator, function($fqn) use (&$implements) {
$extractor($iterator, function ($fqn) use (&$implements) {
$implements[] = $fqn;
});
break;
case T_EXTENDS:
$extractor($iterator, function($fqn) use (&$extends) {
$extractor($iterator, function ($fqn) use (&$extends) {
$extends[] = $fqn;
});
break;
+17 -2
View File
@@ -5,6 +5,9 @@
"keywords": ["nosql", "redis", "predis"],
"homepage": "http://github.com/nrk/predis",
"license": "MIT",
"support": {
"issues": "https://github.com/nrk/predis/issues"
},
"authors": [
{
"name": "Daniele Alessandri",
@@ -13,9 +16,21 @@
}
],
"require": {
"php": ">=5.3.0"
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"suggest": {
"ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol",
"ext-curl": "Allows access to Webdis when paired with phpiredis"
},
"autoload": {
"psr-0": {"Predis": "lib/"}
"psr-4": {"Predis\\": "src/"}
},
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
}
}
-82
View File
@@ -1,82 +0,0 @@
<?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.
*/
require 'SharedConfigurations.php';
// Developers can customize the distribution strategy used by the client
// to distribute keys among a cluster of servers simply by creating a class
// that implements the Predis\Distribution\IDistributionStrategy interface.
use Predis\Distribution\IDistributionStrategy;
use Predis\Network\PredisCluster;
class NaiveDistributionStrategy implements IDistributionStrategy
{
private $nodes;
private $nodesCount;
public function __constructor()
{
$this->nodes = array();
$this->nodesCount = 0;
}
public function add($node, $weight = null)
{
$this->nodes[] = $node;
$this->nodesCount++;
}
public function remove($node)
{
$this->nodes = array_filter($this->nodes, function($n) use($node) {
return $n !== $node;
});
$this->nodesCount = count($this->nodes);
}
public function get($key)
{
$count = $this->nodesCount;
if ($count === 0) {
throw new RuntimeException('No connections');
}
return $this->nodes[$count > 1 ? abs(crc32($key) % $count) : 0];
}
public function generateKey($value)
{
return crc32($value);
}
}
$options = array(
'cluster' => function() {
$distributor = new NaiveDistributionStrategy();
return new PredisCluster($distributor);
},
);
$client = new Predis\Client($multiple_servers, $options);
for ($i = 0; $i < 100; $i++) {
$client->set("key:$i", str_pad($i, 4, '0', 0));
$client->get("key:$i");
}
$server1 = $client->getClientFor('first')->info();
$server2 = $client->getClientFor('second')->info();
printf("Server '%s' has %d keys while server '%s' has %d keys.\n",
'first', $server1['db15']['keys'], 'second', $server2['db15']['keys']
);
-37
View File
@@ -1,37 +0,0 @@
<?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.
*/
require 'SharedConfigurations.php';
// Predis ships with a KeyPrefixProcessor class that is used to transparently
// prefix each key before sending commands to Redis, even for complex commands
// such as SORT, ZUNIONSTORE and ZINTERSTORE. Key prefixes are useful to create
// user-level namespaces for you keyspace, thus eliminating the need for separate
// logical databases.
$client = new Predis\Client($single_server, array('prefix' => 'nrk:'));
$client->mset(array('foo' => 'bar', 'lol' => 'wut'));
var_dump($client->mget('foo', 'lol'));
/*
array(2) {
[0]=> string(3) "bar"
[1]=> string(3) "wut"
}
*/
var_dump($client->keys('*'));
/*
array(2) {
[0]=> string(7) "nrk:foo"
[1]=> string(7) "nrk:lol"
}
*/
-38
View File
@@ -1,38 +0,0 @@
<?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.
*/
require 'SharedConfigurations.php';
// redis can set keys and their relative values in one go
// using MSET, then the same values can be retrieved with
// a single command using MGET.
$mkv = array(
'usr:0001' => 'First user',
'usr:0002' => 'Second user',
'usr:0003' => 'Third user'
);
$client = new Predis\Client($single_server);
$client->mset($mkv);
$retval = $client->mget(array_keys($mkv));
print_r($retval);
/* OUTPUT:
Array
(
[0] => First user
[1] => Second user
[2] => Third user
)
*/
-49
View File
@@ -1,49 +0,0 @@
<?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.
*/
require 'SharedConfigurations.php';
// This example will not work with versions of Redis < 2.6.
//
// Additionally to the EVAL command defined in the current development profile, the new
// Predis\Commands\ScriptedCommand base class can be used to build an higher abstraction
// for our "scripted" commands so that they will appear just like any other command on
// the client-side. This is a quick example used to implement INCREX.
use Predis\Commands\ScriptedCommand;
class IncrementExistingKey extends ScriptedCommand
{
public function getKeysCount()
{
return 1;
}
public function getScript()
{
return
<<<LUA
local cmd = redis.call
if cmd('exists', KEYS[1]) == 1 then
return cmd('incr', KEYS[1])
end
LUA;
}
}
$client = new Predis\Client($single_server, 'dev');
$client->getProfile()->defineCommand('increx', 'IncrementExistingKey');
$client->set('foo', 10);
var_dump($client->increx('foo')); // int(11)
var_dump($client->increx('bar')); // NULL
-25
View File
@@ -1,25 +0,0 @@
<?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.
*/
require 'SharedConfigurations.php';
// simple set and get scenario
$client = new Predis\Client($single_server);
$client->set('library', 'predis');
$retval = $client->get('library');
var_dump($retval);
/* OUTPUT
string(6) "predis"
*/
+117
View File
@@ -0,0 +1,117 @@
<?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.
*/
require __DIR__.'/shared.php';
// Developers can implement Predis\Distribution\DistributorInterface to create
// their own distributors used by the client to distribute keys among a cluster
// of servers.
use Predis\Cluster\PredisStrategy;
use Predis\Cluster\Distributor\DistributorInterface;
use Predis\Cluster\Hash\HashGeneratorInterface;
use Predis\Connection\Aggregate\PredisCluster;
class NaiveDistributor implements DistributorInterface, HashGeneratorInterface
{
private $nodes;
private $nodesCount;
public function __construct()
{
$this->nodes = array();
$this->nodesCount = 0;
}
public function add($node, $weight = null)
{
$this->nodes[] = $node;
$this->nodesCount++;
}
public function remove($node)
{
$this->nodes = array_filter($this->nodes, function ($n) use ($node) {
return $n !== $node;
});
$this->nodesCount = count($this->nodes);
}
public function getSlot($hash)
{
return $this->nodesCount > 1 ? abs($hash % $this->nodesCount) : 0;
}
public function getBySlot($slot)
{
return isset($this->nodes[$slot]) ? $this->nodes[$slot] : null;
}
public function getByHash($hash)
{
if (!$this->nodesCount) {
throw new RuntimeException('No connections.');
}
$slot = $this->getSlot($hash);
$node = $this->getBySlot($slot);
return $node;
}
public function get($value)
{
$hash = $this->hash($value);
$node = $this->getByHash($hash);
return $node;
}
public function hash($value)
{
return crc32($value);
}
public function getHashGenerator()
{
return $this;
}
}
$options = array(
'cluster' => function () {
$distributor = new NaiveDistributor();
$strategy = new PredisStrategy($distributor);
$cluster = new PredisCluster($strategy);
return $cluster;
},
);
$client = new Predis\Client($multiple_servers, $options);
for ($i = 0; $i < 100; $i++) {
$client->set("key:$i", str_pad($i, 4, '0', 0));
$client->get("key:$i");
}
$server1 = $client->getClientFor('first')->info();
$server2 = $client->getClientFor('second')->info();
if (isset($server1['Keyspace'], $server2['Keyspace'])) {
$server1 = $server1['Keyspace'];
$server2 = $server2['Keyspace'];
}
printf("Server '%s' has %d keys while server '%s' has %d keys.\n",
'first', $server1['db15']['keys'], 'second', $server2['db15']['keys']
);
@@ -9,11 +9,15 @@
* file that was distributed with this source code.
*/
require 'SharedConfigurations.php';
require __DIR__.'/shared.php';
use Predis\ConnectionParameters;
use Predis\Commands\ICommand;
use Predis\Network\StreamConnection;
// This is an example of how you can easily extend an existing connection class
// and trace the execution of commands for debugging purposes. This can be quite
// useful as a starting poing to understand how your application interacts with
// Redis.
use Predis\Command\CommandInterface;
use Predis\Connection\StreamConnection;
class SimpleDebuggableConnection extends StreamConnection
{
@@ -27,9 +31,9 @@ class SimpleDebuggableConnection extends StreamConnection
parent::connect();
}
private function storeDebug(ICommand $command, $direction)
private function storeDebug(CommandInterface $command, $direction)
{
$firtsArg = $command->getArgument(0);
$firtsArg = $command->getArgument(0);
$timestamp = round(microtime(true) - $this->tstart, 4);
$debug = $command->getId();
@@ -40,19 +44,19 @@ class SimpleDebuggableConnection extends StreamConnection
$this->debugBuffer[] = $debug;
}
public function writeCommand(ICommand $command)
public function writeRequest(CommandInterface $command)
{
parent::writeCommand($command);
parent::writeRequest($command);
$this->storeDebug($command, '->');
}
public function readResponse(ICommand $command)
public function readResponse(CommandInterface $command)
{
$reply = parent::readResponse($command);
$response = parent::readResponse($command);
$this->storeDebug($command, '<-');
return $reply;
return $response;
}
public function getDebugBuffer()
@@ -72,18 +76,17 @@ $client->set('foo', 'bar');
$client->get('foo');
$client->info();
print_r($client->getConnection()->getDebugBuffer());
var_export($client->getConnection()->getDebugBuffer());
/* OUTPUT:
Array
(
[0] => SELECT 15 -> 127.0.0.1:6379 [0.0008s]
[1] => SELECT 15 <- 127.0.0.1:6379 [0.0012s]
[2] => SET foo -> 127.0.0.1:6379 [0.0014s]
[3] => SET foo <- 127.0.0.1:6379 [0.0014s]
[4] => GET foo -> 127.0.0.1:6379 [0.0016s]
[5] => GET foo <- 127.0.0.1:6379 [0.0018s]
[6] => INFO -> 127.0.0.1:6379 [0.002s]
[7] => INFO <- 127.0.0.1:6379 [0.0025s]
array (
0 => 'SELECT 15 -> 127.0.0.1:6379 [0.0008s]',
1 => 'SELECT 15 <- 127.0.0.1:6379 [0.001s]',
2 => 'SET foo -> 127.0.0.1:6379 [0.001s]',
3 => 'SET foo <- 127.0.0.1:6379 [0.0011s]',
4 => 'GET foo -> 127.0.0.1:6379 [0.0013s]',
5 => 'GET foo <- 127.0.0.1:6379 [0.0015s]',
6 => 'INFO -> 127.0.0.1:6379 [0.0019s]',
7 => 'INFO <- 127.0.0.1:6379 [0.0022s]',
)
*/
@@ -9,28 +9,29 @@
* file that was distributed with this source code.
*/
require 'SharedConfigurations.php';
require __DIR__.'/shared.php';
/*
This is a basic example on how to use the Predis\DispatcherLoop class.
// This is a basic example on how to use the Predis\DispatcherLoop class.
//
// To see this example in action you can just use redis-cli and publish some
// messages to the 'events' and 'control' channel, e.g.:
To see this example in action you can just use redis-cli and publish some
messages to the 'events' and 'control' channel, e.g.:
./redis-cli
PUBLISH events first
PUBLISH events second
PUBLISH events third
PUBLISH control terminate_dispatcher
*/
// ./redis-cli
// PUBLISH events first
// PUBLISH events second
// PUBLISH events third
// PUBLISH control terminate_dispatcher
// Create a client and disable r/w timeout on the socket
$client = new Predis\Client($single_server + array('read_write_timeout' => 0));
// Create a Predis\DispatcherLoop instance and attach a bunch of callbacks.
$dispatcher = new Predis\PubSub\DispatcherLoop($client);
// Return an initialized PubSub consumer instance from the client.
$pubsub = $client->pubSubLoop();
// Demonstrate how to use a callable class as a callback for Predis\DispatcherLoop.
// Create a dispatcher loop instance and attach a bunch of callbacks.
$dispatcher = new Predis\PubSub\DispatcherLoop($pubsub);
// Demonstrate how to use a callable class as a callback for the dispatcher loop.
class EventsListener implements Countable
{
private $events;
@@ -60,7 +61,7 @@ class EventsListener implements Countable
$dispatcher->attachCallback('events', ($events = new EventsListener()));
// Attach a function to control the dispatcher loop termination with a message.
$dispatcher->attachCallback('control', function($payload) use ($dispatcher) {
$dispatcher->attachCallback('control', function ($payload) use ($dispatcher) {
if ($payload === 'terminate_dispatcher') {
$dispatcher->stop();
}
@@ -71,8 +72,8 @@ $dispatcher->attachCallback('control', function($payload) use ($dispatcher) {
$dispatcher->run();
// Display our achievements!
echo "We received {$events->count()} messages!\n";
echo "We received {$events->count()} messages!", PHP_EOL;
// Say goodbye :-)
$info = $client->info();
print_r("Goodbye from Redis v{$info['redis_version']}!\n");
$version = redis_version($client->info());
echo "Goodbye from Redis $version!", PHP_EOL;
+57
View File
@@ -0,0 +1,57 @@
<?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.
*/
require __DIR__.'/shared.php';
$client = new Predis\Client($single_server);
// Plain old SET and GET example...
$client->set('library', 'predis');
$response = $client->get('library');
var_export($response); echo PHP_EOL;
/* OUTPUT: 'predis' */
// Redis has the MSET and MGET commands to set or get multiple keys in one go,
// cases like this Predis accepts arguments for variadic commands both as a list
// of arguments or an array containing all of the keys and/or values.
$mkv = array(
'uid:0001' => '1st user',
'uid:0002' => '2nd user',
'uid:0003' => '3rd user'
);
$client->mset($mkv);
$response = $client->mget(array_keys($mkv));
var_export($response); echo PHP_EOL;
/* OUTPUT:
array (
0 => '1st user',
1 => '2nd user',
2 => '3rd user',
) */
// Predis can also send "raw" commands to Redis. The difference between sending
// commands to Redis the usual way and the "raw" way is that in the latter case
// their arguments are not filtered nor responses coming from Redis are parsed.
$response = $client->executeRaw(array(
'MGET', 'uid:0001', 'uid:0002', 'uid:0003'
));
var_export($response); echo PHP_EOL;
/* OUTPUT:
array (
0 => '1st user',
1 => '2nd user',
2 => '3rd user',
) */
+36
View File
@@ -0,0 +1,36 @@
<?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.
*/
require __DIR__.'/shared.php';
// Predis can prefix keys found in commands arguments before sending commands to
// Redis, even for complex commands such as SORT, ZUNIONSTORE and ZINTERSTORE.
// Prefixing keys can be useful to create user-level namespaces for you keyspace
// thus reducing the need for separate logical databases in certain scenarios.
$client = new Predis\Client($single_server, array('prefix' => 'nrk:'));
$client->mset(array('foo' => 'bar', 'lol' => 'wut'));
var_export($client->mget('foo', 'lol'));
/*
array (
0 => 'bar',
1 => 'wut',
)
*/
var_export($client->keys('*'));
/*
array (
0 => 'nrk:foo',
1 => 'nrk:lol',
)
*/
+71
View File
@@ -0,0 +1,71 @@
<?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.
*/
require __DIR__.'/shared.php';
// This example will not work with versions of Redis < 2.6.
//
// Additionally to the EVAL command defined in the current development profile,
// the Predis\Command\ScriptCommand class can be used to build an higher level
// abstraction for "scriptable" commands so that they will appear just like any
// other command on the client-side. This is a quick example used to implement
// INCREX.
use Predis\Command\ScriptCommand;
class IncrementExistingKeysBy extends ScriptCommand
{
public function getKeysCount()
{
// Tell Predis to use all the arguments but the last one as arguments
// for KEYS. The last one will be used to populate ARGV.
return -1;
}
public function getScript()
{
return <<<LUA
local cmd, insert = redis.call, table.insert
local increment, results = ARGV[1], { }
for idx, key in ipairs(KEYS) do
if cmd('exists', key) == 1 then
insert(results, idx, cmd('incrby', key, increment))
else
insert(results, idx, false)
end
end
return results
LUA;
}
}
$client = new Predis\Client($single_server, array(
'profile' => function ($options) {
$profile = $options->getDefault('profile');
$profile->defineCommand('increxby', 'IncrementExistingKeysBy');
return $profile;
}
));
$client->mset('foo', 10, 'foobar', 100);
var_export($client->increxby('foo', 'foofoo', 'foobar', 50));
/*
array (
0 => 60,
1 => NULL,
2 => 150,
)
*/
@@ -9,10 +9,10 @@
* file that was distributed with this source code.
*/
require 'SharedConfigurations.php';
require __DIR__.'/shared.php';
// This is a basic example on how to use the Predis\MonitorContext class.
// You can use redis-cli to send commands to the same Redis instance your client is
// This is a basic example on how to use the Predis\Monitor\Consumer class. You
// can use redis-cli to send commands to the same Redis instance your client is
// connected to, and then type "ECHO QUIT_MONITOR" in redis-cli when you want to
// exit the monitor loop and terminate this script in a graceful way.
@@ -25,20 +25,20 @@ $timestamp = new DateTime();
foreach (($monitor = $client->monitor()) as $event) {
$timestamp->setTimestamp((int) $event->timestamp);
// If we notice a ECHO command with the message QUIT_MONITOR, we close the
// monitor context and then break the loop.
// If we notice a ECHO command with the message QUIT_MONITOR, we stop the
// monitor consumer and then break the loop.
if ($event->command === 'ECHO' && $event->arguments === '"QUIT_MONITOR"') {
echo "Exiting the monitor loop...\n";
$monitor->closeContext();
echo "Exiting the monitor loop...", PHP_EOL;
$monitor->stop();
break;
}
echo "* Received {$event->command} on DB {$event->database} at {$timestamp->format(DateTime::W3C)}\n";
echo "* Received {$event->command} on DB {$event->database} at {$timestamp->format(DateTime::W3C)}", PHP_EOL;
if (isset($event->arguments)) {
echo " Arguments: {$event->arguments}\n";
echo " Arguments: {$event->arguments}", PHP_EOL;
}
}
// Say goodbye :-)
$info = $client->info();
print_r("Goodbye from Redis v{$info['redis_version']}!\n");
$version = redis_version($client->info());
echo "Goodbye from Redis $version!", PHP_EOL;
@@ -9,15 +9,15 @@
* file that was distributed with this source code.
*/
require 'SharedConfigurations.php';
require __DIR__.'/shared.php';
// When you have a whole set of consecutive commands to send to
// a redis server, you can use a pipeline to improve performances.
// When you have a whole set of consecutive commands to send to a redis server,
// you can use a pipeline to dramatically improve performances. Pipelines can
// greatly reduce the effects of network round-trips.
$client = new Predis\Client($single_server);
$replies = $client->pipeline(function($pipe) {
$pipe->ping();
$responses = $client->pipeline(function ($pipe) {
$pipe->flushdb();
$pipe->incrby('counter', 10);
$pipe->incrby('counter', 30);
@@ -26,22 +26,20 @@ $replies = $client->pipeline(function($pipe) {
$pipe->mget('does_not_exist', 'counter');
});
print_r($replies);
var_export($responses);
/* OUTPUT:
Array
(
[0] => 1
[1] => 1
[2] => 10
[3] => 40
[4] => 1
[5] => 40
[6] => Array
(
[0] =>
[1] => 40
)
array (
0 => Predis\Response\Status::__set_state(array(
'payload' => 'OK',
)),
1 => 10,
2 => 40,
3 => true,
4 => '40',
5 => array (
0 => NULL,
1 => '40',
),
)
*/
@@ -9,16 +9,16 @@
* file that was distributed with this source code.
*/
require 'SharedConfigurations.php';
require __DIR__.'/shared.php';
// Redis 2.0 features new commands that allow clients to subscribe for
// events published on certain channels (PUBSUB).
// Starting from Redis 2.0 clients can subscribe and listen for events published
// on certain channels using a Publish/Subscribe (PUB/SUB) approach.
// Create a client and disable r/w timeout on the socket
$client = new Predis\Client($single_server + array('read_write_timeout' => 0));
// Initialize a new pubsub context
$pubsub = $client->pubSub();
// Initialize a new pubsub consumer.
$pubsub = $client->pubSubLoop();
// Subscribe to your channels
$pubsub->subscribe('control_channel', 'notifications');
@@ -30,32 +30,30 @@ $pubsub->subscribe('control_channel', 'notifications');
foreach ($pubsub as $message) {
switch ($message->kind) {
case 'subscribe':
echo "Subscribed to {$message->channel}\n";
echo "Subscribed to {$message->channel}", PHP_EOL;
break;
case 'message':
if ($message->channel == 'control_channel') {
if ($message->payload == 'quit_loop') {
echo "Aborting pubsub loop...\n";
echo "Aborting pubsub loop...", PHP_EOL;
$pubsub->unsubscribe();
} else {
echo "Received an unrecognized command: {$message->payload}.", PHP_EOL;
}
else {
echo "Received an unrecognized command: {$message->payload}.\n";
}
}
else {
echo "Received the following message from {$message->channel}:\n",
" {$message->payload}\n\n";
} else {
echo "Received the following message from {$message->channel}:",
PHP_EOL, " {$message->payload}", PHP_EOL, PHP_EOL;
}
break;
}
}
// Always unset the pubsub context instance when you are done! The
// Always unset the pubsub consumer instance when you are done! The
// class destructor will take care of cleanups and prevent protocol
// desynchronizations between the client and the server.
unset($pubsub);
// Say goodbye :-)
$info = $client->info();
print_r("Goodbye from Redis v{$info['redis_version']}!\n");
$version = redis_version($client->info());
echo "Goodbye from Redis $version!", PHP_EOL;
+99
View File
@@ -0,0 +1,99 @@
<?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.
*/
require __DIR__.'/shared.php';
use Predis\Collection\Iterator;
// Starting from Redis 2.8, clients can iterate incrementally over collections
// without blocking the server like it happens when a command such as KEYS is
// executed on a Redis instance storing millions of keys. These commands are:
//
// - SCAN (iterates over the keyspace)
// - SSCAN (iterates over members of a set)
// - ZSCAN (iterates over members and ranks of a sorted set)
// - HSCAN (iterates over fields and values of an hash).
// Predis provides a specialized abstraction for each command based on standard
// SPL iterators making it possible to easily consume SCAN-based iterations in
// your PHP code.
//
// See http://redis.io/commands/scan for more details.
//
// Create a client using `2.8` as a server profile (needs Redis 2.8!)
$client = new Predis\Client($single_server, array('profile' => '2.8'));
// Prepare some keys for our example
$client->del('predis:set', 'predis:zset', 'predis:hash');
for ($i = 0; $i < 5; $i++) {
$client->sadd('predis:set', "member:$i");
$client->zadd('predis:zset', -$i, "member:$i");
$client->hset('predis:hash', "field:$i", "value:$i");
}
// === Keyspace iterator based on SCAN ===
echo 'Scan the keyspace matching only our prefixed keys:', PHP_EOL;
foreach (new Iterator\Keyspace($client, 'predis:*') as $key) {
echo " - $key", PHP_EOL;
}
/* OUTPUT
Scan the keyspace matching only our prefixed keys:
- predis:zset
- predis:set
- predis:hash
*/
// === Set iterator based on SSCAN ===
echo 'Scan members of `predis:set`:', PHP_EOL;
foreach (new Iterator\SetKey($client, 'predis:set') as $member) {
echo " - $member", PHP_EOL;
}
/* OUTPUT
Scan members of `predis:set`:
- member:1
- member:4
- member:0
- member:3
- member:2
*/
// === Sorted set iterator based on ZSCAN ===
echo 'Scan members and ranks of `predis:zset`:', PHP_EOL;
foreach (new Iterator\SortedSetKey($client, 'predis:zset') as $member => $rank) {
echo " - $member [rank: $rank]", PHP_EOL;
}
/* OUTPUT
Scan members and ranks of `predis:zset`:
- member:4 [rank: -4]
- member:3 [rank: -3]
- member:2 [rank: -2]
- member:1 [rank: -1]
- member:0 [rank: 0]
*/
// === Hash iterator based on HSCAN ===
echo 'Scan fields and values of `predis:hash`:', PHP_EOL;
foreach (new Iterator\HashKey($client, 'predis:hash') as $field => $value) {
echo " - $field => $value", PHP_EOL;
}
/* OUTPUT
Scan fields and values of `predis:hash`:
- field:0 => value:0
- field:1 => value:1
- field:2 => value:2
- field:3 => value:3
- field:4 => value:4
*/
+85
View File
@@ -0,0 +1,85 @@
<?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.
*/
require __DIR__.'/shared.php';
// Predis allows to set Lua scripts as read-only operations for replication.
// This works for both EVAL and EVALSHA and also for the client-side abstraction
// built upon them (Predis\Command\ScriptCommand). This example shows a slightly
// more complex configuration that injects a new script command in the server
// profile used by the new client instance and marks it marks it as a read-only
// operation for replication so that it will be executed on slaves.
use Predis\Command\ScriptCommand;
use Predis\Connection\Aggregate\MasterSlaveReplication;
use Predis\Replication\ReplicationStrategy;
// ------------------------------------------------------------------------- //
// Define a new script command that returns all the fields of a variable number
// of hashes with a single roundtrip.
class HashMultipleGetAll extends ScriptCommand
{
const BODY = <<<LUA
local hashes = {}
for _, key in pairs(KEYS) do
table.insert(hashes, key)
table.insert(hashes, redis.call('hgetall', key))
end
return hashes
LUA;
public function getScript()
{
return self::BODY;
}
}
// ------------------------------------------------------------------------- //
$parameters = array(
'tcp://127.0.0.1:6379/?alias=master',
'tcp://127.0.0.1:6380/?alias=slave',
);
$options = array(
'profile' => function ($options, $option) {
$profile = $options->getDefault($option);
$profile->defineCommand('hmgetall', 'HashMultipleGetAll');
return $profile;
},
'replication' => function () {
$strategy = new ReplicationStrategy();
$strategy->setScriptReadOnly(HashMultipleGetAll::BODY);
$replication = new MasterSlaveReplication($strategy);
return $replication;
},
);
// ------------------------------------------------------------------------- //
$client = new Predis\Client($parameters, $options);
// Execute the following commands on the master server using redis-cli:
// $ ./redis-cli HMSET metavars foo bar hoge piyo
// $ ./redis-cli HMSET servers master host1 slave host2
$hashes = $client->hmgetall('metavars', 'servers');
$replication = $client->getConnection();
$stillOnSlave = $replication->getCurrent() === $replication->getConnectionById('slave');
echo "Is still on slave? ", $stillOnSlave ? 'YES!' : 'NO!', PHP_EOL;
var_export($hashes);
+52
View File
@@ -0,0 +1,52 @@
<?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.
*/
require __DIR__.'/shared.php';
// Predis supports master / slave replication scenarios where write operations
// are performed on the master server and read operations are executed against
// one of the slaves. The behavior of commands or EVAL scripts can be customized
// at will. As soon as a write operation is performed the client switches to the
// master server for all the subsequent requests (either reads and writes).
//
// This example must be executed using the second Redis server configured as the
// slave of the first one (see the "SLAVEOF" command).
//
$parameters = array(
'tcp://127.0.0.1:6379?database=15&alias=master',
'tcp://127.0.0.1:6380?database=15&alias=slave',
);
$options = array('replication' => true);
$client = new Predis\Client($parameters, $options);
// Read operation.
$exists = $client->exists('foo') ? 'yes' : 'no';
$current = $client->getConnection()->getCurrent()->getParameters();
echo "Does 'foo' exist on {$current->alias}? $exists.", PHP_EOL;
// Write operation.
$client->set('foo', 'bar');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "Now 'foo' has been set to 'bar' on {$current->alias}!", PHP_EOL;
// Read operation.
$bar = $client->get('foo');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "We fetched 'foo' from {$current->alias} and its value is '$bar'.", PHP_EOL;
/* OUTPUT:
Does 'foo' exist on slave? yes.
Now 'foo' has been set to 'bar' on master!
We fetched 'foo' from master and its value is 'bar'.
*/
+52
View File
@@ -0,0 +1,52 @@
<?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.
*/
require __DIR__.'/shared.php';
// This example demonstrates how to use Predis to save PHP sessions on Redis.
//
// The value of `session.gc_maxlifetime` in `php.ini` will be used by default as
// the TTL for keys holding session data but this value can be overridden when
// creating the session handler instance using the `gc_maxlifetime` option.
//
// NOTE: this class requires PHP >= 5.4 but can be used on PHP 5.3 if a polyfill
// for SessionHandlerInterface is provided either by you or an external package
// like `symfony/http-foundation`.
//
// See http://www.php.net/class.sessionhandlerinterface.php for more details.
//
if (!interface_exists('SessionHandlerInterface')) {
die("ATTENTION: the session handler implemented by Predis requires PHP >= 5.4.0 ".
"or a polyfill for SessionHandlerInterface provided by an external package.\n");
}
// Instantiate a new client just like you would normally do. Using a prefix for
// keys will effectively prefix all session keys with the specified string.
$client = new Predis\Client($single_server, array('prefix' => 'sessions:'));
// Set `gc_maxlifetime` to specify a time-to-live of 5 seconds for session keys.
$handler = new Predis\Session\Handler($client, array('gc_maxlifetime' => 5));
// Register the session handler.
$handler->register();
// We just set a fixed session ID only 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']}", PHP_EOL;
} else {
$_SESSION['foo'] = $value = mt_rand();
echo "Empty session, `foo` has been set with $value", PHP_EOL;
}
@@ -11,6 +11,17 @@
require __DIR__.'/../autoload.php';
function redis_version($info)
{
if (isset($info['Server']['redis_version'])) {
return $info['Server']['redis_version'];
} elseif (isset($info['redis_version'])) {
return $info['redis_version'];
} else {
return 'unknown version';
}
}
$single_server = array(
'host' => '127.0.0.1',
'port' => 6379,
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
require 'SharedConfigurations.php';
require __DIR__.'/shared.php';
// This is an implementation of an atomic client-side ZPOP using the support for
// check-and-set (CAS) operations with MULTI/EXEC transactions, as described in
@@ -19,9 +19,10 @@ require 'SharedConfigurations.php';
//
// ./redis-cli
// SELECT 15
// ZADD zset 1 a
// ZADD zset 2 b
// ZADD zset 3 c
// ZADD zset 1 a 2 b 3 c
//
// Then execute this script four times and see its output.
//
function zpop($client, $key)
{
@@ -33,7 +34,7 @@ function zpop($client, $key)
// which the client bails out with an exception.
);
$client->multiExec($options, function($tx) use ($key, &$element) {
$client->transaction($options, function ($tx) use ($key, &$element) {
@list($element) = $tx->zrange($key, 0, 0);
if (isset($element)) {
@@ -48,4 +49,4 @@ function zpop($client, $key)
$client = new Predis\Client($single_server);
$zpopped = zpop($client, 'zset');
echo isset($zpopped) ? "ZPOPed $zpopped" : "Nothing to ZPOP!", "\n";
echo isset($zpopped) ? "ZPOPed $zpopped" : "Nothing to ZPOP!", PHP_EOL;
-57
View File
@@ -1,57 +0,0 @@
<?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;
/**
* Implements a lightweight PSR-0 compliant autoloader.
*
* @author Eric Naeseth <eric@thumbtack.com>
*/
class Autoloader
{
private $baseDir;
private $prefix;
/**
* @param string $baseDirectory Base directory where the source files are located.
*/
public function __construct($baseDirectory = null)
{
$this->baseDir = $baseDirectory ?: dirname(__FILE__);
$this->prefix = __NAMESPACE__ . '\\';
}
/**
* Registers the autoloader class with the PHP SPL autoloader.
*/
public static function register()
{
spl_autoload_register(array(new self, 'autoload'));
}
/**
* Loads a class from a file using its fully qualified name.
*
* @param string $className Fully qualified name of a class.
*/
public function autoload($className)
{
if (0 !== strpos($className, $this->prefix)) {
return;
}
$relativeClassName = substr($className, strlen($this->prefix));
$classNameParts = explode('\\', $relativeClassName);
require_once $this->baseDir.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $classNameParts).'.php';
}
}
-393
View File
@@ -1,393 +0,0 @@
<?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;
use Predis\Commands\ICommand;
use Predis\Options\IClientOptions;
use Predis\Network\IConnection;
use Predis\Network\IConnectionSingle;
use Predis\Profiles\IServerProfile;
use Predis\Options\ClientOptions;
use Predis\Profiles\ServerProfile;
use Predis\PubSub\PubSubContext;
use Predis\Pipeline\PipelineContext;
use Predis\Transaction\MultiExecContext;
/**
* Main class that exposes the most high-level interface to interact with Redis.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class Client
{
const VERSION = '0.7.0';
private $options;
private $profile;
private $connection;
private $connections;
/**
* Initializes a new client with optional connection parameters and client options.
*
* @param mixed $parameters Connection parameters for one or multiple servers.
* @param mixed $options Options that specify certain behaviours for the client.
*/
public function __construct($parameters = null, $options = null)
{
$options = $this->filterOptions($options);
$this->options = $options;
$this->profile = $options->profile;
$this->connections = $options->connections;
$this->connection = $this->initializeConnection($parameters);
}
/**
* Creates an instance of Predis\Options\ClientOptions from various types of
* arguments (string, array, Predis\Profiles\ServerProfile) or returns the
* passed object if it is an instance of Predis\Options\ClientOptions.
*
* @param mixed $options Client options.
* @return ClientOptions
*/
protected function filterOptions($options)
{
if ($options === null) {
return new ClientOptions();
}
if (is_array($options)) {
return new ClientOptions($options);
}
if ($options instanceof IClientOptions) {
return $options;
}
if ($options instanceof IServerProfile || is_string($options)) {
return new ClientOptions(array('profile' => $options));
}
throw new \InvalidArgumentException("Invalid type for client options");
}
/**
* Initializes one or multiple connection (cluster) objects from various
* types of arguments (string, array) or returns the passed object if it
* implements the Predis\Network\IConnection interface.
*
* @param mixed $parameters Connection parameters or instance.
* @return IConnection
*/
protected function initializeConnection($parameters)
{
if ($parameters instanceof IConnection) {
return $parameters;
}
if (is_array($parameters) && isset($parameters[0])) {
return $this->connections->createCluster($this->options->cluster, $parameters, $this->profile);
}
return $this->connections->create($parameters, $this->profile);
}
/**
* Returns the server profile used by the client.
*
* @return IServerProfile
*/
public function getProfile()
{
return $this->profile;
}
/**
* Returns the client options specified upon initialization.
*
* @return ClientOptions
*/
public function getOptions()
{
return $this->options;
}
/**
* Returns the connection factory object used by the client.
*
* @return IConnectionFactory
*/
public function getConnectionFactory()
{
return $this->connections;
}
/**
* Returns a new instance of a client for the specified connection when the
* client is connected to a cluster. The new instance will use the same
* options of the original client.
*
* @return Client
*/
public function getClientFor($connectionAlias)
{
if (($connection = $this->getConnection($connectionAlias)) === null) {
throw new \InvalidArgumentException("Invalid connection alias: '$connectionAlias'");
}
return new Client($connection, $this->options);
}
/**
* Opens the connection to the server.
*/
public function connect()
{
$this->connection->connect();
}
/**
* Disconnects from the server.
*/
public function disconnect()
{
$this->connection->disconnect();
}
/**
* Disconnects from the server.
*
* This method is an alias of disconnect().
*/
public function quit()
{
$this->disconnect();
}
/**
* Checks if the underlying connection is connected to Redis.
*
* @return Boolean True means that the connection is open.
* False means that the connection is closed.
*/
public function isConnected()
{
return $this->connection->isConnected();
}
/**
* Returns the underlying connection instance or, when connected to a cluster,
* one of the connection instances identified by its alias.
*
* @param string $id The alias of a connection when connected to a cluster.
* @return IConnection
*/
public function getConnection($id = null)
{
if (isset($id)) {
if (!Helpers::isCluster($this->connection)) {
$message = 'Retrieving connections by alias is supported only with clustered connections';
throw new NotSupportedException($message);
}
return $this->connection->getConnectionById($id);
}
return $this->connection;
}
/**
* Dinamically invokes a Redis command with the specified arguments.
*
* @param string $method The name of a Redis command.
* @param array $arguments The arguments for the command.
* @return mixed
*/
public function __call($method, $arguments)
{
$command = $this->profile->createCommand($method, $arguments);
return $this->connection->executeCommand($command);
}
/**
* Creates a new instance of the specified Redis command.
*
* @param string $method The name of a Redis command.
* @param array $arguments The arguments for the command.
* @return ICommand
*/
public function createCommand($method, $arguments = array())
{
return $this->profile->createCommand($method, $arguments);
}
/**
* Executes the specified Redis command.
*
* @param ICommand $command A Redis command.
* @return mixed
*/
public function executeCommand(ICommand $command)
{
return $this->connection->executeCommand($command);
}
/**
* Executes the specified Redis command on all the nodes of a cluster.
*
* @param ICommand $command A Redis command.
* @return array
*/
public function executeCommandOnShards(ICommand $command)
{
if (Helpers::isCluster($this->connection)) {
$replies = array();
foreach ($this->connection as $connection) {
$replies[] = $connection->executeCommand($command);
}
return $replies;
}
return array($this->connection->executeCommand($command));
}
/**
* Calls the specified initializer method on $this with 0, 1 or 2 arguments.
*
* TODO: Invert $argv and $initializer.
*
* @param array $argv Arguments for the initializer.
* @param string $initializer The initializer method.
* @return mixed
*/
private function sharedInitializer($argv, $initializer)
{
switch (count($argv)) {
case 0:
return $this->$initializer();
case 1:
list($arg0) = $argv;
return is_array($arg0) ? $this->$initializer($arg0) : $this->$initializer(null, $arg0);
case 2:
list($arg0, $arg1) = $argv;
return $this->$initializer($arg0, $arg1);
default:
return $this->$initializer($this, $argv);
}
}
/**
* Creates a new pipeline context and returns it, or returns the results of
* a pipeline executed inside the optionally provided callable object.
*
* @param mixed $arg,... Options for the context, a callable object, or both.
* @return PipelineContext|array
*/
public function pipeline(/* arguments */)
{
return $this->sharedInitializer(func_get_args(), 'initPipeline');
}
/**
* Pipeline context initializer.
*
* @param array $options Options for the context.
* @param mixed $callable Optional callable object used to execute the context.
* @return PipelineContext|array
*/
protected function initPipeline(Array $options = null, $callable = null)
{
$pipeline = new PipelineContext($this, $options);
return $this->pipelineExecute($pipeline, $callable);
}
/**
* Executes a pipeline context when a callable object is passed.
*
* @param array $options Options of the context initialization.
* @param mixed $callable Optional callable object used to execute the context.
* @return PipelineContext|array
*/
private function pipelineExecute(PipelineContext $pipeline, $callable)
{
return isset($callable) ? $pipeline->execute($callable) : $pipeline;
}
/**
* Creates a new transaction context and returns it, or returns the results of
* a transaction executed inside the optionally provided callable object.
*
* @param mixed $arg,... Options for the context, a callable object, or both.
* @return MultiExecContext|array
*/
public function multiExec(/* arguments */)
{
return $this->sharedInitializer(func_get_args(), 'initMultiExec');
}
/**
* Transaction context initializer.
*
* @param array $options Options for the context.
* @param mixed $callable Optional callable object used to execute the context.
* @return MultiExecContext|array
*/
protected function initMultiExec(Array $options = null, $callable = null)
{
$transaction = new MultiExecContext($this, $options ?: array());
return isset($callable) ? $transaction->execute($callable) : $transaction;
}
/**
* Creates a new Publish / Subscribe context and returns it, or executes it
* inside the optionally provided callable object.
*
* @param mixed $arg,... Options for the context, a callable object, or both.
* @return MultiExecContext|array
*/
public function pubSub(/* arguments */)
{
return $this->sharedInitializer(func_get_args(), 'initPubSub');
}
/**
* Publish / Subscribe context initializer.
*
* @param array $options Options for the context.
* @param mixed $callable Optional callable object used to execute the context.
* @return PubSubContext
*/
protected function initPubSub(Array $options = null, $callable = null)
{
$pubsub = new PubSubContext($this, $options);
if (!isset($callable)) {
return $pubsub;
}
foreach ($pubsub as $message) {
if (call_user_func($callable, $pubsub, $message) === false) {
$pubsub->closeContext();
}
}
}
/**
* Returns a new monitor context.
*
* @return MonitorContext
*/
public function monitor()
{
return new MonitorContext($this);
}
}
-170
View File
@@ -1,170 +0,0 @@
<?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\Commands;
use Predis\Helpers;
use Predis\Distribution\INodeKeyGenerator;
/**
* Base class for Redis commands.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
abstract class Command implements ICommand
{
private $hash;
private $arguments = array();
/**
* Returns a filtered array of the arguments.
*
* @param array $arguments List of arguments.
* @return array
*/
protected function filterArguments(Array $arguments)
{
return $arguments;
}
/**
* {@inheritdoc}
*/
public function setArguments(Array $arguments)
{
$this->arguments = $this->filterArguments($arguments);
unset($this->hash);
}
/**
* Sets the arguments array without filtering.
*
* @param array $arguments List of arguments.
*/
public function setRawArguments(Array $arguments)
{
$this->arguments = $arguments;
unset($this->hash);
}
/**
* {@inheritdoc}
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Gets the argument from the arguments list at the specified index.
*
* @param array $arguments Position of the argument.
*/
public function getArgument($index = 0)
{
if (isset($this->arguments[$index]) === true) {
return $this->arguments[$index];
}
}
/**
* Checks if the command can return an hash for client-side sharding.
*
* @return Boolean
*/
protected function canBeHashed()
{
return isset($this->arguments[0]);
}
/**
* Checks if the specified array of keys will generate the same hash.
*
* @param array $keys Array of keys.
* @return Boolean
*/
protected function checkSameHashForKeys(Array $keys)
{
if (($count = count($keys)) === 0) {
return false;
}
$currentKey = Helpers::extractKeyTag($keys[0]);
for ($i = 1; $i < $count; $i++) {
$nextKey = Helpers::extractKeyTag($keys[$i]);
if ($currentKey !== $nextKey) {
return false;
}
$currentKey = $nextKey;
}
return true;
}
/**
* {@inheritdoc}
*/
public function getHash(INodeKeyGenerator $distributor)
{
if (isset($this->hash)) {
return $this->hash;
}
if ($this->canBeHashed()) {
$key = Helpers::extractKeyTag($this->arguments[0]);
$this->hash = $distributor->generateKey($key);
return $this->hash;
}
return null;
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return $data;
}
/**
* Helper function used to reduce a list of arguments to a string.
*
* @param string $accumulator Temporary string.
* @param string $argument Current argument.
* @return string
*/
protected function toStringArgumentReducer($accumulator, $argument)
{
if (strlen($argument) > 32) {
$argument = substr($argument, 0, 32) . '[...]';
}
$accumulator .= " $argument";
return $accumulator;
}
/**
* Returns a partial string representation of the command with its arguments.
*
* @return string
*/
public function __toString()
{
return array_reduce(
$this->getArguments(),
array($this, 'toStringArgumentReducer'),
$this->getId()
);
}
}
-43
View File
@@ -1,43 +0,0 @@
<?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\Commands;
/**
* @link http://redis.io/commands/ping
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionPing extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'PING';
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return false;
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return $data === 'PONG' ? true : false;
}
}
-59
View File
@@ -1,59 +0,0 @@
<?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\Commands;
use Predis\Distribution\INodeKeyGenerator;
/**
* Defines an abstraction representing a Redis command.
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface ICommand
{
/**
* Gets the ID of a Redis command.
*
* @return string
*/
public function getId();
/**
* Returns an hash of the command using the provided algorithm against the
* key (used to calculate the distribution of keys with client-side sharding).
*
* @param INodeKeyGenerator $distributor Distribution algorithm.
* @return int
*/
public function getHash(INodeKeyGenerator $distributor);
/**
* Sets the arguments of the command.
*
* @param array $arguments List of arguments.
*/
public function setArguments(Array $arguments);
/**
* Gets the arguments of the command.
*
* @return array
*/
public function getArguments();
/**
* Parses a reply buffer and returns a PHP object.
*
* @param string $data Binary string containing the whole reply.
* @return mixed
*/
public function parseResponse($data);
}
-58
View File
@@ -1,58 +0,0 @@
<?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\Commands;
use Predis\Helpers;
/**
* @link http://redis.io/commands/del
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyDelete extends Command implements IPrefixable
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'DEL';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(Array $arguments)
{
return Helpers::filterArrayArguments($arguments);
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::all($this, $prefix);
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
$args = $this->getArguments();
if (count($args) === 1) {
return true;
}
return $this->checkSameHashForKeys($args);
}
}
-43
View File
@@ -1,43 +0,0 @@
<?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\Commands;
/**
* @link http://redis.io/commands/rename
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyRename extends Command implements IPrefixable
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'RENAME';
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::all($this, $prefix);
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return false;
}
}
@@ -1,43 +0,0 @@
<?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\Commands;
/**
* @link http://redis.io/commands/rpoplpush
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ListPopLastPushHead extends Command implements IPrefixable
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'RPOPLPUSH';
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::all($this, $prefix);
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return $this->checkSameHashForKeys($this->getArguments());
}
}
@@ -1,45 +0,0 @@
<?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\Commands;
/**
* @link http://redis.io/commands/brpoplpush
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ListPopLastPushHeadBlocking extends Command implements IPrefixable
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'BRPOPLPUSH';
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::skipLast($this, $prefix);
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return $this->checkSameHashForKeys(
array_slice($args = $this->getArguments(), 0, count($args) - 1)
);
}
}
-87
View File
@@ -1,87 +0,0 @@
<?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\Commands;
/**
* Class that defines a few helpers method for prefixing keys.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class PrefixHelpers
{
/**
* Applies the specified prefix only the first argument.
*
* @param ICommand $command Command instance.
* @param string $prefix Prefix string.
*/
public static function first(ICommand $command, $prefix)
{
if ($arguments = $command->getArguments()) {
$arguments[0] = "$prefix{$arguments[0]}";
$command->setRawArguments($arguments);
}
}
/**
* Applies the specified prefix to all the arguments.
*
* @param ICommand $command Command instance.
* @param string $prefix Prefix string.
*/
public static function all(ICommand $command, $prefix)
{
$arguments = $command->getArguments();
foreach ($arguments as &$key) {
$key = "$prefix$key";
}
$command->setRawArguments($arguments);
}
/**
* Applies the specified prefix only to even arguments in the list.
*
* @param ICommand $command Command instance.
* @param string $prefix Prefix string.
*/
public static function interleaved(ICommand $command, $prefix)
{
$arguments = $command->getArguments();
$length = count($arguments);
for ($i = 0; $i < $length; $i += 2) {
$arguments[$i] = "$prefix{$arguments[$i]}";
}
$command->setRawArguments($arguments);
}
/**
* Applies the specified prefix to all the arguments but the last one.
*
* @param ICommand $command Command instance.
* @param string $prefix Prefix string.
*/
public static function skipLast(ICommand $command, $prefix)
{
$arguments = $command->getArguments();
$length = count($arguments);
for ($i = 0; $i < $length - 1; $i++) {
$arguments[$i] = "$prefix{$arguments[$i]}";
}
$command->setRawArguments($arguments);
}
}
-31
View File
@@ -1,31 +0,0 @@
<?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\Commands;
/**
* Base class for Redis commands with prefixable keys.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
abstract class PrefixableCommand extends Command implements IPrefixable
{
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
if ($arguments = $this->getArguments()) {
$arguments[0] = "$prefix{$arguments[0]}";
$this->setRawArguments($arguments);
}
}
}
@@ -1,29 +0,0 @@
<?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\Commands\Processors;
use Predis\Commands\ICommand;
/**
* A command processor processes commands before they are sent to Redis.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface ICommandProcessor
{
/**
* Processes a Redis command.
*
* @param ICommand $command Redis command.
*/
public function process(ICommand $command);
}
@@ -1,42 +0,0 @@
<?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\Commands\Processors;
/**
* A command processor chain processes a command using multiple chained command
* processor before it is sent to Redis.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface ICommandProcessorChain extends ICommandProcessor, \IteratorAggregate, \Countable
{
/**
* Adds a command processor.
*
* @param ICommandProcessor $processor A command processor.
*/
public function add(ICommandProcessor $processor);
/**
* Removes a command processor from the chain.
*
* @param ICommandProcessor $processor A command processor.
*/
public function remove(ICommandProcessor $processor);
/**
* Returns an ordered list of the command processors in the chain.
*
* @return array
*/
public function getProcessors();
}
@@ -1,34 +0,0 @@
<?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\Commands\Processors;
/**
* Defines an object that can process commands using command processors.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface IProcessingSupport
{
/**
* Associates a command processor.
*
* @param ICommandProcessor $processor The command processor.
*/
public function setProcessor(ICommandProcessor $processor);
/**
* Returns the associated command processor.
*
* @return ICommandProcessor
*/
public function getProcessor();
}
@@ -1,64 +0,0 @@
<?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\Commands\Processors;
use Predis\Commands\ICommand;
use Predis\Commands\IPrefixable;
/**
* Command processor that is used to prefix the keys contained in the arguments
* of a Redis command.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyPrefixProcessor implements ICommandProcessor
{
private $prefix;
/**
* @param string $prefix Prefix for the keys.
*/
public function __construct($prefix)
{
$this->setPrefix($prefix);
}
/**
* Sets a prefix that is applied to all the keys.
*
* @param string $prefix Prefix for the keys.
*/
public function setPrefix($prefix)
{
$this->prefix = $prefix;
}
/**
* Gets the current prefix.
*
* @return string
*/
public function getPrefix()
{
return $this->prefix;
}
/**
* {@inheritdoc}
*/
public function process(ICommand $command)
{
if ($command instanceof IPrefixable) {
$command->prefixKeys($this->prefix);
}
}
}
-53
View File
@@ -1,53 +0,0 @@
<?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\Commands;
use Predis\Helpers;
/**
* @link http://redis.io/commands/subscribe
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class PubSubSubscribe extends Command implements IPrefixable
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'SUBSCRIBE';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(Array $arguments)
{
return Helpers::filterArrayArguments($arguments);
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::all($this, $prefix);
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return false;
}
}
-53
View File
@@ -1,53 +0,0 @@
<?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\Commands;
use Predis\Helpers;
/**
* @link http://redis.io/commands/unsubscribe
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class PubSubUnsubscribe extends Command implements IPrefixable
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'UNSUBSCRIBE';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(Array $arguments)
{
return Helpers::filterArrayArguments($arguments);
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::all($this, $prefix);
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return false;
}
}
-63
View File
@@ -1,63 +0,0 @@
<?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\Commands;
/**
* Base class used to implement an higher level abstraction for "virtual"
* commands based on EVAL.
*
* @link http://redis.io/commands/eval
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
abstract class ScriptedCommand extends ServerEval
{
/**
* Gets the body of a Lua script.
*
* @return string
*/
public abstract function getScript();
/**
* Gets the number of arguments that should be considered as keys.
*
* @todo Should we make a scripted command act by default as a variadic
* command where the first argument is the key (KEYS[1]) and the
* rest is the list of values (ARGV)?
*
* @return int
*/
public function getKeysCount()
{
// The default behaviour for the base class is to use all the arguments
// passed to a scripted command to populate the KEYS table in Lua.
return count($this->getArguments());
}
/**
* Returns the elements from the arguments that are identified as keys.
*
* @return array
*/
public function getKeys()
{
return array_slice($this->getArguments(), 2, $this->getKeysCount());
}
/**
* {@inheritdoc}
*/
protected function filterArguments(Array $arguments)
{
return array_merge(array($this->getScript(), $this->getKeysCount()), $arguments);
}
}
-59
View File
@@ -1,59 +0,0 @@
<?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\Commands;
/**
* @link http://redis.io/commands/eval
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerEval extends Command implements IPrefixable
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'EVAL';
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
$arguments = $this->getArguments();
for ($i = 2; $i < $arguments[1] + 2; $i++) {
$arguments[$i] = "$prefix{$arguments[$i]}";
}
$this->setRawArguments($arguments);
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return false;
}
/**
* Calculates the SHA1 hash of the body of the script.
*
* @return string SHA1 hash.
*/
public function getScriptHash()
{
return sha1($this->getArgument(0));
}
}
-53
View File
@@ -1,53 +0,0 @@
<?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\Commands;
use Predis\Helpers;
/**
* @link http://redis.io/commands/sinter
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class SetIntersection extends Command implements IPrefixable
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'SINTER';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(Array $arguments)
{
return Helpers::filterArrayArguments($arguments);
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::all($this, $prefix);
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return $this->checkSameHashForKeys($this->getArguments());
}
}
-51
View File
@@ -1,51 +0,0 @@
<?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\Commands;
/**
* @link http://redis.io/commands/smove
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class SetMove extends Command implements IPrefixable
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'SMOVE';
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::skipLast($this, $prefix);
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return false;
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
-53
View File
@@ -1,53 +0,0 @@
<?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\Commands;
use Predis\Helpers;
/**
* @link http://redis.io/commands/mget
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class StringGetMultiple extends Command implements IPrefixable
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'MGET';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(Array $arguments)
{
return Helpers::filterArrayArguments($arguments);
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::all($this, $prefix);
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return $this->checkSameHashForKeys($this->getArguments());
}
}
-63
View File
@@ -1,63 +0,0 @@
<?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\Commands;
/**
* @link http://redis.io/commands/watch
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class TransactionWatch extends Command implements IPrefixable
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'WATCH';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(Array $arguments)
{
if (isset($arguments[0]) && is_array($arguments[0])) {
return $arguments[0];
}
return $arguments;
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::all($this, $prefix);
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return false;
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
-58
View File
@@ -1,58 +0,0 @@
<?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;
use Predis\Network\IConnectionSingle;
/**
* Base exception class for network-related errors.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
abstract class CommunicationException extends PredisException
{
private $connection;
/**
* @param IConnectionSingle $connection Connection that generated the exception.
* @param string $message Error message.
* @param int $code Error code.
* @param \Exception $innerException Inner exception for wrapping the original error.
*/
public function __construct(IConnectionSingle $connection,
$message = null, $code = null, \Exception $innerException = null)
{
parent::__construct($message, $code, $innerException);
$this->connection = $connection;
}
/**
* Gets the connection that generated the exception.
*
* @return IConnectionSingle
*/
public function getConnection()
{
return $this->connection;
}
/**
* Indicates if the receiver should reset the underlying connection.
*
* @return Boolean
*/
public function shouldResetConnection()
{
return true;
}
}
-158
View File
@@ -1,158 +0,0 @@
<?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;
use Predis\Profiles\IServerProfile;
use Predis\Network\IConnectionSingle;
use Predis\Network\IConnectionCluster;
use Predis\Profiles\ServerProfile;
/**
* Provides a default factory for Redis connections that maps URI schemes
* to connection classes implementing the Predis\Network\IConnectionSingle
* interface.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionFactory implements IConnectionFactory
{
private $schemes;
/**
* Initializes a new instance of the default connection factory class used by Predis.
*/
public function __construct()
{
$this->schemes = $this->getDefaultSchemes();
}
/**
* Returns a named array that maps URI schemes to connection classes.
*
* @return array Map of URI schemes and connection classes.
*/
protected function getDefaultSchemes()
{
return array(
'tcp' => 'Predis\Network\StreamConnection',
'unix' => 'Predis\Network\StreamConnection',
'http' => 'Predis\Network\WebdisConnection',
);
}
/**
* Checks if the provided argument represents a valid connection class
* implementing the Predis\Network\IConnectionSingle interface. Optionally,
* callable objects are used for lazy initialization of connection objects.
*
* @param mixed $initializer FQN of a connection class or a callable for lazy initialization.
* @return mixed
*/
protected function checkInitializer($initializer)
{
if (is_callable($initializer)) {
return $initializer;
}
$initializerReflection = new \ReflectionClass($initializer);
if (!$initializerReflection->isSubclassOf('Predis\Network\IConnectionSingle')) {
throw new \InvalidArgumentException(
'A connection initializer must be a valid connection class or a callable object'
);
}
return $initializer;
}
/**
* {@inheritdoc}
*/
public function define($scheme, $initializer)
{
$this->schemes[$scheme] = $this->checkInitializer($initializer);
}
/**
* {@inheritdoc}
*/
public function undefine($scheme)
{
unset($this->schemes[$scheme]);
}
/**
* {@inheritdoc}
*/
public function create($parameters, IServerProfile $profile = null)
{
if (!$parameters instanceof IConnectionParameters) {
$parameters = new ConnectionParameters($parameters ?: array());
}
$scheme = $parameters->scheme;
if (!isset($this->schemes[$scheme])) {
throw new \InvalidArgumentException("Unknown connection scheme: $scheme");
}
$initializer = $this->schemes[$scheme];
if (!is_callable($initializer)) {
$connection = new $initializer($parameters);
$this->prepareConnection($connection, $profile ?: ServerProfile::getDefault());
return $connection;
}
$connection = call_user_func($initializer, $parameters, $profile);
if (!$connection instanceof IConnectionSingle) {
throw new \InvalidArgumentException(
'Objects returned by connection initializers must implement ' .
'the Predis\Network\IConnectionSingle interface'
);
}
return $connection;
}
/**
* {@inheritdoc}
*/
public function createCluster(IConnectionCluster $cluster, $parameters, IServerProfile $profile = null)
{
foreach ($parameters as $node) {
$cluster->add($node instanceof IConnectionSingle ? $node : $this->create($node, $profile));
}
return $cluster;
}
/**
* Prepares a connection object after its initialization.
*
* @param IConnectionSingle $connection Instance of a connection object.
* @param IServerProfile $profile $connection Instance of a connection object.
*/
protected function prepareConnection(IConnectionSingle $connection, IServerProfile $profile)
{
$parameters = $connection->getParameters();
if (isset($parameters->password)) {
$command = $profile->createCommand('auth', array($parameters->password));
$connection->pushInitCommand($command);
}
if (isset($parameters->database)) {
$command = $profile->createCommand('select', array($parameters->database));
$connection->pushInitCommand($command);
}
}
}
-278
View File
@@ -1,278 +0,0 @@
<?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;
use Predis\IConnectionParameters;
use Predis\Options\IOption;
/**
* Handles parsing and validation of connection parameters.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionParameters implements IConnectionParameters
{
private static $defaultParameters;
private static $validators;
private $parameters;
private $userDefined;
/**
* @param string|array Connection parameters in the form of an URI string or a named array.
*/
public function __construct($parameters = array())
{
self::ensureDefaults();
if (!is_array($parameters)) {
$parameters = $this->parseURI($parameters);
}
$this->userDefined = array_keys($parameters);
$this->parameters = $this->filter($parameters) + self::$defaultParameters;
}
/**
* Ensures that the default values and validators are initialized.
*/
private static function ensureDefaults()
{
if (!isset(self::$defaultParameters)) {
self::$defaultParameters = array(
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
'database' => null,
'password' => null,
'connection_async' => false,
'connection_persistent' => false,
'connection_timeout' => 5.0,
'read_write_timeout' => null,
'alias' => null,
'weight' => null,
'path' => null,
'iterable_multibulk' => false,
'throw_errors' => true,
);
}
if (!isset(self::$validators)) {
$bool = function($value) { return (bool) $value; };
$float = function($value) { return (float) $value; };
$int = function($value) { return (int) $value; };
self::$validators = array(
'port' => $int,
'connection_async' => $bool,
'connection_persistent' => $bool,
'connection_timeout' => $float,
'read_write_timeout' => $float,
'iterable_multibulk' => $bool,
'throw_errors' => $bool,
);
}
}
/**
* Defines a default value and a validator for the specified parameter.
*
* @param string $parameter Name of the parameter.
* @param mixed $default Default value or an instance of IOption.
* @param mixed $callable A validator callback.
*/
public static function define($parameter, $default, $callable = null)
{
self::ensureDefaults();
self::$defaultParameters[$parameter] = $default;
if ($default instanceof IOption) {
self::$validators[$parameter] = $default;
return;
}
if (!isset($callable)) {
unset(self::$validators[$parameter]);
return;
}
if (!is_callable($callable)) {
throw new \InvalidArgumentException(
"The validator for $parameter must be a callable object"
);
}
self::$validators[$parameter] = $callable;
}
/**
* Undefines the default value and validator for the specified parameter.
*
* @param string $parameter Name of the parameter.
*/
public static function undefine($parameter)
{
self::ensureDefaults();
unset(self::$defaultParameters[$parameter], self::$validators[$parameter]);
}
/**
* Parses an URI string and returns an array of connection parameters.
*
* @param string $uri Connection string.
* @return array
*/
private 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'])) {
throw new ClientException("Invalid URI: $uri");
}
if (isset($parsed['query'])) {
foreach (explode('&', $parsed['query']) as $kv) {
@list($k, $v) = explode('=', $kv);
$parsed[$k] = $v;
}
unset($parsed['query']);
}
return $parsed;
}
/**
* Validates and converts each value of the connection parameters array.
*
* @param array $parameters Connection parameters.
* @return array
*/
private function filter(Array $parameters)
{
if (count($parameters) > 0) {
$validators = array_intersect_key(self::$validators, $parameters);
foreach ($validators as $parameter => $validator) {
$parameters[$parameter] = $validator($parameters[$parameter]);
}
}
return $parameters;
}
/**
* {@inheritdoc}
*/
public function __get($parameter)
{
$value = $this->parameters[$parameter];
if ($value instanceof IOption) {
$this->parameters[$parameter] = ($value = $value->getDefault());
}
return $value;
}
/**
* {@inheritdoc}
*/
public function __isset($parameter)
{
return isset($this->parameters[$parameter]);
}
/**
* Checks if the specified parameter has been set by the user.
*
* @param string $parameter Name of the parameter.
* @return Boolean
*/
public function isSetByUser($parameter)
{
return in_array($parameter, $this->userDefined);
}
/**
* {@inheritdoc}
*/
protected function getBaseURI()
{
if ($this->scheme === 'unix') {
return "{$this->scheme}://{$this->path}";
}
return "{$this->scheme}://{$this->host}:{$this->port}";
}
/**
* Returns the URI parts that must be omitted when calling __toString().
*
* @return array
*/
protected function getDisallowedURIParts()
{
return array('scheme', 'host', 'port', 'password', 'path');
}
/**
* {@inheritdoc}
*/
public function toArray()
{
return $this->parameters;
}
/**
* Returns a string representation of the parameters.
*
* @return string
*/
public function __toString()
{
$query = array();
$parameters = $this->toArray();
$reject = $this->getDisallowedURIParts();
foreach ($this->userDefined as $param) {
if (in_array($param, $reject) || !isset($parameters[$param])) {
continue;
}
$value = $parameters[$param];
$query[] = "$param=" . ($value === false ? '0' : $value);
}
if (count($query) === 0) {
return $this->getBaseURI();
}
return $this->getBaseURI() . '/?' . implode('&', $query);
}
/**
* {@inheritdoc}
*/
public function __sleep()
{
return array('parameters', 'userDefined');
}
/**
* {@inheritdoc}
*/
public function __wakeup()
{
self::ensureDefaults();
}
}
@@ -1,43 +0,0 @@
<?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\Distribution;
/**
* A distributor implements the logic to automatically distribute
* keys among several nodes for client-side sharding.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface IDistributionStrategy extends INodeKeyGenerator
{
/**
* Adds a node to the distributor with an optional weight.
*
* @param mixed $node Node object.
* @param int $weight Weight for the node.
*/
public function add($node, $weight = null);
/**
* Removes a node from the distributor.
*
* @param mixed $node Node object.
*/
public function remove($node);
/**
* Gets a node from the distributor using the computed hash of a key.
*
* @return mixed
*/
public function get($key);
}
@@ -1,29 +0,0 @@
<?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\Distribution;
/**
* A generator of node keys implements the logic used to calculate the hash of
* a key to distribute the respective operations among nodes.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface INodeKeyGenerator
{
/**
* Generates an hash that is used by the distributor algorithm
*
* @param string $value Value used to generate the hash.
* @return int
*/
public function generateKey($value);
}
-102
View File
@@ -1,102 +0,0 @@
<?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;
use Predis\Network\IConnection;
use Predis\Network\IConnectionCluster;
/**
* Defines a few helper methods.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class Helpers
{
/**
* Checks if the specified connection represents a cluster.
*
* @param IConnection $connection Connection object.
* @return Boolean
*/
public static function isCluster(IConnection $connection)
{
return $connection instanceof IConnectionCluster;
}
/**
* Offers a generic and reusable method to handle exceptions generated by
* a connection object.
*
* @param CommunicationException $exception Exception.
*/
public static function onCommunicationException(CommunicationException $exception)
{
if ($exception->shouldResetConnection()) {
$connection = $exception->getConnection();
if ($connection->isConnected()) {
$connection->disconnect();
}
}
throw $exception;
}
/**
* Normalizes the arguments array passed to a Redis command.
*
* @param array $arguments Arguments for a command.
* @return array
*/
public static function filterArrayArguments(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 filterVariadicValues(Array $arguments)
{
if (count($arguments) === 2 && is_array($arguments[1])) {
return array_merge(array($arguments[0]), $arguments[1]);
}
return $arguments;
}
/**
* Returns only the hashable part of a key (delimited by "{...}"), or the
* whole key if a key tag is not found in the string.
*
* @param string $key A key.
* @return string
*/
public static function extractKeyTag($key)
{
$start = strpos($key, '{');
if ($start !== false) {
$end = strpos($key, '}', $start);
if ($end !== false) {
$key = substr($key, ++$start, $end - $start);
}
}
return $key;
}
}
-56
View File
@@ -1,56 +0,0 @@
<?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;
use Predis\Profiles\IServerProfile;
use Predis\Network\IConnectionCluster;
/**
* Interface that must be implemented by classes that provide their own mechanism
* to create and initialize new instances of Predis\Network\IConnectionSingle.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface IConnectionFactory
{
/**
* Defines or overrides the connection class identified by a scheme prefix.
*
* @param string $scheme URI scheme identifying the connection class.
* @param mixed $initializer FQN of a connection class or a callable object for lazy initialization.
*/
public function define($scheme, $initializer);
/**
* Undefines the connection identified by a scheme prefix.
*
* @param string $scheme Parameters for the connection.
*/
public function undefine($scheme);
/**
* Creates a new connection object.
*
* @param mixed $parameters Parameters for the connection.
* @return Predis\Network\IConnectionSingle
*/
public function create($parameters, IServerProfile $profile = null);
/**
* Prepares a cluster of connection objects.
*
* @param IConnectionCluster Instance of a connection cluster class.
* @param array $parameters List of parameters for each connection object.
* @return Predis\Network\IConnectionCluster
*/
public function createCluster(IConnectionCluster $cluster, $parameters, IServerProfile $profile = null);
}
-44
View File
@@ -1,44 +0,0 @@
<?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;
/**
* Interface that must be implemented by classes that provide their own mechanism
* to parse and handle connection parameters.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface IConnectionParameters
{
/**
* Checks if the specified parameters is set.
*
* @param string $property Name of the property.
* @return Boolean
*/
public function __isset($parameter);
/**
* Returns the value of the specified parameter.
*
* @param string $parameter Name of the parameter.
* @return mixed
*/
public function __get($parameter);
/**
* Returns an array representation of the connection parameters.
*
* @return array
*/
public function toArray();
}
@@ -1,88 +0,0 @@
<?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\Iterators;
/**
* Iterator that abstracts the access to multibulk replies and allows
* them to be consumed by user's code in a streaming fashion.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
abstract class MultiBulkResponse implements \Iterator, \Countable
{
protected $position;
protected $current;
protected $replySize;
/**
* {@inheritdoc}
*/
public function rewind()
{
// NOOP
}
/**
* {@inheritdoc}
*/
public function current()
{
return $this->current;
}
/**
* {@inheritdoc}
*/
public function key()
{
return $this->position;
}
/**
* {@inheritdoc}
*/
public function next()
{
if (++$this->position < $this->replySize) {
$this->current = $this->getValue();
}
return $this->position;
}
/**
* {@inheritdoc}
*/
public function valid()
{
return $this->position < $this->replySize;
}
/**
* Returns the number of items of the whole multibulk reply.
*
* This method should be used to get the size of the current multibulk
* reply without using iterator_count, which actually consumes the
* iterator to calculate the size (rewinding is not supported).
*
* @return int
*/
public function count()
{
return $this->replySize;
}
/**
* {@inheritdoc}
*/
protected abstract function getValue();
}
@@ -1,80 +0,0 @@
<?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\Iterators;
use Predis\Network\IConnection;
use Predis\Network\IConnectionSingle;
/**
* Streams a multibulk reply.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class MultiBulkResponseSimple extends MultiBulkResponse
{
private $connection;
/**
* @param IConnectionSingle $connection Connection to Redis.
* @param int $size Number of elements of the multibulk reply.
*/
public function __construct(IConnectionSingle $connection, $size)
{
$this->connection = $connection;
$this->position = 0;
$this->current = $size > 0 ? $this->getValue() : null;
$this->replySize = $size;
}
/**
* Handles the synchronization of the client with the Redis protocol
* then PHP's garbage collector kicks in (e.g. then the iterator goes
* out of the scope of a foreach).
*/
public function __destruct()
{
$this->sync(true);
}
/**
* Synchronizes the client with the queued elements that have not been
* read from the connection by consuming the rest of the multibulk reply,
* or simply by dropping the connection.
*
* @param Boolean $drop True to synchronize the client by dropping the connection.
* False to synchronize the client by consuming the multibulk reply.
*/
public function sync($drop = false)
{
if ($drop == true) {
if ($this->valid()) {
$this->position = $this->replySize;
$this->connection->disconnect();
}
}
else {
while ($this->valid()) {
$this->next();
}
}
}
/**
* Reads the next item of the multibulk reply from the server.
*
* @return mixed
*/
protected function getValue()
{
return $this->connection->read();
}
}
@@ -1,65 +0,0 @@
<?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\Iterators;
/**
* Abstracts the access to a streamable list of tuples represented
* as a multibulk reply that alternates keys and values.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class MultiBulkResponseTuple extends MultiBulkResponse implements \OuterIterator
{
private $iterator;
/**
* @param MultiBulkResponseSimple $iterator Multibulk reply iterator.
*/
public function __construct(MultiBulkResponseSimple $iterator)
{
$virtualSize = count($iterator) / 2;
$this->iterator = $iterator;
$this->position = 0;
$this->current = $virtualSize > 0 ? $this->getValue() : null;
$this->replySize = $virtualSize;
}
/**
* {@inheritdoc}
*/
public function getInnerIterator()
{
return $this->iterator;
}
/**
* {@inheritdoc}
*/
public function __destruct()
{
$this->iterator->sync(true);
}
/**
* {@inheritdoc}
*/
protected function getValue()
{
$k = $this->iterator->current();
$this->iterator->next();
$v = $this->iterator->current();
$this->iterator->next();
return array($k, $v);
}
}
-154
View File
@@ -1,154 +0,0 @@
<?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;
/**
* Client-side abstraction of a Redis MONITOR context.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class MonitorContext implements \Iterator
{
private $client;
private $isValid;
private $position;
/**
* @param Client Client instance used by the context.
*/
public function __construct(Client $client)
{
$this->checkCapabilities($client);
$this->client = $client;
$this->openContext();
}
/**
* Automatically closes the context when PHP's garbage collector kicks in.
*/
public function __destruct()
{
$this->closeContext();
}
/**
* Checks if the passed client instance satisfies the required conditions
* needed to initialize a monitor context.
*
* @param Client Client instance used by the context.
*/
private function checkCapabilities(Client $client)
{
if (Helpers::isCluster($client->getConnection())) {
throw new NotSupportedException('Cannot initialize a monitor context over a cluster of connections');
}
if ($client->getProfile()->supportsCommand('monitor') === false) {
throw new NotSupportedException('The current profile does not support the MONITOR command');
}
}
/**
* Initializes the context and sends the MONITOR command to the server.
*
* @param Client Client instance used by the context.
*/
protected function openContext()
{
$this->isValid = true;
$monitor = $this->client->createCommand('monitor');
$this->client->executeCommand($monitor);
}
/**
* Closes the context. Internally this is done by disconnecting from server
* since there is no way to terminate the stream initialized by MONITOR.
*/
public function closeContext()
{
$this->client->disconnect();
$this->isValid = false;
}
/**
* {@inheritdoc}
*/
public function rewind()
{
// NOOP
}
/**
* Returns the last message payload retrieved from the server.
*
* @return Object
*/
public function current()
{
return $this->getValue();
}
/**
* {@inheritdoc}
*/
public function key()
{
return $this->position;
}
/**
* {@inheritdoc}
*/
public function next()
{
$this->position++;
}
/**
* Checks if the the context is still in a valid state to continue.
*
* @return Boolean
*/
public function valid()
{
return $this->isValid;
}
/**
* Waits for a new message from the server generated by MONITOR and
* returns it when available.
*
* @return Object
*/
private function getValue()
{
$database = 0;
$event = $this->client->getConnection()->read();
$callback = function($matches) use (&$database) {
if (isset($matches[1])) {
$database = (int) $matches[1];
}
return ' ';
};
$event = preg_replace_callback('/ \(db (\d+)\) /', $callback, $event, 1);
@list($timestamp, $command, $arguments) = split(' ', $event, 3);
return (object) array(
'timestamp' => (float) $timestamp,
'database' => $database,
'command' => substr($command, 1, -1),
'arguments' => $arguments,
);
}
}
-63
View File
@@ -1,63 +0,0 @@
<?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\Network;
use Predis\Commands\ICommand;
/**
* Defines a connection object used to communicate with one or multiple
* Redis servers.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface IConnection
{
/**
* Opens the connection.
*/
public function connect();
/**
* Closes the connection.
*/
public function disconnect();
/**
* Returns if the connection is open.
*
* @return Boolean
*/
public function isConnected();
/**
* Write a Redis command on the connection.
*
* @param ICommand $command Instance of a Redis command.
*/
public function writeCommand(ICommand $command);
/**
* Reads the reply for a Redis command from the connection.
*
* @param ICommand $command Instance of a Redis command.
* @return mixed
*/
public function readResponse(ICommand $command);
/**
* Writes a Redis command to the connection and reads back the reply.
*
* @param ICommand $command Instance of a Redis command.
* @return mixed
*/
public function executeCommand(ICommand $command);
}
-54
View File
@@ -1,54 +0,0 @@
<?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\Network;
use Predis\Commands\ICommand;
/**
* Defines a cluster of Redis servers formed by aggregating multiple
* connection objects.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface IConnectionCluster extends IConnection
{
/**
* Adds a connection instance to the cluster.
*
* @param IConnectionSingle $connection Instance of a connection.
*/
public function add(IConnectionSingle $connection);
/**
* Removes the specified connection instance from the cluster.
*
* @param IConnectionSingle $connection Instance of a connection.
* @return Boolean Returns true if the connection was in the pool.
*/
public function remove(IConnectionSingle $connection);
/**
* Gets the actual connection instance in charge of the specified command.
*
* @param ICommand $command Instance of a Redis command.
* @return IConnectionSingle
*/
public function getConnection(ICommand $command);
/**
* Retrieves a connection instance from the cluster using an alias.
*
* @param string $connectionId Alias of a connection
* @return IConnectionSingle
*/
public function getConnectionById($connectionId);
}
@@ -1,57 +0,0 @@
<?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\Network;
use Predis\Protocol\IProtocolProcessor;
/**
* Defines a connection object used to communicate with a single Redis server
* that leverages an external protocol processor to handle pluggable protocol
* handlers.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface IConnectionComposable extends IConnectionSingle
{
/**
* Sets the protocol processor used by the connection.
*
* @param IProtocolProcessor $protocol Protocol processor.
*/
public function setProtocol(IProtocolProcessor $protocol);
/**
* Gets the protocol processor used by the connection.
*/
public function getProtocol();
/**
* Writes a buffer that contains a serialized Redis command.
*
* @param string $buffer Serialized Redis command.
*/
public function writeBytes($buffer);
/**
* Reads a specified number of bytes from the connection.
*
* @param string
*/
public function readBytes($length);
/**
* Reads a line from the connection.
*
* @param string
*/
public function readLine();
}
-83
View File
@@ -1,83 +0,0 @@
<?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\Options;
use Predis\Network\IConnectionCluster;
use Predis\Network\PredisCluster;
/**
* Option class that returns a connection cluster to be used by a client.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ClientCluster extends Option
{
/**
* Checks if the specified value is a valid instance of IConnectionCluster.
*
* @param IConnectionCluster $cluster Instance of a connection cluster.
* @return IConnectionCluster
*/
protected function checkInstance($cluster)
{
if (!$cluster instanceof IConnectionCluster) {
throw new \InvalidArgumentException('Instance of Predis\Network\IConnectionCluster expected');
}
return $cluster;
}
/**
* {@inheritdoc}
*/
public function filter(IClientOptions $options, $value)
{
if (is_callable($value)) {
return $this->checkInstance(call_user_func($value, $options));
}
$initializer = $this->getInitializer($options, $value);
return $this->checkInstance($initializer());
}
/**
* Returns an initializer for the specified FQN or type.
*
* @param string $fqnOrType Type of cluster or FQN of a class implementing IConnectionCluster.
* @param IClientOptions $options Instance of the client options.
* @return \Closure
*/
protected function getInitializer(IClientOptions $options, $fqnOrType)
{
switch ($fqnOrType) {
case 'predis':
return function() { return new PredisCluster(); };
default:
// TODO: we should not even allow non-string values here.
if (is_string($fqnOrType) && !class_exists($fqnOrType)) {
throw new \InvalidArgumentException('Class $fqnOrType does not exist');
}
return function() use($fqnOrType) {
return new $fqnOrType();
};
}
}
/**
* {@inheritdoc}
*/
public function getDefault(IClientOptions $options)
{
return new PredisCluster();
}
}
@@ -1,56 +0,0 @@
<?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\Options;
use Predis\IConnectionFactory;
use Predis\ConnectionFactory;
/**
* Option class that returns a connection factory to be used by a client.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ClientConnectionFactory extends Option
{
/**
* {@inheritdoc}
*/
public function filter(IClientOptions $options, $value)
{
if ($value instanceof IConnectionFactory) {
return $value;
}
if (is_array($value)) {
$factory = $this->getDefault($options);
foreach ($value as $scheme => $initializer) {
$factory->define($scheme, $initializer);
}
return $factory;
}
if (is_string($value) && class_exists($value)) {
if (!($factory = new $value()) && !$factory instanceof IConnectionFactory) {
throw new \InvalidArgumentException("Class $value must be an instance of Predis\IConnectionFactory");
}
return $factory;
}
throw new \InvalidArgumentException('Invalid value for the connections option');
}
/**
* {@inheritdoc}
*/
public function getDefault(IClientOptions $options)
{
return new ConnectionFactory();
}
}
-105
View File
@@ -1,105 +0,0 @@
<?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\Options;
/**
* Class that manages client options with filtering and conversion.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ClientOptions implements IClientOptions
{
private $handlers;
private $defined;
private $options = array();
/**
* @param array $options Array of client options.
*/
public function __construct(Array $options = array())
{
$this->handlers = $this->initialize($options);
$this->defined = array_fill_keys(array_keys($options), true);
}
/**
* Ensures that the default options are initialized.
*
* @return array
*/
protected function getDefaultOptions()
{
return array(
'profile' => new ClientProfile(),
'connections' => new ClientConnectionFactory(),
'cluster' => new ClientCluster(),
'prefix' => new ClientPrefix(),
);
}
/**
* Initializes client options handlers.
*
* @param array $options List of client options values.
* @return array
*/
protected function initialize(Array $options)
{
$handlers = $this->getDefaultOptions();
foreach ($options as $option => $value) {
if (isset($handlers[$option])) {
$handler = $handlers[$option];
$handlers[$option] = function($options) use($handler, $value) {
return $handler->filter($options, $value);
};
}
else {
$this->options[$option] = $value;
}
}
return $handlers;
}
/**
* Checks if the specified option is set.
*
* @param string $option Name of the option.
* @return Boolean
*/
public function __isset($option)
{
return isset($this->defined[$option]);
}
/**
* Returns the value of the specified option.
*
* @param string $option Name of the option.
* @return mixed
*/
public function __get($option)
{
if (isset($this->options[$option])) {
return $this->options[$option];
}
if (isset($this->handlers[$option])) {
$handler = $this->handlers[$option];
$value = $handler instanceof IOption ? $handler->getDefault($this) : $handler($this);
$this->options[$option] = $value;
return $value;
}
}
}
-30
View File
@@ -1,30 +0,0 @@
<?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\Options;
use Predis\Commands\Processors\KeyPrefixProcessor;
/**
* Option class that handles the prefixing of keys in commands.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ClientPrefix extends Option
{
/**
* {@inheritdoc}
*/
public function filter(IClientOptions $options, $value)
{
return new KeyPrefixProcessor($value);
}
}
-55
View File
@@ -1,55 +0,0 @@
<?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\Options;
use Predis\Profiles\ServerProfile;
use Predis\Profiles\IServerProfile;
/**
* Option class that handles server profiles to be used by a client.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ClientProfile extends Option
{
/**
* {@inheritdoc}
*/
public function filter(IClientOptions $options, $value)
{
if (is_string($value)) {
$value = ServerProfile::get($value);
if (isset($options->prefix)) {
$value->setProcessor($options->prefix);
}
}
if (!$value instanceof IServerProfile) {
throw new \InvalidArgumentException('Invalid value for the profile option');
}
return $value;
}
/**
* {@inheritdoc}
*/
public function getDefault(IClientOptions $options)
{
$profile = ServerProfile::getDefault();
if (isset($options->prefix)) {
$profile->setProcessor($options->prefix);
}
return $profile;
}
}
-92
View File
@@ -1,92 +0,0 @@
<?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\Options;
/**
* Implements a generic class used to dinamically define a client option.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class CustomOption implements IOption
{
private $filter;
private $default;
/**
* @param array $options List of options
*/
public function __construct(Array $options = array())
{
$this->filter = $this->ensureCallable($options, 'filter');
$this->default = $this->ensureCallable($options, 'default');
}
/**
* Checks if the specified value in the options array is a callable object.
*
* @param array $options Array of options
* @param string $key Target option.
*/
private function ensureCallable($options, $key)
{
if (!isset($options[$key])) {
return;
}
$callable = $options[$key];
if (is_callable($callable)) {
return $callable;
}
throw new \InvalidArgumentException("The parameter $key must be callable");
}
/**
* {@inheritdoc}
*/
public function filter(IClientOptions $options, $value)
{
if (isset($value)) {
if ($this->filter === null) {
return $value;
}
$validator = $this->filter;
return $validator($options, $value);
}
}
/**
* {@inheritdoc}
*/
public function getDefault(IClientOptions $options)
{
if (!isset($this->default)) {
return;
}
$default = $this->default;
return $default($options);
}
/**
* {@inheritdoc}
*/
public function __invoke(IClientOptions $options, $value)
{
if (isset($value)) {
return $this->filter($options, $value);
}
return $this->getDefault($options);
}
}
-45
View File
@@ -1,45 +0,0 @@
<?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\Options;
/**
* Interface that defines a client option.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface IOption
{
/**
* Filters (and optionally converts) the passed value.
*
* @param mixed $value Input value.
* @return mixed
*/
public function filter(IClientOptions $options, $value);
/**
* Returns a default value for the option.
*
* @param mixed $value Input value.
* @return mixed
*/
public function getDefault(IClientOptions $options);
/**
* Filters a value and, if no value is specified, returns
* the default one defined by the option.
*
* @param mixed $value Input value.
* @return mixed
*/
public function __invoke(IClientOptions $options, $value);
}
-48
View File
@@ -1,48 +0,0 @@
<?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\Options;
/**
* Implements a client option.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class Option implements IOption
{
/**
* {@inheritdoc}
*/
public function filter(IClientOptions $options, $value)
{
return $value;
}
/**
* {@inheritdoc}
*/
public function getDefault(IClientOptions $options)
{
return null;
}
/**
* {@inheritdoc}
*/
public function __invoke(IClientOptions $options, $value)
{
if (isset($value)) {
return $this->filter($options, $value);
}
return $this->getDefault($options);
}
}
-32
View File
@@ -1,32 +0,0 @@
<?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\Pipeline;
use Predis\Network\IConnection;
/**
* Defines a strategy to write a list of commands to the network
* and read back their replies.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface IPipelineExecutor
{
/**
* Writes a list of commands to the network and reads back their replies.
*
* @param IConnection $connection Connection to Redis.
* @param array $commands List of commands.
* @return array
*/
public function execute(IConnection $connection, &$commands);
}
-199
View File
@@ -1,199 +0,0 @@
<?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\Pipeline;
use Predis\Client;
use Predis\Helpers;
use Predis\ClientException;
use Predis\Commands\ICommand;
/**
* Abstraction of a pipeline context where write and read operations
* of commands and their replies over the network are pipelined.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class PipelineContext
{
private $client;
private $executor;
private $pipeline = array();
private $replies = array();
private $running = false;
/**
* @param Client Client instance used by the context.
* @param array Options for the context initialization.
*/
public function __construct(Client $client, Array $options = null)
{
$this->client = $client;
$this->executor = $this->createExecutor($client, $options ?: array());
}
/**
* Returns a pipeline executor depending on the kind of the underlying
* connection and the passed options.
*
* @param Client Client instance used by the context.
* @param array Options for the context initialization.
* @return IPipelineExecutor
*/
protected function createExecutor(Client $client, Array $options)
{
if (!$options) {
return new StandardExecutor();
}
if (isset($options['executor'])) {
$executor = $options['executor'];
if (!$executor instanceof IPipelineExecutor) {
throw new \InvalidArgumentException(
'The executor option accepts only instances ' .
'of Predis\Pipeline\IPipelineExecutor'
);
}
return $executor;
}
if (isset($options['safe']) && $options['safe'] == true) {
$isCluster = Helpers::isCluster($client->getConnection());
return $isCluster ? new SafeClusterExecutor() : new SafeExecutor();
}
return new StandardExecutor();
}
/**
* Queues a command into the pipeline buffer.
*
* @param string $method Command ID.
* @param array $arguments Arguments for the command.
* @return PipelineContext
*/
public function __call($method, $arguments)
{
$command = $this->client->createCommand($method, $arguments);
$this->recordCommand($command);
return $this;
}
/**
* Queues a command instance into the pipeline buffer.
*
* @param ICommand $command Command to queue in the buffer.
*/
protected function recordCommand(ICommand $command)
{
$this->pipeline[] = $command;
}
/**
* Queues a command instance into the pipeline buffer.
*
* @param ICommand $command Command to queue in the buffer.
*/
public function executeCommand(ICommand $command)
{
$this->recordCommand($command);
}
/**
* Flushes the buffer that holds the queued commands.
*
* @param Boolean $send Specifies if the commands in the buffer should be sent to Redis.
* @return PipelineContext
*/
public function flushPipeline($send = true)
{
if (count($this->pipeline) > 0) {
if ($send) {
$connection = $this->client->getConnection();
$replies = $this->executor->execute($connection, $this->pipeline);
$this->replies = array_merge($this->replies, $replies);
}
$this->pipeline = array();
}
return $this;
}
/**
* Marks the running status of the pipeline.
*
* @param Boolean $bool True if the pipeline is running.
* False if the pipeline is not running.
*/
private function setRunning($bool)
{
if ($bool === true && $this->running === true) {
throw new ClientException("This pipeline is already opened");
}
$this->running = $bool;
}
/**
* Handles the actual execution of the whole pipeline.
*
* @param mixed $callable Callback for execution.
* @return array
*/
public function execute($callable = null)
{
if ($callable && !is_callable($callable)) {
throw new \InvalidArgumentException('Argument passed must be a callable object');
}
$this->setRunning(true);
$pipelineBlockException = null;
try {
if ($callable !== null) {
call_user_func($callable, $this);
}
$this->flushPipeline();
}
catch (\Exception $exception) {
$pipelineBlockException = $exception;
}
$this->setRunning(false);
if ($pipelineBlockException !== null) {
throw $pipelineBlockException;
}
return $this->replies;
}
/**
* Returns the underlying client instance used by the pipeline object.
*
* @return Client
*/
public function getClient()
{
return $this->client;
}
/**
* Returns the underlying pipeline executor used by the pipeline object.
*
* @return IPipelineExecutor
*/
public function getExecutor()
{
return $this->executor;
}
}
@@ -1,78 +0,0 @@
<?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\Pipeline;
use Predis\ServerException;
use Predis\CommunicationException;
use Predis\Network\IConnection;
/**
* Implements a pipeline executor strategy for connection clusters that does
* not fail when an error is encountered, but adds the returned error in the
* replies array.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class SafeClusterExecutor implements IPipelineExecutor
{
/**
* {@inheritdoc}
*/
public function execute(IConnection $connection, &$commands)
{
$connectionExceptions = array();
$sizeofPipe = count($commands);
$values = array();
foreach ($commands as $command) {
$cmdConnection = $connection->getConnection($command);
if (isset($connectionExceptions[spl_object_hash($cmdConnection)])) {
continue;
}
try {
$cmdConnection->writeCommand($command);
}
catch (CommunicationException $exception) {
$connectionExceptions[spl_object_hash($cmdConnection)] = $exception;
}
}
for ($i = 0; $i < $sizeofPipe; $i++) {
$command = $commands[$i];
unset($commands[$i]);
$cmdConnection = $connection->getConnection($command);
$connectionObjectHash = spl_object_hash($cmdConnection);
if (isset($connectionExceptions[$connectionObjectHash])) {
$values[] = $connectionExceptions[$connectionObjectHash];
continue;
}
try {
$response = $cmdConnection->readResponse($command);
$values[] = $response instanceof \Iterator ? iterator_to_array($response) : $response;
}
catch (ServerException $exception) {
$values[] = $exception->toResponseError();
}
catch (CommunicationException $exception) {
$values[] = $exception;
$connectionExceptions[$connectionObjectHash] = $exception;
}
}
return $values;
}
}
-63
View File
@@ -1,63 +0,0 @@
<?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\Pipeline;
use Predis\ServerException;
use Predis\CommunicationException;
use Predis\Network\IConnection;
/**
* Implements a pipeline executor strategy that does not fail when an error is
* encountered, but adds the returned error in the replies array.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class SafeExecutor implements IPipelineExecutor
{
/**
* {@inheritdoc}
*/
public function execute(IConnection $connection, &$commands)
{
$sizeofPipe = count($commands);
$values = array();
foreach ($commands as $command) {
try {
$connection->writeCommand($command);
}
catch (CommunicationException $exception) {
return array_fill(0, $sizeofPipe, $exception);
}
}
for ($i = 0; $i < $sizeofPipe; $i++) {
$command = $commands[$i];
unset($commands[$i]);
try {
$response = $connection->readResponse($command);
$values[] = $response instanceof \Iterator ? iterator_to_array($response) : $response;
}
catch (ServerException $exception) {
$values[] = $exception->toResponseError();
}
catch (CommunicationException $exception) {
$toAdd = count($commands) - count($values);
$values = array_merge($values, array_fill(0, $toAdd, $exception));
break;
}
}
return $values;
}
}
-55
View File
@@ -1,55 +0,0 @@
<?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\Pipeline;
use Predis\ServerException;
use Predis\Network\IConnection;
/**
* Implements the standard pipeline executor strategy used
* to write a list of commands and read their replies over
* a connection to Redis.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class StandardExecutor implements IPipelineExecutor
{
/**
* {@inheritdoc}
*/
public function execute(IConnection $connection, &$commands)
{
$sizeofPipe = count($commands);
$values = array();
foreach ($commands as $command) {
$connection->writeCommand($command);
}
try {
for ($i = 0; $i < $sizeofPipe; $i++) {
$response = $connection->readResponse($commands[$i]);
$values[] = $response instanceof \Iterator
? iterator_to_array($response)
: $response;
unset($commands[$i]);
}
}
catch (ServerException $exception) {
// Force disconnection to prevent protocol desynchronization.
$connection->disconnect();
throw $exception;
}
return $values;
}
}
-55
View File
@@ -1,55 +0,0 @@
<?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\Profiles;
/**
* A server profile defines features and commands supported by certain
* versions of Redis. Instances of Predis\Client should use a server
* profile matching the version of Redis in use.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface IServerProfile
{
/**
* Gets a profile version corresponding to a Redis version.
*
* @return string
*/
public function getVersion();
/**
* Checks if the profile supports the specified command.
*
* @param string $command Command ID.
* @return Boolean
*/
public function supportsCommand($command);
/**
* Checks if the profile supports the specified list of commands.
*
* @param array $commands List of command IDs.
* @return string
*/
public function supportsCommands(Array $commands);
/**
* Creates a new command instance.
*
* @param string $method Command ID.
* @param array $arguments Arguments for the command.
* @return Predis\Commands\ICommand
*/
public function createCommand($method, $arguments = array());
}
-234
View File
@@ -1,234 +0,0 @@
<?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\Profiles;
use Predis\ClientException;
use Predis\Commands\Processors\ICommandProcessor;
use Predis\Commands\Processors\IProcessingSupport;
/**
* Base class that implements common functionalities of server profiles.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
abstract class ServerProfile implements IServerProfile, IProcessingSupport
{
private static $profiles;
private $commands;
private $processor;
/**
*
*/
public function __construct()
{
$this->commands = $this->getSupportedCommands();
}
/**
* Returns a map of all the commands supported by the profile and their
* actual PHP classes.
*
* @return array
*/
protected abstract function getSupportedCommands();
/**
* Returns the default server profile.
*
* @return IServerProfile
*/
public static function getDefault()
{
return self::get('default');
}
/**
* Returns the development server profile.
*
* @return IServerProfile
*/
public static function getDevelopment()
{
return self::get('dev');
}
/**
* Returns a map of all the server profiles supported by default and their
* actual PHP classes.
*
* @return array
*/
private static function getDefaultProfiles()
{
return array(
'1.2' => 'Predis\Profiles\ServerVersion12',
'2.0' => 'Predis\Profiles\ServerVersion20',
'2.2' => 'Predis\Profiles\ServerVersion22',
'2.4' => 'Predis\Profiles\ServerVersion24',
'default' => 'Predis\Profiles\ServerVersion24',
'dev' => 'Predis\Profiles\ServerVersionNext',
);
}
/**
* Registers a new server profile.
*
* @param string $alias Profile version or alias.
* @param string $profileClass FQN of a class implementing Predis\Profiles\IServerProfile.
*/
public static function define($alias, $profileClass)
{
if (!isset(self::$profiles)) {
self::$profiles = self::getDefaultProfiles();
}
$profileReflection = new \ReflectionClass($profileClass);
if (!$profileReflection->isSubclassOf('Predis\Profiles\IServerProfile')) {
throw new \InvalidArgumentException("Cannot register '$profileClass' as it is not a valid profile class");
}
self::$profiles[$alias] = $profileClass;
}
/**
* Returns the specified server profile.
*
* @param string $version Profile version or alias.
* @return IServerProfile
*/
public static function get($version)
{
if (!isset(self::$profiles)) {
self::$profiles = self::getDefaultProfiles();
}
if (!isset(self::$profiles[$version])) {
throw new ClientException("Unknown server profile: $version");
}
$profile = self::$profiles[$version];
return new $profile();
}
/**
* {@inheritdoc}
*/
public function supportsCommands(Array $commands)
{
foreach ($commands as $command) {
if ($this->supportsCommand($command) === false) {
return false;
}
}
return true;
}
/**
* {@inheritdoc}
*/
public function supportsCommand($command)
{
return isset($this->commands[strtolower($command)]);
}
/**
* Returns the FQN of the class that represent the specified command ID
* registered in the current server profile.
*
* @param string $command Command ID.
* @return string
*/
public function getCommandClass($command)
{
if (isset($this->commands[$command = strtolower($command)])) {
return $this->commands[$command];
}
}
/**
* {@inheritdoc}
*/
public function createCommand($method, $arguments = array())
{
$method = strtolower($method);
if (!isset($this->commands[$method])) {
throw new ClientException("'$method' is not a registered Redis command");
}
$commandClass = $this->commands[$method];
$command = new $commandClass();
$command->setArguments($arguments);
if (isset($this->processor)) {
$this->processor->process($command);
}
return $command;
}
/**
* Defines new commands in the server profile.
*
* @param array $commands Named list of command IDs and their classes.
*/
public function defineCommands(Array $commands)
{
foreach ($commands as $alias => $command) {
$this->defineCommand($alias, $command);
}
}
/**
* Defines a new commands in the server profile.
*
* @param string $alias Command ID.
* @param string $command FQN of a class implementing Predis\Commands\ICommand.
*/
public function defineCommand($alias, $command)
{
$commandReflection = new \ReflectionClass($command);
if (!$commandReflection->isSubclassOf('Predis\Commands\ICommand')) {
throw new \InvalidArgumentException("Cannot register '$command' as it is not a valid Redis command");
}
$this->commands[strtolower($alias)] = $command;
}
/**
* {@inheritdoc}
*/
public function setProcessor(ICommandProcessor $processor = null)
{
$this->processor = $processor;
}
/**
* {@inheritdoc}
*/
public function getProcessor()
{
return $this->processor;
}
/**
* Returns the version of server profile as its string representation.
*
* @return string
*/
public function __toString()
{
return $this->getVersion();
}
}
-125
View File
@@ -1,125 +0,0 @@
<?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\Profiles;
/**
* Server profile for Redis v1.2.x.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerVersion12 extends ServerProfile
{
/**
* {@inheritdoc}
*/
public function getVersion()
{
return '1.2';
}
/**
* {@inheritdoc}
*/
public function getSupportedCommands()
{
return array(
/* ---------------- Redis 1.2 ---------------- */
/* commands operating on the key space */
'exists' => 'Predis\Commands\KeyExists',
'del' => 'Predis\Commands\KeyDelete',
'type' => 'Predis\Commands\KeyType',
'keys' => 'Predis\Commands\KeyKeysV12x',
'randomkey' => 'Predis\Commands\KeyRandom',
'rename' => 'Predis\Commands\KeyRename',
'renamenx' => 'Predis\Commands\KeyRenamePreserve',
'expire' => 'Predis\Commands\KeyExpire',
'expireat' => 'Predis\Commands\KeyExpireAt',
'ttl' => 'Predis\Commands\KeyTimeToLive',
'move' => 'Predis\Commands\KeyMove',
'sort' => 'Predis\Commands\KeySort',
/* commands operating on string values */
'set' => 'Predis\Commands\StringSet',
'setnx' => 'Predis\Commands\StringSetPreserve',
'mset' => 'Predis\Commands\StringSetMultiple',
'msetnx' => 'Predis\Commands\StringSetMultiplePreserve',
'get' => 'Predis\Commands\StringGet',
'mget' => 'Predis\Commands\StringGetMultiple',
'getset' => 'Predis\Commands\StringGetSet',
'incr' => 'Predis\Commands\StringIncrement',
'incrby' => 'Predis\Commands\StringIncrementBy',
'decr' => 'Predis\Commands\StringDecrement',
'decrby' => 'Predis\Commands\StringDecrementBy',
/* commands operating on lists */
'rpush' => 'Predis\Commands\ListPushTail',
'lpush' => 'Predis\Commands\ListPushHead',
'llen' => 'Predis\Commands\ListLength',
'lrange' => 'Predis\Commands\ListRange',
'ltrim' => 'Predis\Commands\ListTrim',
'lindex' => 'Predis\Commands\ListIndex',
'lset' => 'Predis\Commands\ListSet',
'lrem' => 'Predis\Commands\ListRemove',
'lpop' => 'Predis\Commands\ListPopFirst',
'rpop' => 'Predis\Commands\ListPopLast',
'rpoplpush' => 'Predis\Commands\ListPopLastPushHead',
/* commands operating on sets */
'sadd' => 'Predis\Commands\SetAdd',
'srem' => 'Predis\Commands\SetRemove',
'spop' => 'Predis\Commands\SetPop',
'smove' => 'Predis\Commands\SetMove',
'scard' => 'Predis\Commands\SetCardinality',
'sismember' => 'Predis\Commands\SetIsMember',
'sinter' => 'Predis\Commands\SetIntersection',
'sinterstore' => 'Predis\Commands\SetIntersectionStore',
'sunion' => 'Predis\Commands\SetUnion',
'sunionstore' => 'Predis\Commands\SetUnionStore',
'sdiff' => 'Predis\Commands\SetDifference',
'sdiffstore' => 'Predis\Commands\SetDifferenceStore',
'smembers' => 'Predis\Commands\SetMembers',
'srandmember' => 'Predis\Commands\SetRandomMember',
/* commands operating on sorted sets */
'zadd' => 'Predis\Commands\ZSetAdd',
'zincrby' => 'Predis\Commands\ZSetIncrementBy',
'zrem' => 'Predis\Commands\ZSetRemove',
'zrange' => 'Predis\Commands\ZSetRange',
'zrevrange' => 'Predis\Commands\ZSetReverseRange',
'zrangebyscore' => 'Predis\Commands\ZSetRangeByScore',
'zcard' => 'Predis\Commands\ZSetCardinality',
'zscore' => 'Predis\Commands\ZSetScore',
'zremrangebyscore' => 'Predis\Commands\ZSetRemoveRangeByScore',
/* connection related commands */
'ping' => 'Predis\Commands\ConnectionPing',
'auth' => 'Predis\Commands\ConnectionAuth',
'select' => 'Predis\Commands\ConnectionSelect',
'echo' => 'Predis\Commands\ConnectionEcho',
'quit' => 'Predis\Commands\ConnectionQuit',
/* remote server control commands */
'info' => 'Predis\Commands\ServerInfo',
'slaveof' => 'Predis\Commands\ServerSlaveOf',
'monitor' => 'Predis\Commands\ServerMonitor',
'dbsize' => 'Predis\Commands\ServerDatabaseSize',
'flushdb' => 'Predis\Commands\ServerFlushDatabase',
'flushall' => 'Predis\Commands\ServerFlushAll',
'save' => 'Predis\Commands\ServerSave',
'bgsave' => 'Predis\Commands\ServerBackgroundSave',
'lastsave' => 'Predis\Commands\ServerLastSave',
'shutdown' => 'Predis\Commands\ServerShutdown',
'bgrewriteaof' => 'Predis\Commands\ServerBackgroundRewriteAOF',
);
}
}
-174
View File
@@ -1,174 +0,0 @@
<?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\Profiles;
/**
* Server profile for Redis v2.0.x.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerVersion20 extends ServerProfile
{
/**
* {@inheritdoc}
*/
public function getVersion()
{
return '2.0';
}
/**
* {@inheritdoc}
*/
public function getSupportedCommands()
{
return array(
/* ---------------- Redis 1.2 ---------------- */
/* commands operating on the key space */
'exists' => 'Predis\Commands\KeyExists',
'del' => 'Predis\Commands\KeyDelete',
'type' => 'Predis\Commands\KeyType',
'keys' => 'Predis\Commands\KeyKeys',
'randomkey' => 'Predis\Commands\KeyRandom',
'rename' => 'Predis\Commands\KeyRename',
'renamenx' => 'Predis\Commands\KeyRenamePreserve',
'expire' => 'Predis\Commands\KeyExpire',
'expireat' => 'Predis\Commands\KeyExpireAt',
'ttl' => 'Predis\Commands\KeyTimeToLive',
'move' => 'Predis\Commands\KeyMove',
'sort' => 'Predis\Commands\KeySort',
/* commands operating on string values */
'set' => 'Predis\Commands\StringSet',
'setnx' => 'Predis\Commands\StringSetPreserve',
'mset' => 'Predis\Commands\StringSetMultiple',
'msetnx' => 'Predis\Commands\StringSetMultiplePreserve',
'get' => 'Predis\Commands\StringGet',
'mget' => 'Predis\Commands\StringGetMultiple',
'getset' => 'Predis\Commands\StringGetSet',
'incr' => 'Predis\Commands\StringIncrement',
'incrby' => 'Predis\Commands\StringIncrementBy',
'decr' => 'Predis\Commands\StringDecrement',
'decrby' => 'Predis\Commands\StringDecrementBy',
/* commands operating on lists */
'rpush' => 'Predis\Commands\ListPushTail',
'lpush' => 'Predis\Commands\ListPushHead',
'llen' => 'Predis\Commands\ListLength',
'lrange' => 'Predis\Commands\ListRange',
'ltrim' => 'Predis\Commands\ListTrim',
'lindex' => 'Predis\Commands\ListIndex',
'lset' => 'Predis\Commands\ListSet',
'lrem' => 'Predis\Commands\ListRemove',
'lpop' => 'Predis\Commands\ListPopFirst',
'rpop' => 'Predis\Commands\ListPopLast',
'rpoplpush' => 'Predis\Commands\ListPopLastPushHead',
/* commands operating on sets */
'sadd' => 'Predis\Commands\SetAdd',
'srem' => 'Predis\Commands\SetRemove',
'spop' => 'Predis\Commands\SetPop',
'smove' => 'Predis\Commands\SetMove',
'scard' => 'Predis\Commands\SetCardinality',
'sismember' => 'Predis\Commands\SetIsMember',
'sinter' => 'Predis\Commands\SetIntersection',
'sinterstore' => 'Predis\Commands\SetIntersectionStore',
'sunion' => 'Predis\Commands\SetUnion',
'sunionstore' => 'Predis\Commands\SetUnionStore',
'sdiff' => 'Predis\Commands\SetDifference',
'sdiffstore' => 'Predis\Commands\SetDifferenceStore',
'smembers' => 'Predis\Commands\SetMembers',
'srandmember' => 'Predis\Commands\SetRandomMember',
/* commands operating on sorted sets */
'zadd' => 'Predis\Commands\ZSetAdd',
'zincrby' => 'Predis\Commands\ZSetIncrementBy',
'zrem' => 'Predis\Commands\ZSetRemove',
'zrange' => 'Predis\Commands\ZSetRange',
'zrevrange' => 'Predis\Commands\ZSetReverseRange',
'zrangebyscore' => 'Predis\Commands\ZSetRangeByScore',
'zcard' => 'Predis\Commands\ZSetCardinality',
'zscore' => 'Predis\Commands\ZSetScore',
'zremrangebyscore' => 'Predis\Commands\ZSetRemoveRangeByScore',
/* connection related commands */
'ping' => 'Predis\Commands\ConnectionPing',
'auth' => 'Predis\Commands\ConnectionAuth',
'select' => 'Predis\Commands\ConnectionSelect',
'echo' => 'Predis\Commands\ConnectionEcho',
'quit' => 'Predis\Commands\ConnectionQuit',
/* remote server control commands */
'info' => 'Predis\Commands\ServerInfo',
'slaveof' => 'Predis\Commands\ServerSlaveOf',
'monitor' => 'Predis\Commands\ServerMonitor',
'dbsize' => 'Predis\Commands\ServerDatabaseSize',
'flushdb' => 'Predis\Commands\ServerFlushDatabase',
'flushall' => 'Predis\Commands\ServerFlushAll',
'save' => 'Predis\Commands\ServerSave',
'bgsave' => 'Predis\Commands\ServerBackgroundSave',
'lastsave' => 'Predis\Commands\ServerLastSave',
'shutdown' => 'Predis\Commands\ServerShutdown',
'bgrewriteaof' => 'Predis\Commands\ServerBackgroundRewriteAOF',
/* ---------------- Redis 2.0 ---------------- */
/* commands operating on string values */
'setex' => 'Predis\Commands\StringSetExpire',
'append' => 'Predis\Commands\StringAppend',
'substr' => 'Predis\Commands\StringSubstr',
/* commands operating on lists */
'blpop' => 'Predis\Commands\ListPopFirstBlocking',
'brpop' => 'Predis\Commands\ListPopLastBlocking',
/* commands operating on sorted sets */
'zunionstore' => 'Predis\Commands\ZSetUnionStore',
'zinterstore' => 'Predis\Commands\ZSetIntersectionStore',
'zcount' => 'Predis\Commands\ZSetCount',
'zrank' => 'Predis\Commands\ZSetRank',
'zrevrank' => 'Predis\Commands\ZSetReverseRank',
'zremrangebyrank' => 'Predis\Commands\ZSetRemoveRangeByRank',
/* commands operating on hashes */
'hset' => 'Predis\Commands\HashSet',
'hsetnx' => 'Predis\Commands\HashSetPreserve',
'hmset' => 'Predis\Commands\HashSetMultiple',
'hincrby' => 'Predis\Commands\HashIncrementBy',
'hget' => 'Predis\Commands\HashGet',
'hmget' => 'Predis\Commands\HashGetMultiple',
'hdel' => 'Predis\Commands\HashDelete',
'hexists' => 'Predis\Commands\HashExists',
'hlen' => 'Predis\Commands\HashLength',
'hkeys' => 'Predis\Commands\HashKeys',
'hvals' => 'Predis\Commands\HashValues',
'hgetall' => 'Predis\Commands\HashGetAll',
/* transactions */
'multi' => 'Predis\Commands\TransactionMulti',
'exec' => 'Predis\Commands\TransactionExec',
'discard' => 'Predis\Commands\TransactionDiscard',
/* publish - subscribe */
'subscribe' => 'Predis\Commands\PubSubSubscribe',
'unsubscribe' => 'Predis\Commands\PubSubUnsubscribe',
'psubscribe' => 'Predis\Commands\PubSubSubscribeByPattern',
'punsubscribe' => 'Predis\Commands\PubSubUnsubscribeByPattern',
'publish' => 'Predis\Commands\PubSubPublish',
/* remote server control commands */
'config' => 'Predis\Commands\ServerConfig',
);
}
}
-204
View File
@@ -1,204 +0,0 @@
<?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\Profiles;
/**
* Server profile for Redis v2.2.x.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerVersion22 extends ServerProfile
{
/**
* {@inheritdoc}
*/
public function getVersion()
{
return '2.2';
}
/**
* {@inheritdoc}
*/
public function getSupportedCommands()
{
return array(
/* ---------------- Redis 1.2 ---------------- */
/* commands operating on the key space */
'exists' => 'Predis\Commands\KeyExists',
'del' => 'Predis\Commands\KeyDelete',
'type' => 'Predis\Commands\KeyType',
'keys' => 'Predis\Commands\KeyKeys',
'randomkey' => 'Predis\Commands\KeyRandom',
'rename' => 'Predis\Commands\KeyRename',
'renamenx' => 'Predis\Commands\KeyRenamePreserve',
'expire' => 'Predis\Commands\KeyExpire',
'expireat' => 'Predis\Commands\KeyExpireAt',
'ttl' => 'Predis\Commands\KeyTimeToLive',
'move' => 'Predis\Commands\KeyMove',
'sort' => 'Predis\Commands\KeySort',
/* commands operating on string values */
'set' => 'Predis\Commands\StringSet',
'setnx' => 'Predis\Commands\StringSetPreserve',
'mset' => 'Predis\Commands\StringSetMultiple',
'msetnx' => 'Predis\Commands\StringSetMultiplePreserve',
'get' => 'Predis\Commands\StringGet',
'mget' => 'Predis\Commands\StringGetMultiple',
'getset' => 'Predis\Commands\StringGetSet',
'incr' => 'Predis\Commands\StringIncrement',
'incrby' => 'Predis\Commands\StringIncrementBy',
'decr' => 'Predis\Commands\StringDecrement',
'decrby' => 'Predis\Commands\StringDecrementBy',
/* commands operating on lists */
'rpush' => 'Predis\Commands\ListPushTail',
'lpush' => 'Predis\Commands\ListPushHead',
'llen' => 'Predis\Commands\ListLength',
'lrange' => 'Predis\Commands\ListRange',
'ltrim' => 'Predis\Commands\ListTrim',
'lindex' => 'Predis\Commands\ListIndex',
'lset' => 'Predis\Commands\ListSet',
'lrem' => 'Predis\Commands\ListRemove',
'lpop' => 'Predis\Commands\ListPopFirst',
'rpop' => 'Predis\Commands\ListPopLast',
'rpoplpush' => 'Predis\Commands\ListPopLastPushHead',
/* commands operating on sets */
'sadd' => 'Predis\Commands\SetAdd',
'srem' => 'Predis\Commands\SetRemove',
'spop' => 'Predis\Commands\SetPop',
'smove' => 'Predis\Commands\SetMove',
'scard' => 'Predis\Commands\SetCardinality',
'sismember' => 'Predis\Commands\SetIsMember',
'sinter' => 'Predis\Commands\SetIntersection',
'sinterstore' => 'Predis\Commands\SetIntersectionStore',
'sunion' => 'Predis\Commands\SetUnion',
'sunionstore' => 'Predis\Commands\SetUnionStore',
'sdiff' => 'Predis\Commands\SetDifference',
'sdiffstore' => 'Predis\Commands\SetDifferenceStore',
'smembers' => 'Predis\Commands\SetMembers',
'srandmember' => 'Predis\Commands\SetRandomMember',
/* commands operating on sorted sets */
'zadd' => 'Predis\Commands\ZSetAdd',
'zincrby' => 'Predis\Commands\ZSetIncrementBy',
'zrem' => 'Predis\Commands\ZSetRemove',
'zrange' => 'Predis\Commands\ZSetRange',
'zrevrange' => 'Predis\Commands\ZSetReverseRange',
'zrangebyscore' => 'Predis\Commands\ZSetRangeByScore',
'zcard' => 'Predis\Commands\ZSetCardinality',
'zscore' => 'Predis\Commands\ZSetScore',
'zremrangebyscore' => 'Predis\Commands\ZSetRemoveRangeByScore',
/* connection related commands */
'ping' => 'Predis\Commands\ConnectionPing',
'auth' => 'Predis\Commands\ConnectionAuth',
'select' => 'Predis\Commands\ConnectionSelect',
'echo' => 'Predis\Commands\ConnectionEcho',
'quit' => 'Predis\Commands\ConnectionQuit',
/* remote server control commands */
'info' => 'Predis\Commands\ServerInfo',
'slaveof' => 'Predis\Commands\ServerSlaveOf',
'monitor' => 'Predis\Commands\ServerMonitor',
'dbsize' => 'Predis\Commands\ServerDatabaseSize',
'flushdb' => 'Predis\Commands\ServerFlushDatabase',
'flushall' => 'Predis\Commands\ServerFlushAll',
'save' => 'Predis\Commands\ServerSave',
'bgsave' => 'Predis\Commands\ServerBackgroundSave',
'lastsave' => 'Predis\Commands\ServerLastSave',
'shutdown' => 'Predis\Commands\ServerShutdown',
'bgrewriteaof' => 'Predis\Commands\ServerBackgroundRewriteAOF',
/* ---------------- Redis 2.0 ---------------- */
/* commands operating on string values */
'setex' => 'Predis\Commands\StringSetExpire',
'append' => 'Predis\Commands\StringAppend',
'substr' => 'Predis\Commands\StringSubstr',
/* commands operating on lists */
'blpop' => 'Predis\Commands\ListPopFirstBlocking',
'brpop' => 'Predis\Commands\ListPopLastBlocking',
/* commands operating on sorted sets */
'zunionstore' => 'Predis\Commands\ZSetUnionStore',
'zinterstore' => 'Predis\Commands\ZSetIntersectionStore',
'zcount' => 'Predis\Commands\ZSetCount',
'zrank' => 'Predis\Commands\ZSetRank',
'zrevrank' => 'Predis\Commands\ZSetReverseRank',
'zremrangebyrank' => 'Predis\Commands\ZSetRemoveRangeByRank',
/* commands operating on hashes */
'hset' => 'Predis\Commands\HashSet',
'hsetnx' => 'Predis\Commands\HashSetPreserve',
'hmset' => 'Predis\Commands\HashSetMultiple',
'hincrby' => 'Predis\Commands\HashIncrementBy',
'hget' => 'Predis\Commands\HashGet',
'hmget' => 'Predis\Commands\HashGetMultiple',
'hdel' => 'Predis\Commands\HashDelete',
'hexists' => 'Predis\Commands\HashExists',
'hlen' => 'Predis\Commands\HashLength',
'hkeys' => 'Predis\Commands\HashKeys',
'hvals' => 'Predis\Commands\HashValues',
'hgetall' => 'Predis\Commands\HashGetAll',
/* transactions */
'multi' => 'Predis\Commands\TransactionMulti',
'exec' => 'Predis\Commands\TransactionExec',
'discard' => 'Predis\Commands\TransactionDiscard',
/* publish - subscribe */
'subscribe' => 'Predis\Commands\PubSubSubscribe',
'unsubscribe' => 'Predis\Commands\PubSubUnsubscribe',
'psubscribe' => 'Predis\Commands\PubSubSubscribeByPattern',
'punsubscribe' => 'Predis\Commands\PubSubUnsubscribeByPattern',
'publish' => 'Predis\Commands\PubSubPublish',
/* remote server control commands */
'config' => 'Predis\Commands\ServerConfig',
/* ---------------- Redis 2.2 ---------------- */
/* commands operating on the key space */
'persist' => 'Predis\Commands\KeyPersist',
/* commands operating on string values */
'strlen' => 'Predis\Commands\StringStrlen',
'setrange' => 'Predis\Commands\StringSetRange',
'getrange' => 'Predis\Commands\StringGetRange',
'setbit' => 'Predis\Commands\StringSetBit',
'getbit' => 'Predis\Commands\StringGetBit',
/* commands operating on lists */
'rpushx' => 'Predis\Commands\ListPushTailX',
'lpushx' => 'Predis\Commands\ListPushHeadX',
'linsert' => 'Predis\Commands\ListInsert',
'brpoplpush' => 'Predis\Commands\ListPopLastPushHeadBlocking',
/* commands operating on sorted sets */
'zrevrangebyscore' => 'Predis\Commands\ZSetReverseRangeByScore',
/* transactions */
'watch' => 'Predis\Commands\TransactionWatch',
'unwatch' => 'Predis\Commands\TransactionUnwatch',
/* remote server control commands */
'object' => 'Predis\Commands\ServerObject',
'slowlog' => 'Predis\Commands\ServerSlowlog',
);
}
}
-210
View File
@@ -1,210 +0,0 @@
<?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\Profiles;
/**
* Server profile for Redis v2.4.x.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerVersion24 extends ServerProfile
{
/**
* {@inheritdoc}
*/
public function getVersion()
{
return '2.4';
}
/**
* {@inheritdoc}
*/
public function getSupportedCommands()
{
return array(
/* ---------------- Redis 1.2 ---------------- */
/* commands operating on the key space */
'exists' => 'Predis\Commands\KeyExists',
'del' => 'Predis\Commands\KeyDelete',
'type' => 'Predis\Commands\KeyType',
'keys' => 'Predis\Commands\KeyKeys',
'randomkey' => 'Predis\Commands\KeyRandom',
'rename' => 'Predis\Commands\KeyRename',
'renamenx' => 'Predis\Commands\KeyRenamePreserve',
'expire' => 'Predis\Commands\KeyExpire',
'expireat' => 'Predis\Commands\KeyExpireAt',
'ttl' => 'Predis\Commands\KeyTimeToLive',
'move' => 'Predis\Commands\KeyMove',
'sort' => 'Predis\Commands\KeySort',
/* commands operating on string values */
'set' => 'Predis\Commands\StringSet',
'setnx' => 'Predis\Commands\StringSetPreserve',
'mset' => 'Predis\Commands\StringSetMultiple',
'msetnx' => 'Predis\Commands\StringSetMultiplePreserve',
'get' => 'Predis\Commands\StringGet',
'mget' => 'Predis\Commands\StringGetMultiple',
'getset' => 'Predis\Commands\StringGetSet',
'incr' => 'Predis\Commands\StringIncrement',
'incrby' => 'Predis\Commands\StringIncrementBy',
'decr' => 'Predis\Commands\StringDecrement',
'decrby' => 'Predis\Commands\StringDecrementBy',
/* commands operating on lists */
'rpush' => 'Predis\Commands\ListPushTail',
'lpush' => 'Predis\Commands\ListPushHead',
'llen' => 'Predis\Commands\ListLength',
'lrange' => 'Predis\Commands\ListRange',
'ltrim' => 'Predis\Commands\ListTrim',
'lindex' => 'Predis\Commands\ListIndex',
'lset' => 'Predis\Commands\ListSet',
'lrem' => 'Predis\Commands\ListRemove',
'lpop' => 'Predis\Commands\ListPopFirst',
'rpop' => 'Predis\Commands\ListPopLast',
'rpoplpush' => 'Predis\Commands\ListPopLastPushHead',
/* commands operating on sets */
'sadd' => 'Predis\Commands\SetAdd',
'srem' => 'Predis\Commands\SetRemove',
'spop' => 'Predis\Commands\SetPop',
'smove' => 'Predis\Commands\SetMove',
'scard' => 'Predis\Commands\SetCardinality',
'sismember' => 'Predis\Commands\SetIsMember',
'sinter' => 'Predis\Commands\SetIntersection',
'sinterstore' => 'Predis\Commands\SetIntersectionStore',
'sunion' => 'Predis\Commands\SetUnion',
'sunionstore' => 'Predis\Commands\SetUnionStore',
'sdiff' => 'Predis\Commands\SetDifference',
'sdiffstore' => 'Predis\Commands\SetDifferenceStore',
'smembers' => 'Predis\Commands\SetMembers',
'srandmember' => 'Predis\Commands\SetRandomMember',
/* commands operating on sorted sets */
'zadd' => 'Predis\Commands\ZSetAdd',
'zincrby' => 'Predis\Commands\ZSetIncrementBy',
'zrem' => 'Predis\Commands\ZSetRemove',
'zrange' => 'Predis\Commands\ZSetRange',
'zrevrange' => 'Predis\Commands\ZSetReverseRange',
'zrangebyscore' => 'Predis\Commands\ZSetRangeByScore',
'zcard' => 'Predis\Commands\ZSetCardinality',
'zscore' => 'Predis\Commands\ZSetScore',
'zremrangebyscore' => 'Predis\Commands\ZSetRemoveRangeByScore',
/* connection related commands */
'ping' => 'Predis\Commands\ConnectionPing',
'auth' => 'Predis\Commands\ConnectionAuth',
'select' => 'Predis\Commands\ConnectionSelect',
'echo' => 'Predis\Commands\ConnectionEcho',
'quit' => 'Predis\Commands\ConnectionQuit',
/* remote server control commands */
'info' => 'Predis\Commands\ServerInfo',
'slaveof' => 'Predis\Commands\ServerSlaveOf',
'monitor' => 'Predis\Commands\ServerMonitor',
'dbsize' => 'Predis\Commands\ServerDatabaseSize',
'flushdb' => 'Predis\Commands\ServerFlushDatabase',
'flushall' => 'Predis\Commands\ServerFlushAll',
'save' => 'Predis\Commands\ServerSave',
'bgsave' => 'Predis\Commands\ServerBackgroundSave',
'lastsave' => 'Predis\Commands\ServerLastSave',
'shutdown' => 'Predis\Commands\ServerShutdown',
'bgrewriteaof' => 'Predis\Commands\ServerBackgroundRewriteAOF',
/* ---------------- Redis 2.0 ---------------- */
/* commands operating on string values */
'setex' => 'Predis\Commands\StringSetExpire',
'append' => 'Predis\Commands\StringAppend',
'substr' => 'Predis\Commands\StringSubstr',
/* commands operating on lists */
'blpop' => 'Predis\Commands\ListPopFirstBlocking',
'brpop' => 'Predis\Commands\ListPopLastBlocking',
/* commands operating on sorted sets */
'zunionstore' => 'Predis\Commands\ZSetUnionStore',
'zinterstore' => 'Predis\Commands\ZSetIntersectionStore',
'zcount' => 'Predis\Commands\ZSetCount',
'zrank' => 'Predis\Commands\ZSetRank',
'zrevrank' => 'Predis\Commands\ZSetReverseRank',
'zremrangebyrank' => 'Predis\Commands\ZSetRemoveRangeByRank',
/* commands operating on hashes */
'hset' => 'Predis\Commands\HashSet',
'hsetnx' => 'Predis\Commands\HashSetPreserve',
'hmset' => 'Predis\Commands\HashSetMultiple',
'hincrby' => 'Predis\Commands\HashIncrementBy',
'hget' => 'Predis\Commands\HashGet',
'hmget' => 'Predis\Commands\HashGetMultiple',
'hdel' => 'Predis\Commands\HashDelete',
'hexists' => 'Predis\Commands\HashExists',
'hlen' => 'Predis\Commands\HashLength',
'hkeys' => 'Predis\Commands\HashKeys',
'hvals' => 'Predis\Commands\HashValues',
'hgetall' => 'Predis\Commands\HashGetAll',
/* transactions */
'multi' => 'Predis\Commands\TransactionMulti',
'exec' => 'Predis\Commands\TransactionExec',
'discard' => 'Predis\Commands\TransactionDiscard',
/* publish - subscribe */
'subscribe' => 'Predis\Commands\PubSubSubscribe',
'unsubscribe' => 'Predis\Commands\PubSubUnsubscribe',
'psubscribe' => 'Predis\Commands\PubSubSubscribeByPattern',
'punsubscribe' => 'Predis\Commands\PubSubUnsubscribeByPattern',
'publish' => 'Predis\Commands\PubSubPublish',
/* remote server control commands */
'config' => 'Predis\Commands\ServerConfig',
/* ---------------- Redis 2.2 ---------------- */
/* commands operating on the key space */
'persist' => 'Predis\Commands\KeyPersist',
/* commands operating on string values */
'strlen' => 'Predis\Commands\StringStrlen',
'setrange' => 'Predis\Commands\StringSetRange',
'getrange' => 'Predis\Commands\StringGetRange',
'setbit' => 'Predis\Commands\StringSetBit',
'getbit' => 'Predis\Commands\StringGetBit',
/* commands operating on lists */
'rpushx' => 'Predis\Commands\ListPushTailX',
'lpushx' => 'Predis\Commands\ListPushHeadX',
'linsert' => 'Predis\Commands\ListInsert',
'brpoplpush' => 'Predis\Commands\ListPopLastPushHeadBlocking',
/* commands operating on sorted sets */
'zrevrangebyscore' => 'Predis\Commands\ZSetReverseRangeByScore',
/* transactions */
'watch' => 'Predis\Commands\TransactionWatch',
'unwatch' => 'Predis\Commands\TransactionUnwatch',
/* remote server control commands */
'object' => 'Predis\Commands\ServerObject',
'slowlog' => 'Predis\Commands\ServerSlowlog',
/* ---------------- Redis 2.4 ---------------- */
/* remote server control commands */
'client' => 'Predis\Commands\ServerClient',
);
}
}
-56
View File
@@ -1,56 +0,0 @@
<?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\Profiles;
/**
* Server profile for the current development version of Redis.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerVersionNext extends ServerVersion24
{
/**
* {@inheritdoc}
*/
public function getVersion()
{
return '2.6';
}
/**
* {@inheritdoc}
*/
public function getSupportedCommands()
{
return array_merge(parent::getSupportedCommands(), array(
/* commands operating on the key space */
'pttl' => 'Predis\Commands\KeyPreciseTimeToLive',
'pexpire' => 'Predis\Commands\KeyPreciseExpire',
'pexpireat' => 'Predis\Commands\KeyPreciseExpireAt',
/* commands operating on string values */
'psetex' => 'Predis\Commands\StringPreciseSetExpire',
'incrbyfloat' => 'Predis\Commands\StringIncrementByFloat',
/* commands operating on hashes */
'hincrbyfloat' => 'Predis\Commands\HashIncrementByFloat',
/* scripting */
'eval' => 'Predis\Commands\ServerEval',
'evalsha' => 'Predis\Commands\ServerEvalSHA',
'script' => 'Predis\Commands\ServerScript',
/* remote server control commands */
'info' => 'Predis\Commands\ServerInfoV26x',
));
}
}
@@ -1,50 +0,0 @@
<?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\Protocol;
/**
* Interface that defines a customizable protocol processor that serializes
* Redis commands and parses replies returned by the server to PHP objects
* using a pluggable set of classes defining the underlying wire protocol.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface IComposableProtocolProcessor extends IProtocolProcessor
{
/**
* Sets the command serializer to be used by the protocol processor.
*
* @param ICommandSerializer $serializer Command serializer.
*/
public function setSerializer(ICommandSerializer $serializer);
/**
* Returns the command serializer used by the protocol processor.
*
* @return ICommandSerializer
*/
public function getSerializer();
/**
* Sets the response reader to be used by the protocol processor.
*
* @param IResponseReader $reader Response reader.
*/
public function setReader(IResponseReader $reader);
/**
* Returns the response reader used by the protocol processor.
*
* @return IResponseReader
*/
public function getReader();
}
@@ -1,40 +0,0 @@
<?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\Protocol;
use Predis\Commands\ICommand;
use Predis\Network\IConnectionComposable;
/**
* Interface that defines a protocol processor that serializes Redis commands
* and parses replies returned by the server to PHP objects.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface IProtocolProcessor extends IResponseReader
{
/**
* Writes a Redis command on the specified connection.
*
* @param IConnectionComposable $connection Connection to Redis.
* @param ICommand $command Redis command.
*/
public function write(IConnectionComposable $connection, ICommand $command);
/**
* Sets the options for the protocol processor.
*
* @param string $option Name of the option.
* @param mixed $value Value of the option.
*/
public function setOption($option, $value);
}
-32
View File
@@ -1,32 +0,0 @@
<?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\Protocol;
use Predis\Network\IConnectionComposable;
/**
* Interface that defines an handler able to parse a reply.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface IResponseHandler
{
/**
* Parses a type of reply returned by Redis and reads more data from the
* connection if needed.
*
* @param IConnectionComposable $connection Connection to Redis.
* @param string $payload Initial payload of the reply.
* @return mixed
*/
function handle(IConnectionComposable $connection, $payload);
}
-31
View File
@@ -1,31 +0,0 @@
<?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\Protocol;
use Predis\Network\IConnectionComposable;
/**
* Interface that defines a response reader able to parse replies returned by
* Redis and deserialize them to PHP objects.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface IResponseReader
{
/**
* Reads replies from a connection to Redis and deserializes them.
*
* @param IConnectionComposable $connection Connection to Redis.
* @return mixed
*/
public function read(IConnectionComposable $connection);
}
@@ -1,134 +0,0 @@
<?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\Protocol\Text;
use Predis\Commands\ICommand;
use Predis\Protocol\IResponseReader;
use Predis\Protocol\ICommandSerializer;
use Predis\Protocol\IComposableProtocolProcessor;
use Predis\Network\IConnectionComposable;
/**
* Implements a customizable protocol processor that uses the standard Redis
* wire protocol to serialize Redis commands and parse replies returned by
* the server using a pluggable set of classes.
*
* @link http://redis.io/topics/protocol
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ComposableTextProtocol implements IComposableProtocolProcessor
{
private $serializer;
private $reader;
/**
* @param array $options Set of options used to initialize the protocol processor.
*/
public function __construct(Array $options = array())
{
$this->setSerializer(new TextCommandSerializer());
$this->setReader(new TextResponseReader());
if (count($options) > 0) {
$this->initializeOptions($options);
}
}
/**
* Initializes the protocol processor using a set of options.
*
* @param array $options Set of options.
*/
private function initializeOptions(Array $options)
{
foreach ($options as $k => $v) {
$this->setOption($k, $v);
}
}
/**
* {@inheritdoc}
*/
public function setOption($option, $value)
{
switch ($option) {
case 'iterable_multibulk':
$handler = $value ? new ResponseMultiBulkStreamHandler() : new ResponseMultiBulkHandler();
$this->reader->setHandler(TextProtocol::PREFIX_MULTI_BULK, $handler);
break;
case 'throw_errors':
$handler = $value ? new ResponseErrorHandler() : new ResponseErrorSilentHandler();
$this->reader->setHandler(TextProtocol::PREFIX_ERROR, $handler);
break;
default:
throw new \InvalidArgumentException("The option $option is not supported by the current protocol");
}
}
/**
* {@inheritdoc}
*/
public function serialize(ICommand $command)
{
return $this->serializer->serialize($command);
}
/**
* {@inheritdoc}
*/
public function write(IConnectionComposable $connection, ICommand $command)
{
$connection->writeBytes($this->serializer->serialize($command));
}
/**
* {@inheritdoc}
*/
public function read(IConnectionComposable $connection)
{
return $this->reader->read($connection);
}
/**
* {@inheritdoc}
*/
public function setSerializer(ICommandSerializer $serializer)
{
$this->serializer = $serializer;
}
/**
* {@inheritdoc}
*/
public function getSerializer()
{
return $this->serializer;
}
/**
* {@inheritdoc}
*/
public function setReader(IResponseReader $reader)
{
$this->reader = $reader;
}
/**
* {@inheritdoc}
*/
public function getReader()
{
return $this->reader;
}
}

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