Compare commits

..

236 Commits

Author SHA1 Message Date
Daniele Alessandri 5a1430e99c Implement abstraction for WAIT.
This abstraction makes it possible to issue a WAIT command using a
client instance connected to a single Redis node or configured with
either a replication or cluster backend:

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

  $ack->set('key:1', 'value:1');
  $ack->set('key:2', 'value:2');

  if ($ack->wait(2, 500)) {
    // Writes acknowledged by at least 2 slaves.
  } else {
    // Writes were not acknowledged by at least 2 slaves in 500ms.
  }

When the client is operating in replication mode, WAIT is executed
against the connection currently in use by the underlying backend. On
the other hand when it is operating in cluster mode WAIT is executed
against only one connection as cross-slot operations are not allowed.

This is just a first draft in response to #298 that serves as a base
for further improvements and changes. Tests are still missing.
2016-06-15 18:23:24 +02:00
Daniele Alessandri a70971549e Fix GC issues with closures for phpiredis reader handlers.
The following code, despite not being something you would do in real
world scenarios, eventually ended up generating an exception for "Too
many open files".

  for ($i = 0; $i < 10000; $i++) {
    $client = new Predis\Client();
    $client->connect();
  }

The reason was that the destructor for the connection was not invoked
by PHP as expected when the client instance went out of scope, so the
underlying stream resource were not being properly released.
Even without an actual "connect()" operation, the memory usage of PHP
kept growing until exhausting the configured value of max memory.

The source of the issue is related to the use of closures as handlers
for the phpiredis reader resource, to be more precise it seems to be
some kind of circular reference memory leak. Apparently PHP does not
like the fact that a closure, automatically bound to "$this" starting
with PHP 5.4, is stored in the reader resource which in turn is kept
referenced by "$this". This ends up the refcount not going down thus
the GC does not collect those connection objects going out of scope.
This is confirmed by the fact that this issue is not triggered when
using PHP 5.3 where the ZE does not automatically bind closures to
"$this", unless you capture "$this" with the "use()" directive (and
the usual "$that = $this" trick).

Using a static assignement instead of simply returning new closures
in "getStatusHandler()" and "getErrorHandler()" is kind of an hack
that seems to be working fine, the added value of this solution is
that we do not have to break the internal API of the three connection
backends based on ext-phpiredis.

This commit fixes #345.
2016-06-14 21:27:42 +02:00
Daniele Alessandri e88ffc767d Merge remote-tracking branch 'github/pr/346' 2016-06-14 17:19:32 +02:00
Pascal Hofmann ce8b3fb683 Don't send AUTH and/or SELECT command after connecting to sentinels 2016-06-14 16:38:59 +02:00
Daniele Alessandri e0b4b2c20a [tests] Add test for Predis\Cluster\Hash\CRC16. 2016-06-13 18:58:02 +02:00
Daniele Alessandri 2640062803 Use master for connect() when sentinel reports no slaves.
Fixes #342.
2016-06-13 16:32:45 +02:00
orvice 1811288009 fix typos
possibile -> possible
indentify -> identify
publis ->  publish
2016-06-13 15:22:31 +02:00
Daniele Alessandri fa643ac20c Apply CS fixes. 2016-06-10 11:12:26 +02:00
Daniele Alessandri eb1e686ff9 [tests] Change indentation of mock method expectations. 2016-06-10 11:08:26 +02:00
Daniele Alessandri f02f3b6d37 [tests] Improve code coverage.
There is still some work to improve coverage in other classes though.
2016-06-09 16:41:19 +02:00
Daniele Alessandri d72a1b5550 Do not extend EVALSHA for ScriptCommand class. 2016-06-09 14:02:22 +02:00
Daniele Alessandri 8b466d05df Get string of basic connection parameters from parameters class. 2016-06-08 18:36:35 +02:00
Daniele Alessandri b553c6b9d0 Pass dispatcher loop instance to callback.
This should not break existing code but allows users to retrieve more
easily the current dispatcher loop instance without resorting to some
tricks (like relying on the "use()" directive with closures).
2016-06-08 12:12:31 +02:00
Daniele Alessandri c10479e238 Remove executeCommandOnNodes() from predis cluster backend.
It is still possible to achieve the same simply by iterating over the
connection or, even better, over the client instance.
2016-06-08 12:01:54 +02:00
Daniele Alessandri 228ccaebe3 Use static instead of self. 2016-06-07 21:01:59 +02:00
Daniele Alessandri 82c256422a Fix failure on PHP 5.3.
Also use get_called_class() where possible.
2016-06-07 20:42:08 +02:00
Daniele Alessandri ee7104d2e5 Replace "getClientFor()" with "on()" in Client.
This new method acts exactly like "getClientFor()" by returning a new
client instance for the specified node unless a callback is passed as
the second argument, in this case the callback is invoked and the new
client instance is passed to it. The value returned by the callback
is used as the return value of the "on()" method.
2016-06-07 20:00:11 +02:00
Daniele Alessandri 7c4c4ae58a Change methods of replication connection interface.
Promoted the "switchToMaster()" and "switchToSlave()" methods to be
part of the replication connection interface and demoted the method
"switchTo($connection)".
2016-06-07 18:50:11 +02:00
Daniele Alessandri 185e31e080 Make some fields of Predis\Client private. 2016-06-07 18:22:57 +02:00
Daniele Alessandri 64b3a4805c Apply CS fixes. 2016-06-07 15:23:32 +02:00
Daniele Alessandri 5b4e942112 Add notice about dangers of using master branch. 2016-06-07 15:23:32 +02:00
Daniele Alessandri b2547fc7f4 [tests] Ignore code coverage for Predis\Autoloader. 2016-06-07 15:23:32 +02:00
Daniele Alessandri d34bdd38c3 Reorganize and improve client options.
All option classes have been moved in the Predis\Configuration\Option
namespace and some have been optimized to have less impact on client
initialization timings.

Furthermore the accepted values for some options have been changed,
this is the complete list of accepted values:

- _aggregate_: callable returning an aggregate connection.
- cluster: string value ("predis", "redis"), callable returning an
  aggregate connection.
- replication: string value ("predis", "sentinel"), callable returning
  an aggregate connection.
- commands: command factory, named array mapping command IDs to PHP
  classes, callable returning a command factory or a named array.
- connections: connection factory, callable returning a connection
  factory, named array mapping connection schemes to PHP classes.
- _prefix_: string value, command processor, callable.
- _exceptions_: boolean value.

Note that the cluster and replication options now return a closure
acting as initializer instead of an aggregate connection.
2016-06-07 15:23:32 +02:00
Daniele Alessandri 1028599ac5 Split Predis\Connection\Aggregate namespace.
Now we have two namespaces for cluster and replication backends:

  - Predis\Connection\Cluster
  - Predis\Connection\Replication
2016-06-07 15:23:32 +02:00
Daniele Alessandri 822f02b8eb Implement new logic to load command classes.
By default Predis now uses a convention-over-configuration approach
by looking for a command class in the Predis\Command\Redis namespace
if it is not already defined in the commands class map.

This change allow us to decrease the time needed to load Predis on
each request since we removed 99% of the mappings in the commands
class map. Classes defined in the internal class map still take the
precedence over this mechanism, so users can still define their own
command classes to handle each command.
2016-06-07 15:23:28 +02:00
Daniele Alessandri 4b47639f9e Rename command classes using command ID as name.
Some notable exceptions are EVAL and ECHO because having these names
as class names would raise a syntax error.
2016-06-04 20:54:41 +02:00
Daniele Alessandri e4872af747 Modify ctor signature of Predis\Command\RawCommand. 2016-06-04 20:54:36 +02:00
Daniele Alessandri c1d34f022f Drop useless method from base command class. 2016-06-04 15:57:12 +02:00
Daniele Alessandri 62b421f20e Switch from server profiles to commands factory.
This change reduces some unnecessary complexity in the library, Redis
commands do not change much after all. Developers can still implement
their own commands factory, inject new commands or override existing
ones. The "profile" client options has been renamed to "commands" and
it accepts instances of Predis\Command\FactoryInterface.

The test suite checks at runtime the version of the running instance
of Redis for integration tests to adapt itself automatically.
2016-06-04 15:36:21 +02:00
Daniele Alessandri 6e3f301588 Move commands classes in Predis\Command\Redis. 2016-06-04 15:36:16 +02:00
Daniele Alessandri 3dccd6bf87 Back to development.
The master branch now hosts the development of Predis v2.0.0-dev.
2016-06-02 09:10:25 +02:00
Daniele Alessandri 0e17edbefb Update CHANGELOG and bump VERSION. 2016-06-02 00:06:21 +02:00
Daniele Alessandri 81c0a8f051 Ensure big ints are not truncated on 32 bits PHP.
We check if the string value is different than the casted int value,
if so it means that the integer is beyond PHP_INT_MAX or PHP_INT_MIN
and we simply return the string value. This is also useful on Windows
builds of PHP since the maximum integer size (prior to PHP 7.0) is 32
bits even for 64 bit builds.
2016-06-01 23:49:23 +02:00
Daniele Alessandri 1065edc8d1 Apply last round of CS fixes. 2016-06-01 22:31:06 +02:00
Daniele Alessandri 843ad23ea7 [tests] Move utility method into base test class. 2016-06-01 22:27:36 +02:00
Daniele Alessandri e386f5c732 Update README.
[ci skip]
2016-06-01 22:20:58 +02:00
Daniele Alessandri a06063d2e6 Update CHANGELOG.
[ci skip]
2016-06-01 21:59:28 +02:00
Daniele Alessandri d58929e5a5 Update README.
Let's try using Gitter...

[ci skip]
2016-06-01 20:59:25 +02:00
Daniele Alessandri ecab7e4642 Implement IteratorAggregate interface for Client.
Now it is possible to iterate over traversable aggregate connections
and get a key/value pair of $connectionId => $clientInstance for each
node.
2016-06-01 12:33:20 +02:00
Daniele Alessandri a22fc17800 Update CHANGELOG.
[ci skip]
2016-05-30 17:45:08 +02:00
Daniele Alessandri 7a50b02c36 [tests] Do not allow failures for HHVM on Travis CI. 2016-05-30 17:09:15 +02:00
Daniele Alessandri f6bf2b5977 [tests] Troubles with HHVM <= 3.6.6 and float timeouts.
HHVM is still being used by Travis CI but this bug makes the build to
take 14 minutes to complete, which is unacceptable.
2016-05-30 17:09:02 +02:00
Daniele Alessandri 5850029f89 Update README.
[ci skip]
2016-05-28 20:28:18 +02:00
Daniele Alessandri f64bd83f9d Update README.
[ci skip]
2016-05-28 20:27:10 +02:00
Daniele Alessandri 922e56b480 Iterate only over connections mapped in slots map.
Iterating over Predis\Connection\Aggregate\RedisCluster returns all
the connections currently mapped in the slots map instead of just the
ones initialized in the pool.

When the slots map is retrieved from Redis (which by default is done
automatically) this allows to iterate over all of the current master
nodes of the cluster. When the underlying use of "CLUSTER SLOTS" is
disabled the iteration returns only connections with a slots range
associated in their parameters or initialized by `-MOVED` responses
to make the behaviour of the iteration consistent between the two
modes of operation.
2016-05-28 17:58:38 +02:00
Daniele Alessandri 5a0dfc3602 Fix parameters overriding for sentinels.
Different fix than PR #339 but thanks @phofmann-trust for spotting.
2016-05-28 15:53:23 +02:00
Daniele Alessandri 39a6e18d71 Update README of test directory.
[ci skip]
2016-05-27 22:03:32 +02:00
Daniele Alessandri a19de6356c Update README.
[ci skip]
2016-05-27 21:31:54 +02:00
Daniele Alessandri 2d01a27e17 Fix fetching slots map from unreachable nodes.
When various nodes in the configuration are unreachable while trying
to send a command, we should attempt to contact a reachable node to
fetch an updated slots map up to $retryLimit times or until there are
no more servers in the pool before giving up.

It is possible that the slots map fetched from Redis contains stale
data and points to a dead server, this happens when the nodes still
have to agree that a master server is down before promoting a slave
to the role of master. In this case no further attempts to execute
the command are performed and an exception is thrown.

This still needs some more testing and will delay v1.0.4 a few days
past its scheduled release.
2016-05-27 14:30:07 +02:00
Daniele Alessandri ad7b8b08cb Run php-cs-fixer. 2016-05-26 09:54:09 +02:00
Daniele Alessandri 0ebfc0e7d2 Update README.
[ci skip]
2016-05-25 21:33:07 +02:00
Daniele Alessandri 9303029c13 Discard slave even when flag is "o_down". 2016-05-25 16:03:12 +02:00
Daniele Alessandri 0477499418 Fix ROLE expectation for read commands with no slaves.
This commit fixes #337.
2016-05-25 15:56:33 +02:00
Daniele Alessandri 9398a793a5 Bump default server profile to Redis 3.2. 2016-05-25 11:25:12 +02:00
Daniele Alessandri 0dff761a61 Merge branch 'v1.1-commands-redis-3.2' 2016-05-24 23:17:33 +02:00
Daniele Alessandri 02ed5a2f4e Update SPOP's @method signature in phpdoc.
SPOP accepts the optional "count" argument since Redis 3.2.
2016-05-24 23:16:00 +02:00
Daniele Alessandri 51932d82e8 Add new command: GEORADIUSBYMEMBER (Redis 3.2.0). 2016-05-24 22:35:27 +02:00
Daniele Alessandri 00000fde4e Add new command: GEORADIUS (Redis 3.2.0). 2016-05-24 22:30:06 +02:00
Daniele Alessandri aeb9b7ccae Check that STORE key in SORT ends up in same slot. 2016-05-24 22:08:41 +02:00
Daniele Alessandri 4273a2a8b6 Improve detection of STORE in SORT command arguments. 2016-05-24 21:38:16 +02:00
Daniele Alessandri b2284d015f Add new command: GEODIST (Redis 3.2.0). 2016-05-24 17:09:35 +02:00
Daniele Alessandri c4e0044269 Add new command: GEOPOS (Redis 3.2.0). 2016-05-24 17:09:33 +02:00
Daniele Alessandri 0b6b3b2558 Add new command: GEOHASH (Redis 3.2.0). 2016-05-24 16:00:08 +02:00
Daniele Alessandri 7ef9619f5c Add new command: GEOADD (Redis 3.2.0). 2016-05-24 16:00:06 +02:00
Daniele Alessandri 3362e474b6 Add new command: BITFIELD (Redis 3.2.0). 2016-05-24 16:00:04 +02:00
Daniele Alessandri 7b3f6e1db5 [tests] Add missing test for key prefix in HSTRLEN. 2016-05-24 15:11:49 +02:00
Daniele Alessandri 1536b2aa82 Add missing @method tag for HSTRLEN in phpdocs.
[ci skip]
2016-05-24 12:27:30 +02:00
Daniele Alessandri 1ab65da368 [tests] Missing @requiresRedisVersion for HSTRLEN. 2016-05-24 11:51:04 +02:00
Daniele Alessandri b11fb84282 Update description in package.ini. 2016-05-24 10:27:57 +02:00
Daniele Alessandri da009e59dd [tests] Test count argument for SPOP in Redis 3.2. 2016-05-23 15:53:30 +02:00
Daniele Alessandri 763acd232d Add new server profile for Redis 3.2 (new stable). 2016-05-23 12:43:00 +02:00
Daniele Alessandri 75a8253ee4 Apply minor grammar fix in comment.
[ci skip]
2016-05-23 12:09:43 +02:00
Daniele Alessandri 9368d98ac3 Update phpunit.xml.* 2016-05-22 20:21:58 +02:00
Daniele Alessandri 8f514fe893 Update description in composer.json. 2016-05-22 20:05:58 +02:00
Daniele Alessandri 6964f060b3 Fix README.
[ci skip]
2016-05-22 15:33:21 +02:00
Daniele Alessandri c125c218ae Apply fix to README for grammar slip.
[ci skip]
2016-05-22 15:10:37 +02:00
Daniele Alessandri 1f3a010681 Update CHANGELOG.
[ci skip]
2016-05-22 15:07:01 +02:00
Daniele Alessandri 49f6b46942 Update and improve README.
[ci skip]
2016-05-22 15:06:56 +02:00
Daniele Alessandri c1795de1f3 Fix README.
[ci skip]
2016-05-21 17:12:20 +02:00
Daniele Alessandri 528cd090d8 Update README.
Improved the section about replication by rewriting some parts of it
and adding details about redis-sentinel.

[ci skip]
2016-05-21 17:05:14 +02:00
Daniele Alessandri 5b3a5bbef9 Run php-cs-fixer. 2016-05-21 15:46:58 +02:00
Daniele Alessandri 349a70a08a Merge branch 'v1.1-sentinel'
This merge resolves #131.
2016-05-21 15:30:22 +02:00
Daniele Alessandri c1de65c4ee Swap params order in redis-sentinel constructor. 2016-05-21 15:23:24 +02:00
Daniele Alessandri ecbdaa1951 Update CHANGELOG.
[ci skip]
2016-05-20 22:33:29 +02:00
Daniele Alessandri 05209e6e7d Switch to next slave on -LOADING error response.
This prevents an early failure of the command execution on the client
when one slave gets back online but is still loading the dataset from
disk (when this happens, Redis returns the -LOADING error response).

This commit fixes #280.
2016-05-20 21:59:40 +02:00
Daniele Alessandri 803a5cfd5b Remove old references about HHVM being unstable. 2016-05-20 20:34:03 +02:00
Daniele Alessandri faaf853cbe [tests] Rename old branch for exclusion.
[ci skip]
2016-05-20 20:14:44 +02:00
Daniele Alessandri 257791edcf Update badges in CHANGELOG.
[ci skip]
2016-05-20 18:34:54 +02:00
Daniele Alessandri d0cc7a5947 Try again on connection failure to node in cluster.
When the connection to a node in the cluster fails in the attempt to
execute a command, Predis now removes the failed connection from the
cluster pool and contacts a random node to ask for a fresh slots map
and tries to execute the command once again.

When the cluster is configured to have each master replicated to one
or more slaves, one the slaves is automatically promoted to the role
of master by redis-cluster with this change being reflected in the
output of CLUSTER SLOTS, so the next execution should run just fine.

Our current approach is relatively naive as CLUSTER SLOTS is executed
against a random master node, meaning that the client must open a new
connection and execute one more roundtrip only to fetch the new slots
map. For now it is enough, it is still better than having the client
fail when you actually have somes slaves in your redis-cluster setup,
but one improvement could consist in caching the list of slaves for
each master returned in the response of CLUSTER SLOTS so that when a
connection fails the client can try to guess which connection should
use for the next attempt.

This commit closes #173, closes #215, and closes #314.
2016-05-20 13:17:06 +02:00
Daniele Alessandri 5adafb352c Evict connection from slots cache when removed. 2016-05-20 11:43:06 +02:00
Daniele Alessandri d6d307696a [tests] Add tests for redis-sentinel connection. 2016-05-19 22:15:55 +02:00
Daniele Alessandri f3999660fe Fix bug during assertion of connection role.
ROLE was not being sent to master when still disconnected and with an
empty slaves pool preventing the client from checking the actual role
of the server upon connect().
2016-05-19 19:06:49 +02:00
Daniele Alessandri 18e846c2ad Fix bug trying to switch to an unknown connection. 2016-05-19 17:42:51 +02:00
Daniele Alessandri 7e4800010a Add minor comment for clarity. 2016-05-19 15:59:21 +02:00
Daniele Alessandri 6b5181b030 Do not wipe server list when removing connection. 2016-05-19 12:58:07 +02:00
Daniele Alessandri 01483234c7 [tests] Fix assertion.
PHPUnit_Util_Type::export() has been removed a while ago...
2016-05-19 11:43:39 +02:00
Daniele Alessandri cdadb796ad Bump min. version of PHPUnit (require-dev). 2016-05-19 11:43:29 +02:00
Daniele Alessandri 354e5e26da Improve handling of slots mapping via parameters.
When using redis-cluster it is now also possible to pass one slot or
non-contiguous ranges of slots via connection parameters in order to
improve the ability to pre-configure the slots map on the client.

Here is an example:

  $parameters = [
    'tcp://10.0.0.1:6379?slots=0-5460,5500-5600,11000',
    'tcp://10.0.0.2:6379?slots=5461-5499,5600-10921',
    'tcp://10.0.0.3:6379?slots=10922-10999,11001-16383',
  ];

This commit fixes #312 (props to @kenotr0n for the original PR).
2016-05-18 15:04:37 +02:00
Daniele Alessandri d55826f35c [tests] Fix tests from previous commit.
They did work, but I am not exactly sure why. Also added a missing
test for Predis\Connection\CompositeStreamConnection.
2016-05-17 20:06:30 +02:00
Daniele Alessandri 26d7147646 Update CHANGELOG. 2016-05-17 19:47:25 +02:00
Daniele Alessandri 973e8592e3 Throw when command sent in connect() returns error.
Common failures are the use of SELECT with a database index outside
the bound of the configured number of databases in redis.conf or the
use of a wrong password for authentication with AUTH.

This resolves #322.
2016-05-17 19:15:21 +02:00
Daniele Alessandri f696ed125e Update CHANGELOG. 2016-05-17 15:31:59 +02:00
Daniele Alessandri 9f6759ca1c Implement discovery in basic replication.
Now the client can discover the whole replication configuration by
asking to one of the servers (master has the precedence) using the
INFO REPLICATION command. This is obviously a best-effort fallback
and there is no strong guarantee about reliability and efficiency.

By enabling auto-discovery, the client automates this process when
the execution of a command fails because one of the target servers
is unreachable. The replication connection requires an instance of
connection factory associated to it in order to be able to create
new connections on the fly.

It is possible to enable the auto-discovery procedure easily via
client options:

  $client = new Predis\Client($servers, [
    'replication' => true,
    'autodiscovery' => true,
  ]);
2016-05-17 14:24:52 +02:00
Daniele Alessandri 89a1e236ce Use custom ID when adding connection with no alias. 2016-05-16 17:28:13 +02:00
Daniele Alessandri 97dfba1e98 Remove useless use directive. 2016-05-16 16:57:24 +02:00
Daniele Alessandri be8ac3e205 Handle error responses returned by redis-sentinel.
Fixes #289.
2016-05-16 16:50:28 +02:00
Daniele Alessandri a933a13087 [tests] Fix wrong method in mock. 2016-05-16 15:38:29 +02:00
Daniele Alessandri ce3d42b9cf Fix CHANGELOG.
[ci skip]
2016-05-15 22:03:34 +02:00
Daniele Alessandri 629329ac76 Fix failing test.
This was exactly what I meant with the @todo annotation, too bad I
forgot to temporarily adjust the test accordingly.
2016-05-15 21:54:00 +02:00
Daniele Alessandri a95860ce00 Bump year in LICENSE. 2016-05-15 21:38:50 +02:00
Daniele Alessandri 51e8d6c46e Update CHANGELOG. 2016-05-15 21:38:21 +02:00
Daniele Alessandri 126998631b Merge branch 'replication-improvements' 2016-05-15 21:37:35 +02:00
Daniele Alessandri d7f3b8c9d2 Update CHANGELOG. 2016-05-15 21:36:31 +02:00
Daniele Alessandri c9366212d0 Add methods to switch to master or random slave. 2016-05-15 21:27:43 +02:00
Daniele Alessandri 5b76b41fda Use master for connect() on empty slaves pool.
Internally the replication class uses this order to pick which server
it should connect to: current connection, one of the slaves, master.

If there is at least 1 slave, connect() will not fail even if master
is undefined. If there are no slaves, connect() will pick master. If
there are no connections registered for replication, connect() will
fail immediatly.
2016-05-15 20:53:09 +02:00
Daniele Alessandri b491dff126 Send read-only commands on next slave on failure.
If no other slave is available try again on master as last resort
before giving up and throwing an exception.
2016-05-15 20:53:06 +02:00
Daniele Alessandri 774b4014d9 Use master for read requests on empty slaves pool.
This is the last resort in case all of the slaves are unreachable.
2016-05-15 17:20:58 +02:00
Daniele Alessandri 00c5d7de19 Apply minor styling fix. 2016-05-15 17:09:43 +02:00
Daniele Alessandri e3412c4a1b Move member variable initialization. 2016-05-15 16:56:50 +02:00
Daniele Alessandri 44ebf5a5e4 Prevent warnings picking slave from empty pool. 2016-05-15 16:54:57 +02:00
Daniele Alessandri 0c99f9ef28 Disconnect before eventually throwing exception. 2016-05-15 15:56:16 +02:00
Daniele Alessandri 6e0743a29a Change method signature. 2016-05-15 15:53:14 +02:00
Daniele Alessandri b047e8b6a1 Rewrite the connection class for redis-sentinel.
Now we do not extend Predis\Connection\Aggregate\MasterSlaveReplication
anymore in order to obtain a more coherent implementation with the logic
of redis-sentinel and apply more optimizations by avoiding useless round
trips with sentinel servers.
2016-05-14 10:39:39 +02:00
Daniele Alessandri 7be83a1fd6 Merge remote-tracking branch 'github/pr/332' 2016-05-13 16:59:33 +02:00
Daniele Alessandri 367ed2c11c [tests] Remove deprecated phpunit directive. 2016-05-13 16:35:10 +02:00
Daniele Alessandri f0b3014cb4 Merge remote-tracking branch 'github/pr/333' 2016-05-13 16:32:48 +02:00
Daniele Alessandri a816adf6e7 Set default parameters via client options.
This is mostly useful when configuring the client to use redis-cluster
or redis-sentinel in order to set a common password for authentication
or database. In these kind of configurations it is impossible to pass
them via connection parameters as connections are created dinamically
by the client depending on the server response.
2016-05-13 14:56:40 +02:00
Ante Braovic 7cb20f5a0c updated .gitignore 2016-05-12 16:38:46 +02:00
Ante Braovic 62c547601c added zrevrangebylex in the list of available methods 2016-05-12 16:27:17 +02:00
Daniele Alessandri f8b3f18abf Implement ROLE for proper redis-sentinel support.
Once the client discovers the address of the master or a slave instance,
it must connect to that node and issue a ROLE command to verify that its
role still matches what the client got from the sentinel server.
2016-05-11 10:52:09 +02:00
Daniele Alessandri 0ec9f3351f Cap the number of retries on connection failure.
I think it is better to have a default limit to the number of attempts
when trying to send a command after a connection failure, I am just not
sure if 20 is a good value but we can adjust it later.
2016-05-10 17:36:30 +02:00
Daniele Alessandri 622e6c6ba2 Apply fixes on error responses from sentinel.
Fixes manually picked from @djagya's fork, thanks for spotting!
2016-05-10 17:09:37 +02:00
Daniele Alessandri bc212ad2b0 Update phpdoc. 2016-05-10 15:29:17 +02:00
Daniele Alessandri e896d89fa8 Merge remote-tracking branch 'github/pr/306' into v1.1-sentinel 2016-05-10 15:19:21 +02:00
Daniele Alessandri e851aaa21d Automatically assign aliases to slave connections. 2016-05-10 14:55:30 +02:00
Daniele Alessandri 7664f1f29b Improve client configuration for redis-sentinel.
Predis\Client now requires a list of connection parameters pointing to
sentinel instances and mandatory options "replication" and "service" set
respectively to "sentinel" and the chosen name for the master instance.

  $sentinels = ['tcp://127.0.0.1:5381', 'tcp://127.0.0.1:5382'];
  $options   = ['replication' => 'sentinel', 'service' => 'mymaster'];
  $client    = new Predis\Client($sentinels, $options);

Despite being nice and clean on the outside I am not really fond of the
code being used internally to make this kind of configuration possible.
Improvements in this respect would require a few breaking changes (not
even an option for a minor release) so things will change for the good
with Predis 2.0.
2016-05-10 14:19:05 +02:00
Daniele Alessandri 104e42cfe0 [tests] Fix test executed on current Redis unstable. 2016-05-10 10:58:46 +02:00
Daniele Alessandri b6a14cda04 [tests] Fix test executed on current Redis unstable. 2016-05-10 10:51:43 +02:00
Daniele Alessandri e61cb4e4f2 [tests] Fix wrong @group annotation 2016-05-09 17:49:12 +02:00
Daniele Alessandri 1349666494 Update CHANGELOG.
[ci skip]
2016-05-08 18:56:25 +02:00
Daniele Alessandri a0487ec5f4 Prevent failures serializing commands with "holes" in arguments array.
This could be triggered when passing an array with "holes" to variadic commands.
Connection classes based on the protocol serialized exposed by phpiredis were
not affected by this bug.

Fixes #316.
2016-05-08 18:43:50 +02:00
Remi Collet 57a7d5d3e6 fix tests, list of allowed commands have changed (redis 3.0.6) 2016-05-08 16:52:05 +02:00
Alexander Cheprasov 8235228acd Update ClientInterface.php 2016-05-08 16:46:27 +02:00
John Maguire 3ceaa39a3c Update useClusterNodes() docs to indicate default 2016-05-08 15:21:11 +02:00
Daniele Alessandri 0baad16064 Fix bug in HSCAN-based iterator when hash have integer fields.
When iterating a hash containing integer fields our iterator abstraction
based on HSCAN was always returning "0" as a field name after the first
$field => $value pair due to a wrong assumption on how the PHP function
array_shift() (which is used internally to advance to the next pair in
our buffered response to HSCAN) works.

The ZSCAN-based iterator had this very same bug which was already fixed
in 24e19a9 so I am not sure how this one went unnoticed until now.
2016-05-08 15:16:05 +02:00
Ante Lucic 939c0821dd add docblocks to MasterSlaveReplication 2016-05-05 14:22:32 +02:00
Chris Butler eb154cd43d Replace automatic retry yes/no with a retry limit 2016-01-27 12:35:46 +00:00
Daniele Alessandri d8da74adac Update documents. 2015-08-16 20:25:42 +02:00
Daniele Alessandri fda293b573 Add example of replication configuration using redis-sentinel. 2015-08-16 20:25:42 +02:00
Daniele Alessandri 24fb72a732 Implement ability to fetch an updated list of sentinels from active sentinel.
This can be optionally done automatically but is disabled by default, just use
SentinelReplication::setUpdateSentinels() accordingly to enable the automatic
fetching of an updated list of sentinels.
2015-08-16 20:25:42 +02:00
Daniele Alessandri 8d15503d6c Implement transparent auto-retry of commands upon server failure.
By default, when the current server dies while executing a command Predis asks
for a new configuration to one of the sentinels and re-issues the same command.

This behavior can be disabled calling SentinelReplication::setAutomaticRetry().
2015-08-16 20:25:39 +02:00
Daniele Alessandri 587fbbc446 Specify a timeout for the connect() operation to sentinel servers.
This value should be reasonably low so that the client can fallback to the next
sentinel if the connect() operation is taking too much and slowing things down.

When the connection parameters of sentinels contain a "timeout" parameter, its
value takes the precedence over the default sentinels timeout.
2015-08-16 16:28:21 +02:00
Daniele Alessandri 370d4e72c2 Add initial support for redis-sentinel.
This is a first implementation that is based on the work of @vmattila but some
more changes and missing bits are required in order to be considered complete.

To leverage redis-sentinel the client must be configured using the "aggregate"
option instead of the usual "replication" option, thought this may change for
the release of Predis v1.1.0 (it __will__ change for Predis v2.0.0 but this is
a whole different matter). This is a configuration example:

  use Predis\Connection\Aggregate\SentinelReplication;

  $sentinels = [
    'tcp://127.0.0.1:5381',
    'tcp://127.0.0.1:5382',
    'tcp://127.0.0.1:5383',
  ];

  $client = new Predis\Client($sentinels, [
    'service' => 'nrk-master',
    'aggregate' => function() {
      return function ($sentinels, $options) {
        $service = $option->service;
        $connections = $options->connections;

        return new SentinelReplication($sentinels, $service, $connections);
      };
    },
  ]);

The missing bits right now are:

  - A more solid handling of failures when querying sentinels.
  - When the connection fails while executing a command on one of the servers,
    we should query again a sentinel and then re-issue the command accordingly.
2015-08-16 16:28:13 +02:00
Daniele Alessandri 6bc1a38123 Fix wrong link in phpdoc.
Fixes #270.

[ci skip]
2015-07-30 20:51:15 +02:00
Daniele Alessandri f7d7cd59c4 Update CHANGELOG with release details of v1.0.3.
[ci skip]
2015-07-30 20:38:21 +02:00
Daniele Alessandri d46de81d91 Fix severe regression on HHVM.
Apparently HHVM is more strict than PHP in stream_socket_client() and does not
like at all IPv4 addresses and hostnames eclosed in square brackets. Note that
it is not that weird as square brackets are mandatory only when IPv6 addresses
are embedded in URI strings, so it is more like a weird incompatiblity of HHVM
with the behaviour of the standard PHP interpreter. The connect() attempt fails
but not due to the server being unavailable or some connectivity issue.

The important lesson is: never rely on undocumented behaviours especially when
targeting different runtimes, and do not forget to run the test suite on every
platform right before release like I unfortunately did.
2015-07-30 20:22:37 +02:00
Daniele Alessandri f4b6c578be Update CHANGELOG with release details of v1.0.2.
[ci skip]
2015-07-30 11:21:36 +02:00
Daniele Alessandri 2fc0e56a09 Preserve remainder of path in URI after database (redis scheme). 2015-07-30 11:11:30 +02:00
Daniele Alessandri 8106c8b00a Use PHP_VERSION_ID constant. 2015-07-30 10:57:20 +02:00
Daniele Alessandri b8bfd1405d Update README.
[ci skip]
2015-07-30 10:50:14 +02:00
Daniele Alessandri a7ee80702c Implement full support for IPv6.
Using IPv6 with Predis was basically impossible due to various inconsistencies
and bugs through the library, now it is supported by all the connection classes.

Following the standard for IPv6 literal addresses in URI strings, the IP literal
must be enclosed within square brackets when passing the parameters as a string:

  $parameters = 'tcp://[2001:db8:0:f101::1]:6379';

See https://tools.ietf.org/html/rfc3986#section-3.2.2 for further details.

This commit also fixes #239 making redis-cluster usable with nodes using IPv6.
2015-07-29 23:01:55 +02:00
Daniele Alessandri 0eaa1d929d Reduce unneeded code duplication in URI parsing. 2015-07-29 17:00:12 +02:00
Daniele Alessandri a26390915d Strip brackets from host when parsing embedded IPv6 address.
I don't know why PHP's parse_url() does not do that, it does not make
sense when the IP is by itself so maybe it is a bug?
2015-07-29 16:44:11 +02:00
Daniele Alessandri 24c0f846c7 Use static:: for invoking static methods in key prefix processor.
This trivial change makes it possible to use overridden static methods
when extending Predis\Command\Processor\KeyPrefixProcessor. PHP always
invokes the static methods of a parent class when using self:: even if
the extended classes override them.
2015-07-29 12:28:44 +02:00
Daniele Alessandri f43433baf1 Add new command: HSTRLEN (Redis 3.2.0).
Also bump the unstable profile version to 3.2.
2015-07-29 11:50:20 +02:00
Daniele Alessandri 3dfe62a5b5 Change format required for URI strings when using "unix" scheme.
Instead of using "unix://" you should just use "unix:":

  $old = 'unix:///path/to/redis.sock';
  $new = 'unix:/path/to/redis.sock';

The old format should be considered obsolete and will not be supported
starting from the next major release of Predis.
Meh
2015-07-28 11:23:33 +02:00
Daniele Alessandri ebb72377bb Implement TLS/SSL-encrypted connections.
This is handy for accessing remote Redis instances over a secure SSL connection
which is currently a popular option or even requirement with many cloud hosting
environments.

In order to configure the client to use an SSL-encrypted connection the scheme
in the connection parameters must be either "tsl" or "rediss" and a set of SSL
options (see http://php.net/manual/en/context.ssl.php) must be provided via the
"ssl" parameter as a named array.

The following example (which does not necessarily represent an example of good
practices!) illustrates how to set the "ssl" parameter using a named array and
the equivalent URI string:

  // Parameters as named array
  $parameters = [
    'scheme' => 'tls',
    'host'   => '127.0.0.1',
    'ssl'    => [
        'cafile'            => '/home/adaniele/redis.pem',
        'verify_peer_name'  => false,
    ],
  ];

  // Parameters as URI string
  $parameters = 'tls://127.0.0.1?ssl[cafile]=redis.pem&ssl[verify_peer_name]=1';

Support for SSL is currently limited to the Predis\Connection\StreamConnection
backend but we intend to investigate if it is possible to extend this feature
to Predis\Connection\PhpiredisStreamConnection in the future.

Be aware that using encrypted connections may lead to a performance degradation
especially in the connect() operation due to the overhead of the TLS handshake.
Unfortunately there is no real way to reuse SSL sessions from userland, aside
from enabling persistent connections, but this will work only on PHP >= 7.0.0
because previous versions of PHP do not provide enough info about a stream from
get_stream_meta_data().

NOTE: Redis does not have built-in support for SSL-encrypted connections, but if
you want to expose it to public networks you may want to rely on "stunnel".
2015-07-27 19:16:20 +02:00
Daniele Alessandri cb91ad1aee Rephrase latest entries of the CHANGELOG.
[ci skip]
2015-07-26 22:34:20 +02:00
Daniele Alessandri 5172555009 Rephrase some parts of the README.
[ci skip]
2015-07-26 22:28:58 +02:00
Daniele Alessandri 4dea992cd2 [tests] Fix typo. 2015-07-25 21:58:52 +02:00
Daniele Alessandri 80af1af459 [tests] Require pcntl extension to run blocking PubSub\Consumer test. 2015-07-25 21:56:36 +02:00
Daniele Alessandri abf2ce0bd1 [tests] Exlude persistent connections tests under PHP 5.3.
The get_resource_type() function does not differentiate between normal streams
and persistent streams, so we cannot really test this case.
2015-07-25 21:52:55 +02:00
Daniele Alessandri c7cae66a97 [tests] Improve code-reuse in tests for the Predis\Connection namespace. 2015-07-25 21:32:36 +02:00
Daniele Alessandri 4f30ac6370 Run php-cs-fixer. 2015-07-25 19:13:05 +02:00
Daniele Alessandri 1c8eb7ff6b [tests] Share common test among connection classes. 2015-07-25 19:13:01 +02:00
Daniele Alessandri cb09a7a2b5 [tests] Use @requires annotation. 2015-07-25 18:37:11 +02:00
Daniele Alessandri d69d6c726d Remove Connection\Aggregate\RedisCluster::setDefaultParameters().
The redis-cluster connection relies on a client-initialized connection factory,
so use Connection\Factory::setDefaultParameters() to set the default parameters
that must be applied to new nodes discovered through -MOVED or -ASK responses.
2015-07-25 18:37:11 +02:00
Daniele Alessandri 2b0c8fbb26 Remove "timeout" as a default parameter in Connection\Parameters.
Falling back to a default timeout values should be done by the connection class
as it is an implementation detail that may vary depending on the backend.
2015-07-25 18:37:11 +02:00
Daniele Alessandri 55aab86800 Add support for default connection parameters in Connection\Factory.
These parameters augment the set of user-supplied parameters when creating a new
connection, but they do not override specific parameters when already defined.

An example of self-contained configuration using client options:

  $client = new Predis\Client('tcp://127.0.0.1', [
    'parameters' => [
      'timeout' => 10,
    ],
    'connections' => function ($options) {
      $factory = $options->getDefault('connections');
      $factory->setDefaultParameters($options->parameters);

      return $factory;
    },
  ]);

This change will be useful for both redis-cluster and redis-sentinel as it makes
it easy to apply shared parameters such as a common password for authentication
when the server returns one ore more new nodes from response (think of -MOVED).
2015-07-25 18:37:11 +02:00
Daniele Alessandri 6cce9eb35c Run php-cs-fixer. 2015-07-25 18:37:10 +02:00
Daniele Alessandri 8277afc7a8 Use "persistent" with non-bool strings to open different persistent connections.
stream_socket_client() has the undocumented ability to open different persistent
streams by providing a path in the $address string. Previously we supported this
behaviour with a combination of "persistent" and "path" (see #139) but this can
be confusing, especially now that we support the redis:// scheme which uses the
path part of an URI string to specify a database number.

After this change, instead of using an URI string such as:

  $parameters = 'tcp://127.0.0.1/first?persistent=1&database=5';

You should use the following ones:

  $parameters = 'tcp://127.0.0.1?persistent=first&database=5';
  $parameters = 'redis://127.0.0.1/5?persistent=first';

Avoiding "path" makes even more sense when using array connection parameters:

  $parameters = [
    'host'       => '127.0.0.1',
    'database'   => 5,
    'persistent' => 'first',
  ]

This feature is not supported when using UNIX domain sockets because the path
trick of stream_socket_client() does not play well with the actual path of the
socket file. The client will throw an InvalidArgumentException exception to
notify the user.

NOTE: unfortunately we have to disable the tests for persistent connections when
running under HHVM due to a bug in their implementation of get_resource_type()
preventing us to recognize a persistent stream from userland code.
2015-07-25 18:34:51 +02:00
Daniele Alessandri b012af3247 Reorganize stream resource creation for stream-based connections. 2015-07-25 14:39:05 +02:00
Daniele Alessandri 7fa3c55f51 Move assertion for connection parameters out of abstract connection class.
Each connection class should implement its own checks for connection parameters,
even at the cost of some code duplication (inheritance is not just about code
reuse after all).
2015-07-25 14:37:22 +02:00
Daniele Alessandri 73c9b9d5be [tests] Fix some tests methods names. 2015-07-25 10:04:27 +02:00
Daniele Alessandri 59798e1f9e [tests] Apply small changes to commands tests with expirations. 2015-07-25 10:04:24 +02:00
Daniele Alessandri d0012e67ff [tests] Cover SET modifiers EX, PX, NX|XX (Redis >= 2.6.12). 2015-07-25 09:10:07 +02:00
Daniele Alessandri 65727ca07d Update .gitattributes file.
[ci skip]
2015-07-24 23:30:21 +02:00
Daniele Alessandri 4ac1a81aa4 Restore alignment for a few equals symbols. 2015-07-24 23:17:05 +02:00
Daniele Alessandri 8dd9893a2f Run php-cs-fixer with new configuration. 2015-07-24 23:17:02 +02:00
Daniele Alessandri 35e97967fd Add .php_cs configuration file for php-cs-fixer. 2015-07-24 23:16:19 +02:00
Daniele Alessandri 7282ca2b52 Remove unneeded "use" imports. 2015-07-24 21:25:40 +02:00
Daniele Alessandri c436e01353 Fix cluster strategy to handle variadic EXISTS (Redis >= 3.0.3). 2015-07-24 18:30:00 +02:00
Daniele Alessandri df2e9f4e71 Fix prefix processor to handle variadic EXISTS (Redis >= 3.0.3). 2015-07-24 18:29:51 +02:00
Daniele Alessandri 6590c44a27 Run php-cs-fixer against codebase in src/ and tests/. 2015-07-24 18:04:42 +02:00
Daniele Alessandri bb89cf67e2 [tests] Fix me being stupid here. 2015-07-24 17:35:27 +02:00
Daniele Alessandri 7c1d324f30 [tests] Apply some fixes and improvements and remove old stuff. 2015-07-24 17:26:06 +02:00
Daniele Alessandri bb6287f22d [tests] Fix obviously wrong annotation. 2015-07-24 16:20:50 +02:00
Daniele Alessandri f315404649 Update CHANGELOG. 2015-07-24 16:06:31 +02:00
Daniele Alessandri 8c76bd6761 Do not parse response to SETNX into boolean value. 2015-07-24 15:52:59 +02:00
Daniele Alessandri 38fafb22ba Do not parse response to MSETNX into boolean value. 2015-07-24 15:52:53 +02:00
Daniele Alessandri 5ce683be09 Do not parse response to SMOVE into boolean value. 2015-07-24 15:52:50 +02:00
Daniele Alessandri e531d39f53 Do not parse response to SISMEMBER into boolean value. 2015-07-24 15:52:45 +02:00
Daniele Alessandri fda9f022dd Do not parse response to RENAMENX into boolean value. 2015-07-24 15:52:27 +02:00
Daniele Alessandri 44e92d8a2f Do not parse response to PERSIST into boolean value. 2015-07-24 15:50:43 +02:00
Daniele Alessandri e75b69f6fd Do not parse response to MOVE into boolean value. 2015-07-24 15:50:38 +02:00
Daniele Alessandri 082c51146a Do not parse response to EXPIREAT into boolean value. 2015-07-24 15:50:34 +02:00
Daniele Alessandri b02e3f4911 Do not parse response to EXPIRE into boolean value. 2015-07-24 15:50:21 +02:00
Daniele Alessandri 87921f7a05 Do not parse response to PFADD into boolean value. 2015-07-24 15:50:10 +02:00
Daniele Alessandri 3fdfc50683 Do not parse response to HSETNX into boolean value. 2015-07-24 15:50:05 +02:00
Daniele Alessandri ae26ecc8bf Do not parse response to HSET into boolean value. 2015-07-24 15:50:01 +02:00
Daniele Alessandri 231a3440f7 Do not parse response to HEXISTS into boolean value. 2015-07-24 15:49:55 +02:00
Daniele Alessandri ab20c52115 Do not parse response to EXISTS into boolean value.
Starting with Redis 3.0.3 the EXISTS command is variadic so that it is
possible to check for the existence of multiple keys in one request,
with the server returning the number of keys found.

This change could break codebases relying on strict comparison (===)
against a boolean value, but just doing $redis->exists('key') == TRUE
is totally fine.
2015-07-24 15:49:40 +02:00
Daniele Alessandri 2eaf3d8595 Remove useless if condition. 2015-07-24 12:40:08 +02:00
Daniele Alessandri bb909c573d No need to check the scheme here.
Bug introduced in v1.0-dev after changes to support redis scheme.

Fixes #268
2015-07-24 10:39:06 +02:00
Daniele Alessandri 35f135be30 Add support for the 'redis://' scheme in URI strings.
The URI string will be handled following the rules as described by the
the provisional IANA registration document that can be found on IANA's
website: http://www.iana.org/assignments/uri-schemes/prov/redis.
2015-07-23 19:16:05 +02:00
Daniele Alessandri 474f3ddbe7 Add missing command: MIGRATE (Redis 2.6.0).
Fixes #209.
2015-07-23 17:22:53 +02:00
Daniele Alessandri ae054fc7e1 [tests] Fix minor oversight. 2015-07-23 16:46:07 +02:00
Daniele Alessandri 8168def08f Switch to container-based builds on Travis CI.
We do not need "sudo" anyway.
See http://docs.travis-ci.com/user/migrating-from-legacy/ for details.
2015-07-23 12:15:14 +02:00
Daniele Alessandri 15f3b6030c [tests] Improve assert failure messages for replication stategy tests. 2015-07-23 11:38:49 +02:00
Daniele Alessandri c0d1a74447 Fix missing BITPOS in replication strategy. 2015-07-23 11:30:32 +02:00
Daniele Alessandri 6882d08373 Add missing BITPOS command in key prefix processor.
See #265.
2015-07-23 11:06:57 +02:00
Daniele Alessandri 871d526300 Exclude .php-version file.
[ci skip]
2015-07-19 09:35:52 +02:00
Richard Heelin d84bca131b Emit socket error if ipredis connection has been lost/reset.
The current code is checking for a failure (return false) or an empty buffer string, however
neither of these will be the case if the connection has been reset or has errored. According
to the docs for socket_recv, $buffer will be set to null if the connection is reset or their
is no data. As currently null is not allowed for, we enter an infinite loop, to prevent this
I've added null to the things we check before we emit a socket error. This prevents the
infinite loop and correctly results in an Exception if the connection is lost/reset.

Conflicts:
	src/Connection/PhpiredisSocketConnection.php
2015-07-07 18:51:00 +02:00
Daniele Alessandri 5cd4f41424 [tests] Fix base test case class to handle required Redis versions.
This change is needed due to some internal changes in one of the
latest minor releases of PHPUnit 4.x that essentially broke how we
were checking for the required Redis version from method annotations.
2015-07-07 17:43:49 +02:00
Daniele Alessandri b3bcf466f5 [tests] Adapt to internal encoding changes for lists in Redis 3.0. 2015-07-07 16:09:20 +02:00
Daniele Alessandri 24d8f68e7f Support ZADD modifiers when using simplified command signature.
The NX|XX, CH and INCR modifiers for ZADD are available in Redis since
version 3.0.2. See http://redis.io/commands/ZADD for additional info.
2015-07-07 16:05:56 +02:00
Daniele Alessandri 0a8754f9b6 Merge remote-tracking branch 'github/pr/235' 2015-07-07 14:48:41 +02:00
Daniele Alessandri 614049cd12 [tests] Remove hhvm-nightly from Travis CI configuration.
See https://github.com/travis-ci/travis-ci/issues/3788 for details.
2015-07-07 14:42:10 +02:00
Daniele Alessandri 5911145d9f [tests] It's 7.0 and not 5.7! 2015-07-07 14:40:34 +02:00
Daniele Alessandri 8029faf72a [tests] Add PHP 5.7 to Travis CI configuration. 2015-07-07 13:23:09 +02:00
Daniele Alessandri e2c08cfe88 Minor tweak in .editorconfig file.
[ci skip]
2015-07-06 18:42:41 +02:00
Daniele Alessandri 3b31b91ad8 Commit .editorconfig file.
See http://editorconfig.org for more info about EditorConfig

[ci skip]
2015-07-06 18:39:28 +02:00
Daniele Alessandri 7a984e7b76 Fix wrong phpdoc.
Thanks @Aliance for spotting this oversight.
2015-07-03 12:12:39 +02:00
Daniele Alessandri 33e6ee2e48 [tests] Implement test for #257 to guard against regressions. 2015-07-03 12:01:47 +02:00
Daniele Alessandri c577c20481 Merge remote-tracking branch 'github/pr/258' 2015-07-03 11:05:42 +02:00
Dominic Scheirlinck 6fbe2d5fa3 Don't check for __invoke on non-objects
`method_exists` will cause autoloading for strings. Fixes #257
2015-06-16 15:55:20 +12:00
493 changed files with 15462 additions and 9595 deletions
+1 -3
View File
@@ -11,7 +11,7 @@ branches:
except:
- v0.5
- v0.6
- php5.2_backport
- v0.6-PHP_5.2
- documentation
services: redis-server
before_script:
@@ -20,6 +20,4 @@ before_script:
script:
- vendor/bin/phpunit -c phpunit.xml.travisci
matrix:
allow_failures:
- php: hhvm
fast_finish: true
+170
View File
@@ -1,3 +1,173 @@
v2.0.0 (201x-xx-xx)
================================================================================
- Accepted values for some client options have changed, this is the new list of
accepted values:
- `aggregate`: callable returning an aggregate connection.
- `cluster`: string value (`predis`, `redis`), callable returning an aggregate
connection.
- `replication`: string value (`predis`, `sentinel`), callable returning an
aggregate connection.
- `commands`: command factory, named array mapping command IDs to PHP classes,
callable returning a command factory or a named array.
- `connections`: connection factory, callable returning a connection factory,
named array mapping connection schemes to PHP classes.
- `prefix`: string value, command processor, callable.
- `exceptions`: boolean.
Note that both the `cluster` and `replication` options now return a closure
acting as initializer instead of an aggregate connection instance.
- Client option classes now live in the `Predis\Configuration\Option` namespace.
- Classes for Redis commands have been moved into the new `Predis\Command\Redis`
namespace and each class name mirrors the respective Redis command ID.
- The concept of server profiles is gone, the library now uses a single command
factory to create instances of commands classes. The `profile` option has been
replaced by the `commands` option accepting `Predis\Command\FactoryInterface`
to customize the underlying command factory. The default command factory class
used by Predis is `Predis\Command\RedisFactory` and it still allows developers
to define or override commands with their own implementations. In addition to
that, `Predis\Command\RedisFactory` relies on a convention-over-configuration
approach by looking for a suitable class with the same name as the command ID
in the `Predis\Command\Redis` when the internal class map does not contain a
class associated.
- The method `Predis\Client::getClientFor($connectionID)` has been replaced by
`Predis\Client::on($connectionID, $callable = null)`. This new method returns
a new client instance for the specified node just like before when the second
argument is omitted, otherwise the callback is invoked and the new client is
passed to it. The value returned by the callback is used as the return value
of the "on()" method.
- Changed the signature for the constructor of `Predis\Command\RawCommand`.
- The `Predis\Connection\Aggregate` namespace has been split into two separate
namespaces for cluster backends (`Predis\Connection\Cluster`) and replication
backends (`Predis\Connection\Replication`).
- The methods `switchToMaster()` and `switchToSlave()` have been promoted to be
part of `Predis\Connection\Replication\ReplicationInterface` while the method
`switchTo($connection)` has been removed from it.
- The method `Predis\Connection\Cluster\PredisCluster::executeCommandOnNodes()`
has been removed as it is possible to achieve the same by iterating over the
connection or, even better, over the client instance in order to execute the
same command against all of the registered connections.
v1.1.0 (2016-06-02)
================================================================================
- The default server profile for the client now targets Redis 3.2.
- Responses to the following commands are not casted into booleans anymore, the
original integer value is returned: `SETNX`, `MSETNX`, `SMOVE`, `SISMEMBER`,
`HSET`, `HSETNX`, `HEXISTS`, `PFADD`, `EXISTS`, `MOVE`, `PERSIST`, `EXPIRE`,
`EXPIREAT`, `RENAMENX`. This change does not have a significant impact unless
when using strict comparisons (=== and !==) the returned value.
- Non-boolean string values passed to the `persistent` connection parameter can
be used to create different persistent connections. Note that this feature was
already present in Predis but required both `persistent` and `path` to be set
as illustrated by [#139](https://github.com/nrk/predis/pull/139). This change
is needed to prevent confusion with how `path` is used to select a database
when using the `redis` scheme.
- The client throws exceptions when Redis returns any kind of error response to
initialization commands (the ones being automatically sent when a connection
is established, such as `SELECT` and `AUTH` when database and password are set
in connection parameters) regardless of the value of the exception option.
- Using `unix:///path/to/socket` in URI strings to specify a UNIX domain socket
file is now deprecated in favor of the format `unix:/path/to/socket` (note the
lack of the double slash after the scheme) and will not be supported starting
with the next major release.
- Implemented full support for redis-sentinel.
- Implemented the ability to specify default connection parameters for aggregate
connections with the new `parameters` client option. These parameters augment
the usual user-supplied connection parameters (but do not take the precedence
over them) when creating new connections and they are mostly useful when the
client is using aggregate connections such as redis-cluster and redis-sentinel
as these backends can create new connections on the fly based on responses and
redirections from Redis.
- Redis servers protected by SSL-encrypted connections can be accessed by using
the `tls` or `rediss` scheme in connection parameters along with SSL-specific
options in the `ssl` parameter (see http://php.net/manual/context.ssl.php).
- `Predis\Client` implements `IteratorAggregate` making it possible to iterate
over traversable aggregate connections and get a new client instance for each
Redis node.
- Iterating over an instance of `Predis\Connection\Aggregate\RedisCluster` will
return all the connections mapped in the slots map instead of just the ones in
the pool. This change makes it possible, when the slots map is retrieved from
Redis, to iterate over all of the master nodes in the cluster. When the use of
`CLUSTER SLOTS` is disabled via the `useClusterSlots()` method, the iteration
returns only the connections with slots ranges associated in their parameters
or the ones initialized by `-MOVED` responses in order to make the behaviour
of the iteration consistent between the two modes of operation.
- Various improvements to `Predis\Connection\Aggregate\MasterSlaveReplication`
(the "basic" replication backend, not the new one based on redis-sentinel):
- When the client is not able to send a read-only command to a slave because
the current connection fails or the slave is resyncing (`-LOADING` response
returned by Redis), the backend discards the failed connection and performs
a new attempt on the next slave. When no other slave is available the master
server is used for read-only commands as last resort.
- It is possible to discover the current replication configuration on the fly
by invoking the `discover()` method which internally relies on the output of
the command `INFO REPLICATION` executed against the master server or one of
the slaves. The backend can also be configured to do this automatically when
it fails to reach one of the servers.
- Implemented the `switchToMaster()` and `switchToSlave()` methods to make it
easier to force a switch to the master server or a random slave when needed.
v1.0.4 (2016-05-30)
================================================================================
- Added new profile for Redis 3.2 with its new commands: `HSTRLEN`, `BITFIELD`,
`GEOADD`, `GEOHASH`, `GEOPOS`, `GEODIST`, `GEORADIUS`, `GEORADIUSBYMEMBER`.
The default server profile for Predis is still the one for Redis 3.0 you must
set the `profile` client option to `3.2` when initializing the client in order
to be able to use them when connecting to Redis 3.2.
- Various improvements in the handling of redis-cluster:
- If the connection to a specific node fails when executing a command, the
client tries to connect to another node in order to refresh the slots map
and perform a new attempt to execute the command.
- Connections to nodes can be preassigned to non-contiguous slot ranges via
the `slots` parameter using a comma separator. This is how it looks like
in practice: `tcp://127.0.0.1:6379?slots=0-5460,5500-5600,11000`.
- __FIX__: broken values returned by `Predis\Collection\Iterator\HashKey` when
iterating hash keys containing integer fields (PR #330, ISSUE #331).
- __FIX__: prevent failures when `Predis\Connection\StreamConnection` serializes
commands with holes in their arguments (e.g. `[0 => 'key:0', 2 => 'key:2']`).
The same fix has been applied to `Predis\Protocol\Text\RequestSerializer`.
(ISSUE #316).
v1.0.3 (2015-07-30)
================================================================================
- __FIX__: the previous release introduced a severe regression on HHVM that made
the library unable to connect to Redis when using IPv4 addresses. Code running
on the standard PHP interpreter is not affected.
v1.0.2 (2015-07-30)
================================================================================
+8
View File
@@ -18,6 +18,14 @@ least to some degree).
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 SSL-encrypted connections? ###
Yes. Encrypted connections are mostly useful when connecting to Redis instances exposed by various
cloud hosting providers without the need to configure an SSL proxy, but you should also take into
account the general performances degradation especially during the connect() operation when the TLS
handshake must be performed to secure the connection. Persistent SSL-encrypted connections may help
in that respect, but they are supported only when running on PHP >= 7.0.0.
### Does Predis support transparent (de)serialization of values? ###
No and it will not ever do that by default. The reason behind this decision is that serialization is
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2009-2015 Daniele Alessandri
Copyright (c) 2009-2016 Daniele Alessandri
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
+196 -106
View File
@@ -1,50 +1,52 @@
# Predis #
[![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)
[![License](https://poser.pugx.org/predis/predis/license.svg)](https://packagist.org/packages/predis/predis)
[![Build Status](https://travis-ci.org/nrk/predis.svg?branch=v1.0)](https://travis-ci.org/nrk/predis)
[![HHVM Status](http://hhvm.h4cc.de/badge/predis/predis.png)](http://hhvm.h4cc.de/package/predis/predis)
[![Software license][ico-license]](LICENSE)
[![Latest stable][ico-version-stable]][link-packagist]
[![Latest development][ico-version-dev]][link-packagist]
[![Monthly installs][ico-downloads-monthly]][link-downloads]
[![Build status][ico-travis]][link-travis]
[![HHVM support][ico-hhvm]][link-hhvm]
[![Gitter room][ico-gitter]][link-gitter]
Predis is a flexible and feature-complete [Redis](http://redis.io) client library for PHP >= 5.3.
Flexible and feature-complete [Redis](http://redis.io) client for PHP >= 5.3 and HHVM >= 2.3.0.
By default this library 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 RESP Protocol](http://redis.io/topics/protocol). An asynchronous implementation of Predis
is available through [Predis\Async](https://github.com/nrk/predis-async) (__experimental__).
__ATTENTION:__ you are on the README file of an unstable branch of Predis specifically meant for the
development of future releases. This means that the code on this branch is potentially unstable, and
breaking change may happen without any prior notice. Do not use it in production environments or use
it at your own risk!
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 and not yet 100% compatible with the _standard_ PHP.
Predis does not require any additional C extension by default, but it can be optionally paired with
[phpiredis](https://github.com/nrk/phpiredis) to lower the overhead of the serialization and parsing
of the [Redis RESP Protocol](http://redis.io/topics/protocol). For an __experimental__ asynchronous
implementation of the client you can refer to [Predis\Async](https://github.com/nrk/predis-async).
More details about this project can be found on the [frequently asked questions](FAQ.md) and on the
[wiki](https://github.com/nrk/predis/wiki).
More details about this project can be found on the [frequently asked questions](FAQ.md).
## Main features ##
- Support for a wide range of Redis versions (from __2.0__ to __3.0__) using profiles.
- Clustering 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 (write operations on master, read operations on 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`.
- Support for Redis from __2.0__ to __3.2__.
- Support for clustering using client-side sharding and pluggable keyspace distributors.
- Support for [redis-cluster](http://redis.io/topics/cluster-tutorial) (Redis >= 3.0).
- Support for master-slave replication setups and [redis-sentinel](http://redis.io/topics/sentinel).
- Transparent key prefixing of keys using a customizable prefix strategy.
- Command pipelining on both single nodes and clusters (client-side sharding only).
- Abstraction for Redis transactions (Redis >= 2.0) and CAS operations (Redis >= 2.2).
- Abstraction for Lua scripting (Redis >= 2.6) and 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.
- Connections are established lazily by the client upon the first command and can be persisted.
- Connections can be established via TCP/IP (also TLS/SSL-encrypted) or UNIX domain sockets.
- Support for [Webdis](http://webd.is) (requires both `ext-curl` and `ext-phpiredis`).
- Support for custom connection classes for providing different network or protocol backends.
- Flexible system for defining custom commands and server profiles.
- Flexible system for defining custom commands and override the default ones.
## How to use Predis ##
## How to _install_ and use Predis ##
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).
This library can be found on [Packagist](http://packagist.org/packages/predis/predis) for an easier
management of projects dependencies using [Composer](http://packagist.org/about-composer) or on our
[own PEAR channel](http://pear.nrk.io) for a more traditional installation using PEAR. Ultimately,
compressed archives of each release are [available on GitHub](https://github.com/nrk/predis/tags).
### Loading the library ###
@@ -52,7 +54,7 @@ Ultimately, archives of each release are [available on GitHub](https://github.co
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:
possible to leverage its own autoloader in projects or scripts lacking any autoload facility:
```php
// Prepend a base path if Predis is not available in your "include_path".
@@ -61,11 +63,9 @@ require 'Predis/Autoloader.php';
Predis\Autoloader::register();
```
It is possible to create a [phar](http://www.php.net/manual/en/intro.phar.php) archive directly from
the repository by launching `bin/create-phar`. The phar contains a stub defining its own autoloader
so you just need to `require()` it to start using the library. Ultimately it is possible to generate
a single big PHP file containing all the source code simply by launching `bin/create-single-file`,
but this practice __is not__ encouraged.
It is also possible to create a [phar](http://www.php.net/manual/en/intro.phar.php) archive directly
from the repository by launching the `bin/create-phar` script. The generated phar already contains a
stub defining its own autoloader, so you just need to `require()` it to start using the library.
### Connecting to Redis ###
@@ -95,8 +95,34 @@ $client = new Predis\Client([
$client = new Predis\Client('tcp://10.0.0.1:6379');
```
Starting with Predis v1.0.2 the client also understands the `redis` scheme in URI strings as defined
by the [provisional IANA registration](http://www.iana.org/assignments/uri-schemes/prov/redis).
It is also possible to connect to local instances of Redis using UNIX domain sockets, in this case
the parameters must use the `unix` scheme and specify a path for the socket file:
```php
$client = new Predis\Client(['scheme' => 'unix', 'path' => '/path/to/redis.sock']);
$client = new Predis\Client('unix:/path/to/redis.sock');
```
The client can leverage TLS/SSL encryption to connect to secured remote Redis instances without the
need to configure an SSL proxy like stunnel. This can be useful when connecting to nodes running on
various cloud hosting providers. Encryption can be enabled with using the `tls` scheme and an array
of suitable [options](http://php.net/manual/context.ssl.php) passed via the `ssl` parameter:
```php
// Named array of connection parameters:
$client = new Predis\Client([
'scheme' => 'tls',
'ssl' => ['cafile' => 'private.pem', 'verify_peer' => true],
]
// Same set of parameters, but using an URI string:
$client = new Predis\Client('tls://127.0.0.1?ssl[cafile]=private.pem&ssl[verify_peer]=1');
```
The connection schemes [`redis`](http://www.iana.org/assignments/uri-schemes/prov/redis) (alias of
`tcp`) and [`rediss`](http://www.iana.org/assignments/uri-schemes/prov/rediss) (alias of `tls`) are
also supported, with the difference that URI strings containing these schemes are parsed following
the rules described on their respective IANA provisional registration documents.
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.
@@ -114,54 +140,123 @@ $client = new Predis\Client([
See the [aggregate connections](#aggregate-connections) section of this document for more details.
Connections to Redis are lazy meaning that the client connects to a server only if and when needed.
While it is recommended to let the client do its own stuff under the hood, there may be times when
it is still desired to have control of when the connection is opened or closed: this can easily be
achieved by invoking `$client->connect()` and `$client->disconnect()`. Please note that the effect
of these methods on aggregate connections may differ depending on each specific implementation.
### Client configuration ###
Various aspects of the client can be configured simply by passing options to the second argument of
`Predis\Client::__construct()`:
Many aspects and behaviors of the client can be configured by passing specific client options to the
second argument of `Predis\Client::__construct()`:
```php
$client = new Predis\Client($parameters, ['profile' => '2.8', 'prefix' => 'sample:']);
$client = new Predis\Client($parameters, ['prefix' => 'sample:']);
```
Options are managed through a mini DI-alike container while their values can be lazily initialized
only when needed. This is a list of the options supported by default:
Options are managed using a mini DI-alike container and their values can be lazily initialized only
when needed. The client options supported by default in Predis are:
- `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.
- `prefix`: prefix string applied to every key 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`).
- `connections`: list of connection backends or a connection factory instance.
- `cluster`: specifies a cluster backend (`predis`, `redis` or callable).
- `replication`: specifies a replication backend (`predis`, `sentinel` or callable).
- `aggregate`: configures the client with a custom aggregate connection (callable).
- `parameters`: list of default connection parameters for aggregate connections.
- `commands`: specifies a command factory instance to use through the library.
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.
Users can also provide custom options with values or callable objects (for lazy initialization) 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 clustering 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 only for write operations. The replication behavior
is fully configurable.
Aggregate connections are the foundation upon which Predis implements clustering and replication and
they are used to group multiple connections to single Redis nodes and hide the specific logic needed
to handle them properly depending on the context. Aggregate connections usually require an array of
connection parameters when creating a new client instance.
#### Cluster ####
By default, when no specific client options are set and an array of connection parameters is passed
to the client's constructor, Predis configures itself to work in clustering mode using a traditional
client-side sharding approach to create a cluster of independent nodes and distribute the keyspace
among them. This approach needs some form of external health monitoring of nodes and requires manual
operations to rebalance the keyspace when changing its configuration by adding or removing nodes:
```php
$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$client = new Predis\Client($parameters);
```
Along with Redis 3.0, a new supervised and coordinated type of clustering was introduced in the form
of [redis-cluster](http://redis.io/topics/cluster-tutorial). This kind of approach uses a different
algorithm to distribute the keyspaces, with Redis nodes coordinating themselves by communicating via
a gossip protocol to handle health status, rebalancing, nodes discovery and request redirection. In
order to connect to a cluster managed by redis-cluster, the client requires a list of its nodes (not
necessarily complete since it will automatically discover new nodes if necessary) and the `cluster`
client options set to `redis`:
```php
$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$options = ['cluster' => 'redis'];
$client = new Predis\Client($parameters, $options);
```
#### Replication ####
The client can be configured to operate in a master-slave setup by executing read-only commands on
slave nodes and automatically switch to the master node as soon as it detects a command that will
perform a write operation. This is the basic configuration needed to work with replication:
The client can be configured to operate in a single master / multiple slaves setup to provide better
service availability. When using replication, Predis recognizes read-only commands and sends them to
a random slave in order to provide some sort of load-balancing and switches to the master as soon as
it detects a command that performs any kind of operation that would end up modifying the keyspace or
the value of a key. Instead of raising a connection error when a slave fails, the client attempts to
fall back to a different slave among the ones provided in the configuration.
The basic configuration needed to use the client in replication mode requires one Redis server to be
identified as the master (this can be done via connection parameters using the `alias` parameter set
to `master`) and one or more servers acting as slaves:
```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'];
$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$options = ['replication' => true];
$client = new Predis\Client($parameters, $options);
```
The above configuration has a static list of servers and relies entirely on the client's logic, but
it is possible to rely on [`redis-sentinel`](http://redis.io/topics/sentinel) for a more robust HA
environment with sentinel servers acting as a source of authority for clients for service discovery.
The minimum configuration required by the client to work with redis-sentinel is a list of connection
parameters pointing to a bunch of sentinel instances, the `replication` option set to `sentinel` and
the `service` option set to the name of the service:
```php
$sentinels = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$options = ['replication' => 'sentinel', 'service' => 'mymaster'];
$client = new Predis\Client($sentinels, $options);
```
If the master and slave nodes are configured to require an authentication from clients, a password
must be provided via the global `parameters` client option. This option can also be used to specify
a different database index. The client options array would then look like this:
```php
$options = [
'replication' => 'sentinel',
'service' => 'mymaster',
'parameters' => [
'password' => $secretpassword,
'database' => 10,
],
];
```
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 cannot
tell when a Lua script is safe to be executed on slaves. While this is indeed the default behavior,
@@ -169,13 +264,13 @@ when certain Lua scripts do not perform write operations it is possible to provi
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'];
$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$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);
return new Predis\Connection\Replication\MasterSlaveReplication($strategy);
}];
$client = new Predis\Client($parameters, $options);
@@ -183,29 +278,8 @@ $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
[basic](examples/replication_simple.php) and [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, you
can specify only a few nodes and the client 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 the library.
The [`examples`](examples/) directory contains a few scripts that demonstrate how the client can be
configured and used to leverage replication in both basic and complex scenarios.
### Command pipelines ###
@@ -255,7 +329,7 @@ of a transaction using CAS you can see [the following example](examples/transact
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 old 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 default server profiles used by the client:
command classes to define or override commands in the default command factory used by the client:
```php
// Define a new command by extending Predis\Command\Command:
@@ -267,9 +341,12 @@ class BrandNewRedisCommand extends Predis\Command\Command
}
}
// Inject your command in the current profile:
$client = new Predis\Client();
$client->getProfile()->defineCommand('newcmd', 'BrandNewRedisCommand');
// Inject your command in the current command factory:
$client = new Predis\Client($parameters, [
'commands' => [
'newcmd' => 'BrandNewRedisCommand',
],
]);
$response = $client->newcmd();
```
@@ -286,9 +363,9 @@ $response = $client->executeRaw(['SET', 'foo', 'bar']);
### Script commands ###
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) directly,
directly [`EVAL`](http://redis.io/commands/eval) and [`EVALSHA`](http://redis.io/commands/evalsha),
Predis offers script commands as an higher level abstraction built upon them to make things simple.
Script commands can be registered in the server profile used by the client and are accessible as if
Script commands can be registered in the command factory 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)
@@ -314,9 +391,12 @@ LUA;
}
}
// Inject the script command in the current profile:
$client = new Predis\Client();
$client->getProfile()->defineCommand('lpushrand', 'ListPushRandomValue');
// Inject the script command in the current command factory:
$client = new Predis\Client($parameters, [
'commands' => [
'lpushrand' => 'ListPushRandomValue',
],
]);
$response = $client->lpushrand('random_values', $seed = mt_rand());
```
@@ -374,15 +454,11 @@ stay consistent while working on the project.
__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 has a comprehensive test suite covering every aspect of the library and that can optionally
perform integration tests against a running instance of Redis (required >= 2.4.0 in order to verify
the correct behavior of the implementation of each command. Integration tests for unsupported Redis
commands are automatically skipped. If you do not have Redis up and running, integration tests can
be disabled. See [the tests README](tests/README.md) for more details about testing this library.
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).
@@ -407,3 +483,17 @@ found [on its project page](http://travis-ci.org/nrk/predis).
### License ###
The code for Predis is distributed under the terms of the MIT license (see [LICENSE](LICENSE)).
[ico-license]: https://img.shields.io/github/license/nrk/predis.svg?style=flat-square
[ico-version-stable]: https://img.shields.io/packagist/v/predis/predis.svg?style=flat-square
[ico-version-dev]: https://img.shields.io/packagist/vpre/predis/predis.svg?style=flat-square
[ico-downloads-monthly]: https://img.shields.io/packagist/dm/predis/predis.svg?style=flat-square
[ico-travis]: https://img.shields.io/travis/nrk/predis.svg?style=flat-square
[ico-hhvm]: https://img.shields.io/hhvm/predis/predis.svg?style=flat-square
[ico-gitter]: https://img.shields.io/gitter/room/nrk/predis.svg?style=flat-square
[link-packagist]: https://packagist.org/packages/predis/predis
[link-travis]: https://travis-ci.org/nrk/predis
[link-downloads]: https://packagist.org/packages/predis/predis/stats
[link-hhvm]: http://hhvm.h4cc.de/package/predis/predis
[link-gitter]: https://gitter.im/nrk/predis
+1 -1
View File
@@ -1 +1 @@
1.0.2
2.0.0-dev
+18 -15
View File
@@ -15,7 +15,7 @@
// of a test case to test a Redis command by specifying the name of the class
// 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\Command\StringSet class):
// Predis\Command\Redis\StringSet class):
//
// $ ./bin/generate-command-test --class=StringSet
//
@@ -45,6 +45,11 @@ class CommandTestCaseGenerator
if (!isset($options['class'])) {
throw new RuntimeException("Missing 'class' option.");
}
if (!isset($options['realm'])) {
throw new RuntimeException("Missing 'realm' option.");
}
$this->options = $options;
}
@@ -92,8 +97,12 @@ class CommandTestCaseGenerator
throw new RuntimeException("Missing 'class' option.");
}
$options['fqn'] = "Predis\\Command\\{$options['class']}";
$options['path'] = "Command/{$options['class']}.php";
if (!isset($options['realm'])) {
throw new RuntimeException("Missing 'realm' option.");
}
$options['fqn'] = "Predis\\Command\\Redis\\{$options['class']}";
$options['path'] = "Command/Redis/{$options['class']}.php";
$source = __DIR__.'/../src/'.$options['path'];
if (!file_exists($source)) {
@@ -101,7 +110,7 @@ class CommandTestCaseGenerator
}
if (!isset($options['output'])) {
$options['output'] = sprintf("%s/%s", $options['tests'], str_replace('.php', 'Test.php', $options['path']));
$options['output'] = sprintf("%s/%s", $options['tests'], str_replace('.php', '_Test.php', $options['path']));
}
return new self($options);
@@ -109,18 +118,11 @@ class CommandTestCaseGenerator
protected function getTestRealm()
{
if (isset($this->options['realm'])) {
if (!$this->options['realm']) {
throw new RuntimeException('Invalid value for realm has been sepcified (empty).');
}
return $this->options['realm'];
if (empty($this->options['realm'])) {
throw new RuntimeException('Invalid value for realm has been sepcified (empty).');
}
$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);
return $this->options['realm'];
}
public function generate()
@@ -130,6 +132,7 @@ class CommandTestCaseGenerator
if (!$reflection->isInstantiable()) {
throw new RuntimeException("Class $class must be instantiable, abstract classes or interfaces are not allowed.");
}
if (!$reflection->implementsInterface('Predis\Command\CommandInterface')) {
throw new RuntimeException("Class $class must implement Predis\Command\CommandInterface.");
}
@@ -173,7 +176,7 @@ class CommandTestCaseGenerator
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
/**
* @group commands
+8 -3
View File
@@ -1,7 +1,7 @@
{
"name": "predis/predis",
"type": "library",
"description": "Flexible and feature-complete PHP client library for Redis",
"description": "Flexible and feature-complete Redis client for PHP and HHVM",
"keywords": ["nosql", "redis", "predis"],
"homepage": "http://github.com/nrk/predis",
"license": "MIT",
@@ -16,10 +16,10 @@
}
],
"require": {
"php": ">=5.3.2"
"php": ">=5.3.9"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
"phpunit/phpunit": "~4.8"
},
"suggest": {
"ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol",
@@ -27,5 +27,10 @@
},
"autoload": {
"psr-4": {"Predis\\": "src/"}
},
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
}
}
+3 -3
View File
@@ -18,7 +18,7 @@ require __DIR__.'/shared.php';
use Predis\Cluster\Distributor\DistributorInterface;
use Predis\Cluster\Hash\HashGeneratorInterface;
use Predis\Cluster\PredisStrategy;
use Predis\Connection\Aggregate\PredisCluster;
use Predis\Connection\Cluster\PredisCluster;
class NaiveDistributor implements DistributorInterface, HashGeneratorInterface
{
@@ -104,8 +104,8 @@ for ($i = 0; $i < 100; ++$i) {
$client->get("key:$i");
}
$server1 = $client->getClientFor('first')->info();
$server2 = $client->getClientFor('second')->info();
$server1 = $client->on('first')->info();
$server2 = $client->on('second')->info();
if (isset($server1['Keyspace'], $server2['Keyspace'])) {
$server1 = $server1['Keyspace'];
+1 -1
View File
@@ -36,7 +36,7 @@ class SimpleDebuggableConnection extends StreamConnection
$firtsArg = $command->getArgument(0);
$timestamp = round(microtime(true) - $this->tstart, 4);
$debug = $command->getId();
$debug = $command->getId();
$debug .= isset($firtsArg) ? " $firtsArg " : ' ';
$debug .= "$direction $this";
$debug .= " [{$timestamp}s]";
+2 -2
View File
@@ -51,7 +51,7 @@ class EventsListener implements Countable
return $this->events;
}
public function __invoke($payload)
public function __invoke($payload, $dispatcher)
{
$this->events[] = $payload;
}
@@ -61,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, $dispatcher) {
if ($payload === 'terminate_dispatcher') {
$dispatcher->stop();
}
+7 -11
View File
@@ -13,11 +13,10 @@ 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.
// Additionally to the EVAL command, the Predis\Command\ScriptCommand class can
// be used to leverage an higher level abstraction for Lua scripting that makes
// scripts appear just like any other command on the client-side. This is basic
// example on how a script-based INCREX command can be defined:
use Predis\Command\ScriptCommand;
@@ -50,12 +49,9 @@ LUA;
}
$client = new Predis\Client($single_server, array(
'profile' => function ($options) {
$profile = $options->getDefault('profile');
$profile->defineCommand('increxby', 'IncrementExistingKeysBy');
return $profile;
},
'commands' => array(
'increxby' => 'IncrementExistingKeysBy',
),
));
$client->mset('foo', 10, 'foobar', 100);
+2 -3
View File
@@ -13,7 +13,7 @@ require __DIR__.'/shared.php';
use Predis\Collection\Iterator;
// Starting from Redis 2.8, clients can iterate incrementally over collections
// Starting with 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:
//
@@ -29,8 +29,7 @@ use Predis\Collection\Iterator;
// 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'));
$client = new Predis\Client($single_server);
// Prepare some keys for our example
$client->del('predis:set', 'predis:zset', 'predis:hash');
+7 -10
View File
@@ -14,12 +14,12 @@ 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.
// more complex configuration that injects a new script command in the command
// factory used by the client and marks it as a read-only operation so that it
// will be executed on slaves.
use Predis\Command\ScriptCommand;
use Predis\Connection\Aggregate\MasterSlaveReplication;
use Predis\Connection\Replication\MasterSlaveReplication;
use Predis\Replication\ReplicationStrategy;
// ------------------------------------------------------------------------- //
@@ -52,12 +52,9 @@ $parameters = array(
);
$options = array(
'profile' => function ($options, $option) {
$profile = $options->getDefault($option);
$profile->defineCommand('hmgetall', 'HashMultipleGetAll');
return $profile;
},
'commands' => array(
'hmgetall' => 'HashMultipleGetAll',
),
'replication' => function () {
$strategy = new ReplicationStrategy();
$strategy->setScriptReadOnly(HashMultipleGetAll::BODY);
+58
View File
@@ -0,0 +1,58 @@
<?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 redis-sentinel to provide high availability in master / slave
// scenarios. The only but relevant difference with a basic replication scenario
// is that sentinel servers can manage the master server and its slaves based on
// their state, which means that they are able to provide an authoritative and
// updated configuration to clients thus avoiding static configurations for the
// replication servers and their roles.
// Instead of connection parameters pointing to redis nodes, we provide a list
// of instances of redis-sentinel. Users should always provide a timeout value
// low enough to not hinder operations just in case a sentinel is unreachable
// but Predis uses a default value of 100 milliseconds for sentinel parameters
// without an explicit timeout value.
//
// NOTE: in real-world scenarios sentinels should be running on different hosts!
$sentinels = array(
'tcp://127.0.0.1:5380?timeout=0.100',
'tcp://127.0.0.1:5381?timeout=0.100',
'tcp://127.0.0.1:5382?timeout=0.100',
);
$client = new Predis\Client($sentinels, array(
'replication' => 'sentinel',
'service' => 'mymaster',
));
// 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-127.0.0.1:6381? yes.
Now 'foo' has been set to 'bar' on master!
We fetched 'foo' from master and its value is 'bar'.
*/
+1 -1
View File
@@ -26,7 +26,7 @@ $parameters = array(
'tcp://127.0.0.1:6380?database=15&alias=slave',
);
$options = array('replication' => true);
$options = array('replication' => 'predis');
$client = new Predis\Client($parameters, $options);
+8 -8
View File
@@ -23,22 +23,22 @@ function redis_version($info)
}
$single_server = array(
'host' => '127.0.0.1',
'port' => 6379,
'host' => '127.0.0.1',
'port' => 6379,
'database' => 15,
);
$multiple_servers = array(
array(
'host' => '127.0.0.1',
'port' => 6379,
'host' => '127.0.0.1',
'port' => 6379,
'database' => 15,
'alias' => 'first',
'alias' => 'first',
),
array(
'host' => '127.0.0.1',
'port' => 6380,
'host' => '127.0.0.1',
'port' => 6380,
'database' => 15,
'alias' => 'second',
'alias' => 'second',
),
);
+1 -1
View File
@@ -28,7 +28,7 @@ function zpop($client, $key)
{
$element = null;
$options = array(
'cas' => true, // Initialize with support for CAS operations
'cas' => true, // Initialize with support for CAS operations
'watch' => $key, // Key that needs to be WATCHed to detect changes
'retry' => 3, // Number of retries on aborted transactions, after
// which the client bails out with an exception.
+4 -4
View File
@@ -7,17 +7,17 @@
[package]
name = "Predis"
desc = "Flexible and feature-complete PHP client library for Redis"
desc = "Flexible and feature-complete Redis client for PHP and HHVM"
homepage = "http://github.com/nrk/predis"
license = "MIT"
version = "1.0.2"
stability = "stable"
version = "2.0.0"
stability = "devel"
channel = "pear.nrk.io"
author = "Daniele Alessandri \"nrk\" <suppakilla@gmail.com>"
[require]
php = ">= 5.3.2"
php = ">= 5.3.9"
pearinstaller = "1.4.1"
[roles]
+10 -5
View File
@@ -1,10 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php"
colors="true"
beStrictAboutTestSize="true"
checkForUnintentionallyCoveredCode="true"
beStrictAboutTestsThatDoNotTestAnything="true">
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
syntaxCheck="true"
beStrictAboutTestSize="true"
beStrictAboutTestsThatDoNotTestAnything="true">
<testsuites>
<testsuite name="Predis Test Suite">
@@ -32,7 +38,6 @@
<php>
<!-- Redis -->
<const name="REDIS_SERVER_VERSION" value="2.8" />
<const name="REDIS_SERVER_HOST" value="127.0.0.1" />
<const name="REDIS_SERVER_PORT" value="6379" />
<const name="REDIS_SERVER_DBNUM" value="15" />
+10 -5
View File
@@ -1,10 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php"
colors="true"
beStrictAboutTestSize="true"
checkForUnintentionallyCoveredCode="true"
beStrictAboutTestsThatDoNotTestAnything="true">
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
syntaxCheck="true"
beStrictAboutTestSize="true"
beStrictAboutTestsThatDoNotTestAnything="true">
<testsuites>
<testsuite name="Predis Test Suite">
@@ -36,7 +42,6 @@
<php>
<!-- Redis -->
<const name="REDIS_SERVER_VERSION" value="2.8" />
<const name="REDIS_SERVER_HOST" value="127.0.0.1" />
<const name="REDIS_SERVER_PORT" value="6379" />
<const name="REDIS_SERVER_DBNUM" value="15" />
+1
View File
@@ -16,6 +16,7 @@ namespace Predis;
*
* @author Eric Naeseth <eric@thumbtack.com>
* @author Daniele Alessandri <suppakilla@gmail.com>
* @codeCoverageIgnore
*/
class Autoloader
{
+100 -58
View File
@@ -19,6 +19,7 @@ use Predis\Configuration\OptionsInterface;
use Predis\Connection\AggregateConnectionInterface;
use Predis\Connection\ConnectionInterface;
use Predis\Connection\ParametersInterface;
use Predis\Connection\Replication\SentinelReplication;
use Predis\Monitor\Consumer as MonitorConsumer;
use Predis\Pipeline\Pipeline;
use Predis\PubSub\Consumer as PubSubConsumer;
@@ -38,13 +39,24 @@ use Predis\Transaction\MultiExec as MultiExecTransaction;
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class Client implements ClientInterface
class Client implements ClientInterface, \IteratorAggregate
{
const VERSION = '1.0.2';
const VERSION = '2.0.0-dev';
protected $connection;
protected $options;
private $profile;
/**
* @var Predis\Configuration\OptionsInterface
*/
private $options;
/**
* @var Predis\Connection\ConnectionInterface
*/
private $connection;
/**
* @var Predis\Command\FactoryInterface
*/
private $commands;
/**
* @param mixed $parameters Connection parameters for one or more servers.
@@ -54,7 +66,7 @@ class Client implements ClientInterface
{
$this->options = $this->createOptions($options ?: array());
$this->connection = $this->createConnection($parameters ?: array());
$this->profile = $this->options->profile;
$this->commands = $this->options->commands;
}
/**
@@ -78,7 +90,7 @@ class Client implements ClientInterface
return $options;
}
throw new \InvalidArgumentException('Invalid type for client options.');
throw new \InvalidArgumentException('Invalid type for client options');
}
/**
@@ -102,76 +114,76 @@ class Client implements ClientInterface
*/
protected function createConnection($parameters)
{
$options = $this->getOptions();
if ($parameters instanceof ConnectionInterface) {
return $parameters;
}
if ($parameters instanceof ParametersInterface || is_string($parameters)) {
return $this->options->connections->create($parameters);
return $options->connections->create($parameters);
}
if (is_array($parameters)) {
if (!isset($parameters[0])) {
return $this->options->connections->create($parameters);
return $options->connections->create($parameters);
}
$options = $this->options;
if ($options->defined('aggregate')) {
$initializer = $this->getConnectionInitializerWrapper($options->aggregate);
$connection = $initializer($parameters, $options);
if ($options->defined('cluster')) {
return $this->createAggregateConnection($parameters, 'cluster');
} elseif ($options->defined('replication')) {
return $this->createAggregateConnection($parameters, 'replication');
} elseif ($options->defined('aggregate')) {
return $this->createAggregateConnection($parameters, 'aggregate');
} else {
if ($options->defined('replication') && $replication = $options->replication) {
$connection = $replication;
} else {
$connection = $options->cluster;
}
$options->connections->aggregate($connection, $parameters);
throw new \InvalidArgumentException(
'Array of connection parameters requires `cluster`, `replication` or `aggregate` client option'
);
}
return $connection;
}
if (is_callable($parameters)) {
$initializer = $this->getConnectionInitializerWrapper($parameters);
$connection = $initializer($this->options);
$connection = call_user_func($parameters, $options);
if (!$connection instanceof ConnectionInterface) {
throw new \InvalidArgumentException('Callable parameters must return a valid connection');
}
return $connection;
}
throw new \InvalidArgumentException('Invalid type for connection parameters.');
throw new \InvalidArgumentException('Invalid type for connection parameters');
}
/**
* Wraps a callable to make sure that its returned value represents a valid
* connection type.
* Creates an aggregate connection.
*
* @param mixed $callable
* @param mixed $parameters Connection parameters.
* @param string $option Option for aggregate connections (`aggregate`, `cluster`, `replication`).
*
* @return \Closure
*/
protected function getConnectionInitializerWrapper($callable)
protected function createAggregateConnection($parameters, $option)
{
return function () use ($callable) {
$connection = call_user_func_array($callable, func_get_args());
$options = $this->getOptions();
if (!$connection instanceof ConnectionInterface) {
throw new \UnexpectedValueException(
'The callable connection initializer returned an invalid type.'
);
}
$initializer = $options->$option;
$connection = $initializer($parameters);
return $connection;
};
// TODO: this is dirty but we must skip the redis-sentinel backend for now.
if ($option !== 'aggregate' && !$connection instanceof SentinelReplication) {
$options->connections->aggregate($connection, $parameters);
}
return $connection;
}
/**
* {@inheritdoc}
*/
public function getProfile()
public function getCommandFactory()
{
return $this->profile;
return $this->commands;
}
/**
@@ -183,23 +195,34 @@ class Client implements ClientInterface
}
/**
* Creates a new client instance for the specified connection ID or alias,
* only when working with an aggregate connection (cluster, replication).
* The new client instances uses the same options of the original one.
* Creates a new client from the specified connection ID / alias.
*
* @param string $connectionID Identifier of a connection.
* The new client instances inherites the same options of the original one.
* When no callable object is supplied, this method returns the new client.
* When a callable object is supplied, the new client is passed as its sole
* argument and its return value is returned by this method to the caller.
*
* @throws \InvalidArgumentException
* NOTE: This method works only when the client is configured to work with
* aggregate connections (cluster, replication).
*
* @return Client
* @param string $connectionID Identifier of a connection.
* @param callable|null $callable Optional callable object.
*
* @return ClientInterface|mixed
*/
public function getClientFor($connectionID)
public function on($connectionID, $callable = null)
{
if (!$connection = $this->getConnectionById($connectionID)) {
throw new \InvalidArgumentException("Invalid connection ID: $connectionID.");
throw new \InvalidArgumentException("Invalid connection ID: `$connectionID`");
}
return new static($connection, $this->options);
$client = new static($connection, $this->getOptions());
if ($callable) {
return call_user_func($callable, $client);
} else {
return $client;
}
}
/**
@@ -273,7 +296,7 @@ class Client implements ClientInterface
* applying any prefix to keys or throwing exceptions on Redis errors even
* regardless of client options.
*
* It is possibile to indentify Redis error responses from normal responses
* It is possible to identify Redis error responses from normal responses
* using the second optional argument which is populated by reference.
*
* @param array $arguments Command arguments as defined by the command signature.
@@ -284,9 +307,10 @@ class Client implements ClientInterface
public function executeRaw(array $arguments, &$error = null)
{
$error = false;
$commandID = array_shift($arguments);
$response = $this->connection->executeCommand(
new RawCommand($arguments)
new RawCommand($commandID, $arguments)
);
if ($response instanceof ResponseInterface) {
@@ -315,7 +339,7 @@ class Client implements ClientInterface
*/
public function createCommand($commandID, $arguments = array())
{
return $this->profile->createCommand($commandID, $arguments);
return $this->commands->createCommand($commandID, $arguments);
}
/**
@@ -349,10 +373,7 @@ class Client implements ClientInterface
protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $response)
{
if ($command instanceof ScriptCommand && $response->getErrorType() === 'NOSCRIPT') {
$eval = $this->createCommand('EVAL');
$eval->setRawArguments($command->getEvalArguments());
$response = $this->executeCommand($eval);
$response = $this->executeCommand($command->getEvalCommand());
if (!$response instanceof ResponseInterface) {
$response = $command->parseResponse($response);
@@ -395,9 +416,11 @@ class Client implements ClientInterface
return $this->$initializer($arg0, $arg1);
// @codeCoverageIgnoreStart
default:
return $this->$initializer($this, $argv);
}
// @codeCoverageIgnoreEnd
}
/**
@@ -476,7 +499,7 @@ class Client implements ClientInterface
}
/**
* Creates a new publis/subscribe context and returns it, or starts its loop
* Creates a new publish/subscribe context and returns it, or starts its loop
* inside the optionally provided callable object.
*
* @param mixed ... Array of options, a callable for execution, or both.
@@ -520,4 +543,23 @@ class Client implements ClientInterface
{
return new MonitorConsumer($this);
}
/**
* {@inheritdoc}
*/
public function getIterator()
{
$clients = array();
$connection = $this->getConnection();
if (!$connection instanceof \Traversable) {
throw new ClientException('The underlying connection is not traversable');
}
foreach ($connection as $node) {
$clients[(string) $node] = new static($node, $this->getOptions());
}
return new \ArrayIterator($clients);
}
}
+10 -1
View File
@@ -38,6 +38,7 @@ use Predis\Command\CommandInterface;
* @method $this append($key, $value)
* @method $this bitcount($key, $start = null, $end = null)
* @method $this bitop($operation, $destkey, $key)
* @method $this bitfield($key, ...)
* @method $this decr($key)
* @method $this decrby($key, $decrement)
* @method $this get($key)
@@ -71,6 +72,7 @@ use Predis\Command\CommandInterface;
* @method $this hset($key, $field, $value)
* @method $this hsetnx($key, $field, $value)
* @method $this hvals($key)
* @method $this hstrlen($key, $field)
* @method $this blpop(array $keys, $timeout)
* @method $this brpop(array $keys, $timeout)
* @method $this brpoplpush($source, $destination, $timeout)
@@ -97,7 +99,7 @@ use Predis\Command\CommandInterface;
* @method $this sismember($key, $member)
* @method $this smembers($key)
* @method $this smove($source, $destination, $member)
* @method $this spop($key)
* @method $this spop($key, $count = null)
* @method $this srandmember($key, $count = null)
* @method $this srem($key, $member)
* @method $this sscan($key, $cursor, array $options = null)
@@ -121,6 +123,7 @@ use Predis\Command\CommandInterface;
* @method $this zscore($key, $member)
* @method $this zscan($key, $cursor, array $options = null)
* @method $this zrangebylex($key, $start, $stop, array $options = null)
* @method $this zrevrangebylex($key, $start, $stop, array $options = null)
* @method $this zremrangebylex($key, $min, $max)
* @method $this zlexcount($key, $min, $max)
* @method $this pfadd($key, array $elements)
@@ -154,6 +157,12 @@ use Predis\Command\CommandInterface;
* @method $this slowlog($subcommand, $argument = null)
* @method $this time()
* @method $this command()
* @method $this geoadd($key, $longitude, $latitude, $member)
* @method $this geohash($key, array $members)
* @method $this geopos($key, array $members)
* @method $this geodist($key, $member1, $member2, $unit = null)
* @method $this georadius($key, $longitude, $latitude, $radius, $unit, array $options = null)
* @method $this georadiusbymember($key, $member, $radius, $unit, array $options = null)
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
+15 -6
View File
@@ -12,9 +12,9 @@
namespace Predis;
use Predis\Command\CommandInterface;
use Predis\Command\FactoryInterface;
use Predis\Configuration\OptionsInterface;
use Predis\Connection\ConnectionInterface;
use Predis\Profile\ProfileInterface;
/**
* Interface defining a client able to execute commands against Redis.
@@ -46,6 +46,7 @@ use Predis\Profile\ProfileInterface;
* @method int append($key, $value)
* @method int bitcount($key, $start = null, $end = null)
* @method int bitop($operation, $destkey, $key)
* @method array bitfield($key, ...)
* @method int decr($key)
* @method int decrby($key, $decrement)
* @method string get($key)
@@ -79,6 +80,7 @@ use Predis\Profile\ProfileInterface;
* @method int hset($key, $field, $value)
* @method int hsetnx($key, $field, $value)
* @method array hvals($key)
* @method int hstrlen($key, $field)
* @method array blpop(array $keys, $timeout)
* @method array brpop(array $keys, $timeout)
* @method array brpoplpush($source, $destination, $timeout)
@@ -105,7 +107,7 @@ use Predis\Profile\ProfileInterface;
* @method int sismember($key, $member)
* @method array smembers($key)
* @method int smove($source, $destination, $member)
* @method string spop($key)
* @method string spop($key, $count = null)
* @method string srandmember($key, $count = null)
* @method int srem($key, $member)
* @method array sscan($key, $cursor, array $options = null)
@@ -123,12 +125,13 @@ use Predis\Profile\ProfileInterface;
* @method int zremrangebyrank($key, $start, $stop)
* @method int zremrangebyscore($key, $min, $max)
* @method array zrevrange($key, $start, $stop, array $options = null)
* @method array zrevrangebyscore($key, $min, $max, array $options = null)
* @method array zrevrangebyscore($key, $max, $min, array $options = null)
* @method int zrevrank($key, $member)
* @method int zunionstore($destination, array $keys, array $options = null)
* @method string zscore($key, $member)
* @method array zscan($key, $cursor, array $options = null)
* @method array zrangebylex($key, $start, $stop, array $options = null)
* @method array zrevrangebylex($key, $start, $stop, array $options = null)
* @method int zremrangebylex($key, $min, $max)
* @method int zlexcount($key, $min, $max)
* @method int pfadd($key, array $elements)
@@ -162,17 +165,23 @@ use Predis\Profile\ProfileInterface;
* @method mixed slowlog($subcommand, $argument = null)
* @method array time()
* @method array command()
* @method int geoadd($key, $longitude, $latitude, $member)
* @method array geohash($key, array $members)
* @method array geopos($key, array $members)
* @method string geodist($key, $member1, $member2, $unit = null)
* @method array georadius($key, $longitude, $latitude, $radius, $unit, array $options = null)
* @method array georadiusbymember($key, $member, $radius, $unit, array $options = null)
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface ClientInterface
{
/**
* Returns the server profile used by the client.
* Returns the command factory used by the client.
*
* @return ProfileInterface
* @return FactoryInterface
*/
public function getProfile();
public function getCommandFactory();
/**
* Returns the client options specified upon initialization.
+73 -2
View File
@@ -43,7 +43,7 @@ abstract class ClusterStrategy implements StrategyInterface
return array(
/* commands operating on the key space */
'EXISTS' => $getKeyFromFirstArgument,
'EXISTS' => $getKeyFromAllArguments,
'DEL' => $getKeyFromAllArguments,
'TYPE' => $getKeyFromFirstArgument,
'EXPIRE' => $getKeyFromFirstArgument,
@@ -53,7 +53,7 @@ abstract class ClusterStrategy implements StrategyInterface
'PEXPIREAT' => $getKeyFromFirstArgument,
'TTL' => $getKeyFromFirstArgument,
'PTTL' => $getKeyFromFirstArgument,
'SORT' => $getKeyFromFirstArgument, // TODO
'SORT' => array($this, 'getKeyFromSortCommand'),
'DUMP' => $getKeyFromFirstArgument,
'RESTORE' => $getKeyFromFirstArgument,
@@ -80,6 +80,7 @@ abstract class ClusterStrategy implements StrategyInterface
'SUBSTR' => $getKeyFromFirstArgument,
'BITOP' => array($this, 'getKeyFromBitOp'),
'BITCOUNT' => $getKeyFromFirstArgument,
'BITFIELD' => $getKeyFromFirstArgument,
/* commands operating on lists */
'LINSERT' => $getKeyFromFirstArgument,
@@ -164,6 +165,14 @@ abstract class ClusterStrategy implements StrategyInterface
/* scripting */
'EVAL' => array($this, 'getKeyFromScriptingCommands'),
'EVALSHA' => array($this, 'getKeyFromScriptingCommands'),
/* commands performing geospatial operations */
'GEOADD' => $getKeyFromFirstArgument,
'GEOHASH' => $getKeyFromFirstArgument,
'GEOPOS' => $getKeyFromFirstArgument,
'GEODIST' => $getKeyFromFirstArgument,
'GEORADIUS' => array($this, 'getKeyFromGeoradiusCommands'),
'GEORADIUSBYMEMBER' => array($this, 'getKeyFromGeoradiusCommands'),
);
}
@@ -261,6 +270,35 @@ abstract class ClusterStrategy implements StrategyInterface
}
}
/**
* Extracts the key from SORT command.
*
* @param CommandInterface $command Command instance.
*
* @return string|null
*/
protected function getKeyFromSortCommand(CommandInterface $command)
{
$arguments = $command->getArguments();
$firstKey = $arguments[0];
if (1 === $argc = count($arguments)) {
return $firstKey;
}
$keys = array($firstKey);
for ($i = 1; $i < $argc; ++$i) {
if (strtoupper($arguments[$i]) === 'STORE') {
$keys[] = $arguments[++$i];
}
}
if ($this->checkSameSlotForKeys($keys)) {
return $firstKey;
}
}
/**
* Extracts the key from BLPOP and BRPOP commands.
*
@@ -293,6 +331,39 @@ abstract class ClusterStrategy implements StrategyInterface
}
}
/**
* Extracts the key from GEORADIUS and GEORADIUSBYMEMBER commands.
*
* @param CommandInterface $command Command instance.
*
* @return string|null
*/
protected function getKeyFromGeoradiusCommands(CommandInterface $command)
{
$arguments = $command->getArguments();
$argc = count($arguments);
$startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4;
if ($argc > $startIndex) {
$keys = array($arguments[0]);
for ($i = $startIndex; $i < $argc; ++$i) {
$argument = strtoupper($arguments[$i]);
if ($argument === 'STORE' || $argument === 'STOREDIST') {
$keys[] = $arguments[++$i];
}
}
if ($this->checkSameSlotForKeys($keys)) {
return $arguments[0];
} else {
return;
}
}
return $arguments[0];
}
/**
* Extracts the key from ZINTERSTORE and ZUNIONSTORE commands.
*
@@ -65,8 +65,8 @@ abstract class CursorBasedIterator implements \Iterator
*/
protected function requiredCommand(ClientInterface $client, $commandID)
{
if (!$client->getProfile()->supportsCommand($commandID)) {
throw new NotSupportedException("The current profile does not support '$commandID'.");
if (!$client->getCommandFactory()->supportsCommand($commandID)) {
throw new NotSupportedException("'$commandID' is not supported by the current command factory.");
}
}
+6 -2
View File
@@ -50,7 +50,11 @@ class HashKey extends CursorBasedIterator
*/
protected function extractNext()
{
$this->position = key($this->elements);
$this->current = array_shift($this->elements);
if ($kv = each($this->elements)) {
$this->position = $kv[0];
$this->current = $kv[1];
unset($this->elements[$this->position]);
}
}
}
+2 -2
View File
@@ -73,8 +73,8 @@ class ListKey implements \Iterator
*/
protected function requiredCommand(ClientInterface $client, $commandID)
{
if (!$client->getProfile()->supportsCommand($commandID)) {
throw new NotSupportedException("The current profile does not support '$commandID'.");
if (!$client->getCommandFactory()->supportsCommand($commandID)) {
throw new NotSupportedException("'$commandID' is not supported by the current command factory.");
}
}
+1 -13
View File
@@ -21,24 +21,12 @@ abstract class Command implements CommandInterface
private $slot;
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);
$this->arguments = $arguments;
unset($this->slot);
}
@@ -9,43 +9,30 @@
* file that was distributed with this source code.
*/
namespace Predis\Profile;
namespace Predis\Command;
use Predis\ClientException;
use Predis\Command\Processor\ProcessorInterface;
/**
* Base class implementing common functionalities for Redis server profiles.
* Base command factory.
*
* This class provides all of the common functionalities needed for the creation
* of new instances of Redis commands.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
abstract class RedisProfile implements ProfileInterface
abstract class Factory implements FactoryInterface
{
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
*/
abstract protected function getSupportedCommands();
protected $commands = array();
protected $processor;
/**
* {@inheritdoc}
*/
public function supportsCommand($commandID)
{
return isset($this->commands[strtoupper($commandID)]);
return $this->getCommandClass($commandID) !== null;
}
/**
@@ -63,8 +50,9 @@ abstract class RedisProfile implements ProfileInterface
}
/**
* Returns the fully-qualified name of a class representing the specified
* command ID registered in the current server profile.
* Returns the FQN of a class that represents the specified command ID.
*
* @codeCoverageIgnore
*
* @param string $commandID Command ID.
*
@@ -82,13 +70,12 @@ abstract class RedisProfile implements ProfileInterface
*/
public function createCommand($commandID, array $arguments = array())
{
$commandID = strtoupper($commandID);
if (!$commandClass = $this->getCommandClass($commandID)) {
$commandID = strtoupper($commandID);
if (!isset($this->commands[$commandID])) {
throw new ClientException("Command '$commandID' is not a registered Redis command.");
}
$commandClass = $this->commands[$commandID];
$command = new $commandClass();
$command->setArguments($arguments);
@@ -100,7 +87,7 @@ abstract class RedisProfile implements ProfileInterface
}
/**
* Defines a new command in the server profile.
* Defines a new command in the factory.
*
* @param string $commandID Command ID.
* @param string $class Fully-qualified name of a Predis\Command\CommandInterface.
@@ -109,10 +96,12 @@ abstract class RedisProfile implements ProfileInterface
*/
public function defineCommand($commandID, $class)
{
$reflection = new \ReflectionClass($class);
if ($class !== null) {
$reflection = new \ReflectionClass($class);
if (!$reflection->isSubclassOf('Predis\Command\CommandInterface')) {
throw new \InvalidArgumentException("The class '$class' is not a valid command class.");
if (!$reflection->isSubclassOf('Predis\Command\CommandInterface')) {
throw new \InvalidArgumentException("The class '$class' is not a valid command class.");
}
}
$this->commands[strtoupper($commandID)] = $class;
@@ -133,14 +122,4 @@ abstract class RedisProfile implements ProfileInterface
{
return $this->processor;
}
/**
* Returns the version of server profile as its string representation.
*
* @return string
*/
public function __toString()
{
return $this->getVersion();
}
}
@@ -9,28 +9,20 @@
* file that was distributed with this source code.
*/
namespace Predis\Profile;
use Predis\Command\CommandInterface;
namespace Predis\Command;
/**
* A profile defines all the features and commands supported by certain versions
* of Redis. Instances of Predis\Client should use a server profile matching the
* version of Redis being used.
* Command factory interface.
*
* Each Redis command should have a class counterpart and commands factories are
* used to create new instances of these classes through the library.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface ProfileInterface
interface FactoryInterface
{
/**
* Returns the profile version corresponding to the Redis version.
*
* @return string
*/
public function getVersion();
/**
* Checks if the profile supports the specified command.
* Checks if the command factory supports the specified command.
*
* @param string $commandID Command ID.
*
@@ -39,7 +31,7 @@ interface ProfileInterface
public function supportsCommand($commandID);
/**
* Checks if the profile supports the specified list of commands.
* Checks if the command factory supports the specified list of commands.
*
* @param array $commandIDs List of command IDs.
*
+37 -2
View File
@@ -33,7 +33,7 @@ class KeyPrefixProcessor implements ProcessorInterface
$this->prefix = $prefix;
$this->commands = array(
/* ---------------- Redis 1.2 ---------------- */
'EXISTS' => 'static::first',
'EXISTS' => 'static::all',
'DEL' => 'static::all',
'TYPE' => 'static::first',
'KEYS' => 'static::first',
@@ -159,6 +159,13 @@ class KeyPrefixProcessor implements ProcessorInterface
'BITPOS' => 'static::first',
/* ---------------- Redis 3.2 ---------------- */
'HSTRLEN' => 'static::first',
'BITFIELD' => 'static::first',
'GEOADD' => 'static::first',
'GEOHASH' => 'static::first',
'GEOPOS' => 'static::first',
'GEODIST' => 'static::first',
'GEORADIUS' => 'static::georadius',
'GEORADIUSBYMEMBER' => 'static::georadius',
);
}
@@ -338,7 +345,7 @@ class KeyPrefixProcessor implements ProcessorInterface
if (($count = count($arguments)) > 1) {
for ($i = 1; $i < $count; ++$i) {
switch ($arguments[$i]) {
switch (strtoupper($arguments[$i])) {
case 'BY':
case 'STORE':
$arguments[$i] = "$prefix{$arguments[++$i]}";
@@ -412,4 +419,32 @@ class KeyPrefixProcessor implements ProcessorInterface
$command->setRawArguments($arguments);
}
}
/**
* Applies the specified prefix to the key of a GEORADIUS command.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
*/
public static function georadius(CommandInterface $command, $prefix)
{
if ($arguments = $command->getArguments()) {
$arguments[0] = "$prefix{$arguments[0]}";
$startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4;
if (($count = count($arguments)) > $startIndex) {
for ($i = $startIndex; $i < $count; ++$i) {
switch (strtoupper($arguments[$i])) {
case 'STORE':
case 'STOREDIST':
$arguments[$i] = "$prefix{$arguments[++$i]}";
break;
}
}
}
$command->setRawArguments($arguments);
}
}
}
+1 -2
View File
@@ -111,8 +111,7 @@ class ProcessorChain implements \ArrayAccess, ProcessorInterface
{
if (!$processor instanceof ProcessorInterface) {
throw new \InvalidArgumentException(
'A processor chain accepts only instances of '.
"'Predis\Command\Processor\ProcessorInterface'."
'Processor chain accepts only instances of `Predis\Command\Processor\ProcessorInterface`'
);
}
+6 -13
View File
@@ -28,20 +28,13 @@ class RawCommand implements CommandInterface
private $arguments;
/**
* @param array $arguments Command ID and its arguments.
*
* @throws \InvalidArgumentException
* @param string $commandID Command ID.
* @param array $arguments Command arguments.
*/
public function __construct(array $arguments)
public function __construct($commandID, array $arguments = array())
{
if (!$arguments) {
throw new \InvalidArgumentException(
'The arguments array must contain at least the command ID.'
);
}
$this->commandID = strtoupper(array_shift($arguments));
$this->arguments = $arguments;
$this->commandID = strtoupper($commandID);
$this->setArguments($arguments);
}
/**
@@ -55,7 +48,7 @@ class RawCommand implements CommandInterface
public static function create($commandID /* [ $arg, ... */)
{
$arguments = func_get_args();
$command = new self($arguments);
$command = new static(array_shift($arguments), $arguments);
return $command;
}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/append
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class StringAppend extends Command
class APPEND extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/auth
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionAuth extends Command
class AUTH extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/bgrewriteaof
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerBackgroundRewriteAOF extends Command
class BGREWRITEAOF extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/bgsave
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerBackgroundSave extends Command
class BGSAVE extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/bitcount
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class StringBitCount extends Command
class BITCOUNT extends RedisCommand
{
/**
* {@inheritdoc}
+30
View File
@@ -0,0 +1,30 @@
<?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\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/bitfield
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class BITFIELD extends RedisCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'BITFIELD';
}
}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/bitop
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class StringBitOp extends Command
class BITOP extends RedisCommand
{
/**
* {@inheritdoc}
@@ -29,7 +31,7 @@ class StringBitOp extends Command
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
public function setArguments(array $arguments)
{
if (count($arguments) === 3 && is_array($arguments[2])) {
list($operation, $destination) = $arguments;
@@ -37,6 +39,6 @@ class StringBitOp extends Command
array_unshift($arguments, $operation, $destination);
}
return $arguments;
parent::setArguments($arguments);
}
}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/bitpos
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class StringBitPos extends Command
class BITPOS extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/blpop
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ListPopFirstBlocking extends Command
class BLPOP extends RedisCommand
{
/**
* {@inheritdoc}
@@ -29,13 +31,13 @@ class ListPopFirstBlocking extends Command
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
public function setArguments(array $arguments)
{
if (count($arguments) === 2 && is_array($arguments[0])) {
list($arguments, $timeout) = $arguments;
array_push($arguments, $timeout);
}
return $arguments;
parent::setArguments($arguments);
}
}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/brpop
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ListPopLastBlocking extends ListPopFirstBlocking
class BRPOP extends RedisCommand
{
/**
* {@inheritdoc}
@@ -25,4 +27,17 @@ class ListPopLastBlocking extends ListPopFirstBlocking
{
return 'BRPOP';
}
/**
* {@inheritdoc}
*/
public function setArguments(array $arguments)
{
if (count($arguments) === 2 && is_array($arguments[0])) {
list($arguments, $timeout) = $arguments;
array_push($arguments, $timeout);
}
parent::setArguments($arguments);
}
}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/brpoplpush
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ListPopLastPushHeadBlocking extends Command
class BRPOPLPUSH extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,7 +9,9 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/client-list
@@ -19,7 +21,7 @@ namespace Predis\Command;
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerClient extends Command
class CLIENT extends RedisCommand
{
/**
* {@inheritdoc}
@@ -44,7 +46,7 @@ class ServerClient extends Command
case 'SETNAME':
default:
return $data;
}
} // @codeCoverageIgnore
}
/**
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as BaseCommand;
/**
* @link http://redis.io/commands/command
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerCommand extends Command
class COMMAND extends BaseCommand
{
/**
* {@inheritdoc}
@@ -9,7 +9,9 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/config-set
@@ -19,7 +21,7 @@ namespace Predis\Command;
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerConfig extends Command
class CONFIG extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/dbsize
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerDatabaseSize extends Command
class DBSIZE extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/decr
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class StringDecrement extends Command
class DECR extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/decrby
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class StringDecrementBy extends Command
class DECRBY extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/del
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyDelete extends Command
class DEL extends RedisCommand
{
/**
* {@inheritdoc}
@@ -29,8 +31,10 @@ class KeyDelete extends Command
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
public function setArguments(array $arguments)
{
return self::normalizeArguments($arguments);
$arguments = self::normalizeArguments($arguments);
parent::setArguments($arguments);
}
}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/discard
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class TransactionDiscard extends Command
class DISCARD extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/dump
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyDump extends Command
class DUMP extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/echo
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionEcho extends Command
class ECHO_ extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,14 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
/**
* @link http://redis.io/commands/evalsha
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerEvalSHA extends ServerEval
class EVALSHA extends EVAL_
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/eval
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerEval extends Command
class EVAL_ extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/exec
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class TransactionExec extends Command
class EXEC extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/exists
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyExists extends Command
class EXISTS extends RedisCommand
{
/**
* {@inheritdoc}
@@ -25,12 +27,4 @@ class KeyExists extends Command
{
return 'EXISTS';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/expire
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyExpire extends Command
class EXPIRE extends RedisCommand
{
/**
* {@inheritdoc}
@@ -25,12 +27,4 @@ class KeyExpire extends Command
{
return 'EXPIRE';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/expireat
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyExpireAt extends Command
class EXPIREAT extends RedisCommand
{
/**
* {@inheritdoc}
@@ -25,12 +27,4 @@ class KeyExpireAt extends Command
{
return 'EXPIREAT';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/flushall
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerFlushAll extends Command
class FLUSHALL extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/flushdb
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerFlushDatabase extends Command
class FLUSHDB extends RedisCommand
{
/**
* {@inheritdoc}
+44
View File
@@ -0,0 +1,44 @@
<?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\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/geoadd
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class GEOADD extends RedisCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'GEOADD';
}
/**
* {@inheritdoc}
*/
public function setArguments(array $arguments)
{
if (count($arguments) === 2 && is_array($arguments[1])) {
foreach (array_pop($arguments) as $item) {
$arguments = array_merge($arguments, $item);
}
}
parent::setArguments($arguments);
}
}
+30
View File
@@ -0,0 +1,30 @@
<?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\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/geodist
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class GEODIST extends RedisCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'GEODIST';
}
}
+43
View File
@@ -0,0 +1,43 @@
<?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\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/geohash
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class GEOHASH extends RedisCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'GEOHASH';
}
/**
* {@inheritdoc}
*/
public function setArguments(array $arguments)
{
if (count($arguments) === 2 && is_array($arguments[1])) {
$members = array_pop($arguments);
$arguments = array_merge($arguments, $members);
}
parent::setArguments($arguments);
}
}
+43
View File
@@ -0,0 +1,43 @@
<?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\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/geopos
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class GEOPOS extends RedisCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'GEOPOS';
}
/**
* {@inheritdoc}
*/
public function setArguments(array $arguments)
{
if (count($arguments) === 2 && is_array($arguments[1])) {
$members = array_pop($arguments);
$arguments = array_merge($arguments, $members);
}
parent::setArguments($arguments);
}
}
+73
View File
@@ -0,0 +1,73 @@
<?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\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/georadius
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class GEORADIUS extends RedisCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'GEORADIUS';
}
/**
* {@inheritdoc}
*/
public function setArguments(array $arguments)
{
if ($arguments && is_array(end($arguments))) {
$options = array_change_key_case(array_pop($arguments), CASE_UPPER);
if (isset($options['WITHCOORD']) && $options['WITHCOORD'] == true) {
$arguments[] = 'WITHCOORD';
}
if (isset($options['WITHDIST']) && $options['WITHDIST'] == true) {
$arguments[] = 'WITHDIST';
}
if (isset($options['WITHHASH']) && $options['WITHHASH'] == true) {
$arguments[] = 'WITHHASH';
}
if (isset($options['COUNT'])) {
$arguments[] = 'COUNT';
$arguments[] = $options['COUNT'];
}
if (isset($options['SORT'])) {
$arguments[] = strtoupper($options['SORT']);
}
if (isset($options['STORE'])) {
$arguments[] = 'STORE';
$arguments[] = $options['STORE'];
}
if (isset($options['STOREDIST'])) {
$arguments[] = 'STOREDIST';
$arguments[] = $options['STOREDIST'];
}
}
parent::setArguments($arguments);
}
}
+28
View File
@@ -0,0 +1,28 @@
<?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\Command\Redis;
/**
* @link http://redis.io/commands/georadiusbymember
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class GEORADIUSBYMEMBER extends GEORADIUS
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'GEORADIUSBYMEMBER';
}
}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/get
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class StringGet extends Command
class GET extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/getbit
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class StringGetBit extends Command
class GETBIT extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/getrange
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class StringGetRange extends Command
class GETRANGE extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/getset
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class StringGetSet extends Command
class GETSET extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/hdel
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashDelete extends Command
class HDEL extends RedisCommand
{
/**
* {@inheritdoc}
@@ -29,8 +31,10 @@ class HashDelete extends Command
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
public function setArguments(array $arguments)
{
return self::normalizeVariadic($arguments);
$arguments = self::normalizeVariadic($arguments);
parent::setArguments($arguments);
}
}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/hexists
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashExists extends Command
class HEXISTS extends RedisCommand
{
/**
* {@inheritdoc}
@@ -25,12 +27,4 @@ class HashExists extends Command
{
return 'HEXISTS';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/hget
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashGet extends Command
class HGET extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/hgetall
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashGetAll extends Command
class HGETALL extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/hincrby
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashIncrementBy extends Command
class HINCRBY extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/hincrbyfloat
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashIncrementByFloat extends Command
class HINCRBYFLOAT extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/hkeys
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashKeys extends Command
class HKEYS extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/hlen
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashLength extends Command
class HLEN extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/hmget
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashGetMultiple extends Command
class HMGET extends RedisCommand
{
/**
* {@inheritdoc}
@@ -29,8 +31,10 @@ class HashGetMultiple extends Command
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
public function setArguments(array $arguments)
{
return self::normalizeVariadic($arguments);
$arguments = self::normalizeVariadic($arguments);
parent::setArguments($arguments);
}
}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/hmset
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashSetMultiple extends Command
class HMSET extends RedisCommand
{
/**
* {@inheritdoc}
@@ -29,7 +31,7 @@ class HashSetMultiple extends Command
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
public function setArguments(array $arguments)
{
if (count($arguments) === 2 && is_array($arguments[1])) {
$flattenedKVs = array($arguments[0]);
@@ -40,9 +42,9 @@ class HashSetMultiple extends Command
$flattenedKVs[] = $v;
}
return $flattenedKVs;
$arguments = $flattenedKVs;
}
return $arguments;
parent::setArguments($arguments);
}
}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/hscan
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashScan extends Command
class HSCAN extends RedisCommand
{
/**
* {@inheritdoc}
@@ -29,14 +31,14 @@ class HashScan extends Command
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
public function setArguments(array $arguments)
{
if (count($arguments) === 3 && is_array($arguments[2])) {
$options = $this->prepareOptions(array_pop($arguments));
$arguments = array_merge($arguments, $options);
}
return $arguments;
parent::setArguments($arguments);
}
/**
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/hset
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashSet extends Command
class HSET extends RedisCommand
{
/**
* {@inheritdoc}
@@ -25,12 +27,4 @@ class HashSet extends Command
{
return 'HSET';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/hsetnx
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashSetPreserve extends Command
class HSETNX extends RedisCommand
{
/**
* {@inheritdoc}
@@ -25,12 +27,4 @@ class HashSetPreserve extends Command
{
return 'HSETNX';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/hstrlen
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashStringLength extends Command
class HSTRLEN extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/hvals
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashValues extends Command
class HVALS extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/incr
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class StringIncrement extends Command
class INCR extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/incrby
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class StringIncrementBy extends Command
class INCRBY extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/incrbyfloat
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class StringIncrementByFloat extends Command
class INCRBYFLOAT extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/info
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerInfo extends Command
class INFO extends RedisCommand
{
/**
* {@inheritdoc}
@@ -31,10 +33,51 @@ class ServerInfo extends Command
*/
public function parseResponse($data)
{
$info = array();
$infoLines = preg_split('/\r?\n/', $data);
if (empty($data) || !$lines = preg_split('/\r?\n/', $data)) {
return array();
}
foreach ($infoLines as $row) {
if (strpos($lines[0], '#') === 0) {
return $this->parseNewResponseFormat($lines);
} else {
return $this->parseOldResponseFormat($lines);
}
}
/**
* {@inheritdoc}
*/
public function parseNewResponseFormat($lines)
{
$info = array();
$current = null;
foreach ($lines as $row) {
if ($row === '') {
continue;
}
if (preg_match('/^# (\w+)$/', $row, $matches)) {
$info[$matches[1]] = array();
$current = &$info[$matches[1]];
continue;
}
list($k, $v) = $this->parseRow($row);
$current[$k] = $v;
}
return $info;
}
/**
* {@inheritdoc}
*/
public function parseOldResponseFormat($lines)
{
$info = array();
foreach ($lines as $row) {
if (strpos($row, ':') === false) {
continue;
}
@@ -82,30 +125,4 @@ class ServerInfo extends Command
return $db;
}
/**
* Parses the response and extracts the allocation statistics.
*
* @param string $str Response buffer.
*
* @return array
*/
protected function parseAllocationStats($str)
{
$stats = array();
foreach (explode(',', $str) as $kv) {
@list($size, $objects, $extra) = explode('=', $kv);
// hack to prevent incorrect values when parsing the >=256 key
if (isset($extra)) {
$size = ">=$objects";
$objects = $extra;
}
$stats[$size] = $objects;
}
return $stats;
}
}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/keys
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyKeys extends Command
class KEYS extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/lastsave
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerLastSave extends Command
class LASTSAVE extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/lindex
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ListIndex extends Command
class LINDEX extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/linsert
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ListInsert extends Command
class LINSERT extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/llen
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ListLength extends Command
class LLEN extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/lpop
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ListPopFirst extends Command
class LPOP extends RedisCommand
{
/**
* {@inheritdoc}
@@ -9,14 +9,16 @@
* file that was distributed with this source code.
*/
namespace Predis\Command;
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/lpush
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ListPushHead extends ListPushTail
class LPUSH extends RedisCommand
{
/**
* {@inheritdoc}
@@ -25,4 +27,14 @@ class ListPushHead extends ListPushTail
{
return 'LPUSH';
}
/**
* {@inheritdoc}
*/
public function setArguments(array $arguments)
{
$arguments = self::normalizeVariadic($arguments);
parent::setArguments($arguments);
}
}

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