初始化代码

This commit is contained in:
2025-12-22 14:34:25 +08:00
parent c2c5ae2fdd
commit a77dbc743f
1510 changed files with 213008 additions and 0 deletions

985
vendor/predis/predis/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,985 @@
v1.1.1 (2016-06-16)
================================================================================
- __FIX__: `password` and `database` from the global `parameters` client option
were still being applied to sentinels connections making them fail (sentinels
do not understand the `AUTH` and `SELECT` commands) (PR #346).
- __FIX__: when a sentinel instance reports no sentinel for a service, invoking
`connect()` on the redis-sentinel connection backend should fall back to the
master connection instead of failing (ISSUE #342).
- __FIX__: the two connection backends based on ext-phpiredis has some kind of
issues with the GC and the internal use of closures as reader callbacks that
prevented connections going out of scope from being properly collected and the
underlying stream or socket resources from being closed and freed. This should
not have had any actual effect in real-world scenarios due to the lifecycle of
PHP scripts, but we fixed it anyway (ISSUE #345).
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)
================================================================================
- IPv6 is now fully supported.
- Added `redis` as an accepted scheme for connection parameters. When using this
scheme, the rules used to parse URI strings match the provisional registration
[published by IANA](http://www.iana.org/assignments/uri-schemes/prov/redis).
- Added new or missing commands: `HSTRLEN` (>= 3.2), `ZREVRANGEBYLEX` (>= 2.8)
and `MIGRATE` (>= 2.6).
- Implemented support for the `ZADD` modifiers `NX|XX`, `CH`, `INCR` (Redis >=
3.0.2) using the simplified signature where scores and members are passed as
a named array.
- __FIX__: `Predis\Configuration\Options` must not trigger the autoloader when
option values are strings (ISSUE #257).
- __FIX__: `BITPOS` was not defined in the key-prefix processor (ISSUE #265) and
in the replication strategy.
v1.0.1 (2015-01-02)
================================================================================
- Added `BITPOS` to the server profile for Redis 2.8.
- Connection timeout for read/write operations can now be set for UNIX sockets
where the underlying connection uses PHP's stream.
- __FIX__: broken values returned by `Predis\Collection\Iterator\SortedSetKey`
when iterating sorted set containing integer members (ISSUE #216).
- __FIX__: applied a minor workaround for a bug in old versions of PHP < 5.3.9
affecting inheritance.
- __FIX__: prevent E_NOTICE warnings when using INFO [section] returns an empty
response due to an unsupported specific set of information requested to Redis.
v1.0.0 (2014-08-01)
================================================================================
- Switched to PSR-4 for autoloading.
- The default server profile for Redis is `3.0`.
- Removed server profile for Redis 1.2.
- Added `SENTINEL` to the profile for Redis 2.6 and `PUBSUB` to the profile for
Redis 2.8.
- `Predis\Client` can now send raw commands using `Predis\Client::executeRaw()`.
- Status responses are returned as instances of `Predis\Response\Status`, for
example +OK is not returned as boolean TRUE anymore which is a breaking change
for those using strict comparisons. Status responses can be casted to string
values carrying the original payload, so one can do `$response == 'OK'` which
is also more akin to how Redis replies to clients.
- Commands `ZRANGE`, `ZRANGEBYSCORE`, `ZREVRANGE` and `ZREVRANGEBYSCORE` using
`WITHSCORE` return a named array of member => score instead of using an array
of [member, score] elements. Insertion order is preserved anyway due to how
PHP works internally.
- The command `ZSCAN` returns a named array of member => score instead of using
an array of [member, score] elements. Insertion order is preserved anyway due
to how PHP works internally.
- The rules for redis-cluster are now leveraged for empty key tags when using
client-side sharding, which means that when one or the first occurrence of {}
is found in a key it will most likely produce a different hash than previous
versions of Predis thus leading to a different partitioning in these cases.
- Invoking `Predis\Client::connect()` when the underlying connection has been
already established does not throw any exception anymore, now the connection
simply does not attempt to perform any operation.
- Added the `aggregate` client option, useful to fully customize how the client
should aggregate multiple connections when an array of connection parameters
is passed to `Predis\Client::__construct()`.
- Dropped support for streamable multibulk responses. Actually we still ship the
iterator response classes just in case anyone would want to build custom stuff
at a level lower than the client abstraction (our standard and composable text
protocol processors still handle them and can be used as an example).
- Simplified the implementation of connection parameters by removing method used
to cast to int / bool / float certain parameters supplied by users. Casting
values, if deemed necessary, should be done by the consumer or you can just
subclass `Predis\Connection\Parameters` and override the `filter()` method.
- Changed a couple of options for our transaction abstraction:
- `exceptions`: overrides the value of the client option with the same name.
Please note that it does not affect all the transaction control commands
such as `MULTI`, `EXEC`, `DISCARD`, `WATCH` and `UNWATCH`.
- `on_retry`: this option has been removed.
- Removed pipeline executors, now command pipelines can be easily customized by
extending the standard `Predis\Pipeline\Pipeline` class. Accepted options when
creating a pipeline using `Predis\Client::pipeline()` are:
- `atomic`: returns a pipeline wrapped in a MULTI / EXEC transaction
(class: `Predis\Pipeline\Atomic`).
- `fire-and-forget`: returns a pipeline that does not read back responses
(class: `Predis\Pipeline\FireAndForget`).
- Renamed the two base abstract command classes:
- `Predis\Command\AbstractCommand` is now `Predis\Command\Command`
- `Predis\Command\ScriptedCommand` is now `Predis\Command\ScriptCommand`
- Dropped `Predis\Command\Command::__toString()` (see issue #151).
- The key prefixing logic has been moved from command classes to the key prefix
processor. Developers can define or override handlers used to prefix keys, but
they can also define the needed logic in their command classes by implementing
`Predis\Command\PrefixableCommandInterface` just like before.
- `Predis\PubSub\DispatcherLoop` now takes a `Predis\PubSub\Consumer` instance
as the sole argument of its constructor instead of `Predis\ClientInterface`.
- All of the interfaces and classes related to translated Redis response types
have been moved in the new `Predis\Response` namespace and most of them have
been renamed to make their fully-qualified name less redundant. Now the base
response interface is `Predis\Response\ResponseInterface`.
- Renamed interface `Predis\Command\Processor\CommandProcessorInterface` to a
shorter `Predis\Command\Processor\ProcessorInterface`. Also removed interface
for chain processors since it is basically useless.
- Renamed `Predis\ExecutableContextInterface` to `Predis\ClientContextInterface`
and augmented it with a couple of required methods since this interface is no
more comparable to a basic client as it could be misleading.
- The `Predis\Option` namespace is now known as `Predis\Configuration` and have
a fully-reworked `Options` class with the ability to lazily initialize values
using objects that responds to `__invoke()` (not all the kinds of callables)
even for custom options defined by the user.
- Renamed `Predis\Connection\ConnectionInterface::writeCommand()` into
`writeRequest()` for consistency with its counterpart, `readResponse()`.
- Renamed `Predis\Connection\SingleConnectionInterface::pushInitCommand()` into
`addConnectCommand()` which is more obvious.
- Renamed the connection class based on both ext-phpiredis and ext-socket into
`Predis\Connection\PhpiredisSocketConnection`. The one based on PHP's streams
is still named `Predis\Connection\PhpiredisStreamConnection`.
- Renamed the connection factory class to `Predis\Connection\Factory`. Now its
constructor does not require anymore a profile instance to create `AUTH` and
`SELECT` commands when parameters contain both `password` and `database`. Raw
commands will be used instead.
- Renamed the connection parameters class to `Predis\Connection\Parameters`. Now
its constructor accepts only named arrays, but instances can still be created
using both URIs or arrays using the static method `Parameters::create()`.
- The profile factory code has been extracted from the abstract Redis profile
class and now lives in `Predis\Profile\Factory`.
- The `Predis\Connection` namespace has been completely reorganized by renaming
a few classes and interfaces and adding some sub-namespaces.
- Most classes and interfaces in the `Predis\Protocol` namespace have been moved
or renamed while rationalizing the whole API for external protocol processors.
v0.8.7 (2014-08-01)
================================================================================
- Added `3.0` in the server profiles aliases list for Redis 3.0. `2.8` is still
the default server profile and `dev` still targets Redis 3.0.
- Added `COMMAND` to the server profile for Redis 2.8.
- Switched internally to the `CLUSTER SLOTS` command instead of `CLUSTER NODES`
to fetch the updated slots map from redis-cluster. This change requires users
to upgrade Redis nodes to >= 3.0.0b7.
- The updated slots map is now fetched automatically from redis-cluster upon the
first `-MOVED` response by default. This change makes it possible to feed the
client constructor with only a few nodes of the actual cluster composition,
without needing a more complex configuration.
- Implemented support for `PING` in PUB/SUB loop for Redis >= 3.0.0b8.
- The default client-side sharding strategy and the one for redis-cluster now
share the same implementations as they follow the same rules. One difference,
aside from the different hashing function used to calculate distribution, is
in how empty hash tags like {} are treated by redis-cluster.
- __FIX__: the patch applied to fix #180 introduced a regression affecting read/
write timeouts in `Predis\Connection\PhpiredisStreamConnection`. Unfortunately
the only possible solution requires PHP 5.4+. On PHP 5.3, read/write timeouts
will be ignored from now on.
v0.8.6 (2014-07-15)
================================================================================
- Redis 2.8 is now the default server profile as there are no changes that would
break compatibility with previous releases.
- Added `PFADD`, `PFCOUNT`, `PFMERGE` to the server profile for Redis 2.8 for
handling the HyperLogLog data structure introduced in Redis 2.8.9.
- Added `ZLEXCOUNT`, `ZRANGEBYLEX`, `ZREMRANGEBYLEX` to the server profile for
Redis 2.8 for handling lexicographic operations on members of sorted sets.
- Added support for key hash tags when using redis-cluster (Redis 3.0.0b1).
- __FIX__: minor tweaks to make Predis compatible with HHVM >= 2.4.0.
- __FIX__: responses to `INFO` are now properly parsed and will not break when
redis sentinel is being used (ISSUE #154).
- __FIX__: added missing support for `INCRBYFLOAT` in cluster and replication
configurations (ISSUE #159).
- __FIX__: fix parsing of the output of `CLUSTER NODES` to fetch the slots map
from a node when redis-cluster has slaves in its configuration (ISSUE #165).
- __FIX__: prevent a stack overflow when iterating over large Redis collections
using our abstraction for cursor-based iterators (ISSUE #182).
- __FIX__: properly discards transactions when the server immediately returns an
error response (e.g. -OOM or -ERR on invalid arguments for a command) instead
of a +QUEUED response (ISSUE #187).
- Upgraded to PHPUnit 4.* for the test suite.
v0.8.5 (2014-01-16)
================================================================================
- Added `2.8` in the server profiles aliases list for Redis 2.8. `2.6` is still
the default server profile and `dev` now targets Redis 3.0.
- Added `SCAN`, `SSCAN`, `ZSCAN`, `HSCAN` to the server profile for Redis 2.8.
- Implemented PHP iterators for incremental iterations over Redis collections:
- keyspace (cursor-based iterator using `SCAN`)
- sets (cursor-based iterator using `SSCAN`)
- sorted sets (cursor-based iterator using `ZSCAN`)
- hashes (cursor-based iterator using `HSCAN`)
- lists (plain iterator using `LRANGE`)
- It is now possible to execute "raw commands" using `Predis\Command\RawCommand`
and a variable list of command arguments. Input arguments are not filtered and
responses are not parsed, which means arguments must follow the signature of
the command as defined by Redis and complex responses are left untouched.
- URI parsing for connection parameters has been improved and has slightly less
overhead when the number of fields in the querystring grows. New features are:
- Parsing does not break when value of a field contains one or more "=".
- Repeated fieldnames using [] produce an array of values.
- Empty or incomplete "key=value" pairs result in an empty string for "key".
- Various improvements and fixes to the redis-cluster connection backend:
- __FIX__: the `ASKING` command is sent upon -ASK redirections.
- An updated slots-map can be fetched from nodes using the `CLUSTER NODES`
command. By default this is a manual operation but can be enabled to get
automatically done upon -MOVED redirections.
- It is possible to specify a common set of connection parameters that are
applied to connections created on the fly upon redirections to nodes not
part of the initial pool.
- List of deprecated methods:
- `Predis\Client::multiExec()`: superseded by `Predis\Client::transaction()`
and to be removed in the next major release.
- `Predis\Client::pubSub()`: superseded by `Predis\Client::pubSubLoop()` and
to be removed in the next major release. This change was needed due to the
recently introduced `PUBSUB` command in Redis 2.8.
v0.8.4 (2013-07-27)
================================================================================
- Added `DUMP` and `RESTORE` to the server profile for Redis 2.6.
- Connection exceptions now report basic host details in their messages.
- Allow `Predis\Connection\PhpiredisConnection` to use a random IP when a host
actually has several IPs (ISSUE #116).
- __FIX__: allow `HMSET` when using a cluster of Redis nodes with client-side
sharding or redis-cluster (ISSUE #106).
- __FIX__: set `WITHSCORES` modifer for `ZRANGE`, `ZREVRANGE`, `ZRANGEBYSCORE`
and `ZREVRANGEBYSCORE` only when the options array passed to these commands
has `WITHSCORES` set to `true` (ISSUE #107).
- __FIX__: scripted commands falling back from `EVALSHA` to `EVAL` resulted in
PHP errors when using a prefixed client (ISSUE #109).
- __FIX__: `Predis\PubSub\DispatcherLoop` now works properly when using key
prefixing (ISSUE #114).
v0.8.3 (2013-02-18)
================================================================================
- Added `CLIENT SETNAME` and `CLIENT GETNAME` (ISSUE #102).
- Implemented the `Predis\Connection\PhpiredisStreamConnection` class using the
`phpiredis` extension like `Predis\Connection\PhpiredisStreamConnection`, but
without requiring the `socket` extension since it relies on PHP's streams.
- Added support for the TCP_NODELAY flag via the `tcp_nodelay` parameter for
stream-based connections, namely `Predis\Connection\StreamConnection` and
`Predis\Connection\PhpiredisStreamConnection` (requires PHP >= 5.4.0).
- Updated the aggregated connection class for redis-cluster to work with 16384
hash slots instead of 4096 to reflect the recent change from redis unstable
([see this commit](https://github.com/antirez/redis/commit/ebd666d)).
- The constructor of `Predis\Client` now accepts a callable as first argument
returning `Predis\Connection\ConnectionInterface`. Users can create their
own self-contained strategies to create and set up the underlying connection.
- Users should return `0` from `Predis\Command\ScriptedCommand::getKeysCount()`
instead of `FALSE` to indicate that all of the arguments of a Lua script must
be used to populate `ARGV[]`. This does not represent a breaking change.
- The `Predis\Helpers` class has been deprecated and it will be removed in
future releases.
v0.8.2 (2013-02-03)
================================================================================
- Added `Predis\Session\SessionHandler` to make it easy to store PHP sessions
on Redis using Predis. Please note that this class needs either PHP >= 5.4.0
or a polyfill for PHP's `SessionHandlerInterface`.
- Added the ability to get the default value of a client option directly from
`Predis\Option\ClientOption` using the `getDefault()` method by passing the
option name or its instance.
- __FIX__: the standard pipeline executor was not using the response parser
methods associated to commands to process raw responses (ISSUE #101).
v0.8.1 (2013-01-19)
================================================================================
- The `connections` client option can now accept a callable object returning
an instance of `Predis\Connection\ConnectionFactoryInterface`.
- Client options accepting callable objects as factories now pass their actual
instance to the callable as the second argument.
- `Predis\Command\Processor\KeyPrefixProcessor` can now be directly casted to
string to obtain the current prefix, useful with string interpolation.
- Added an optional callable argument to `Predis\Cluster\Distribution\HashRing`
and `Predis\Cluster\Distribution\KetamaPureRing` constructor that can be used
to customize how the distributor should extract the connection hash when
initializing the nodes distribution (ISSUE #36).
- Correctly handle `TTL` and `PTTL` returning -2 on non existing keys starting
with Redis 2.8.
- __FIX__: a missing use directive in `Predis\Transaction\MultiExecContext`
caused PHP errors when Redis did not return `+QUEUED` replies to commands
when inside a MULTI / EXEC context.
- __FIX__: the `parseResponse()` method implemented for a scripted command was
ignored when retrying to execute a Lua script by falling back to `EVAL` after
a `-NOSCRIPT` error (ISSUE #94).
- __FIX__: when subclassing `Predis\Client` the `getClientFor()` method returns
a new instance of the subclass instead of a new instance of `Predis\Client`.
v0.8.0 (2012-10-23)
================================================================================
- The default server profile for Redis is now `2.6`.
- Certain connection parameters have been renamed:
- `connection_async` is now `async_connect`
- `connection_timeout` is now `timeout`
- `connection_persistent` is now `persistent`
- The `throw_errors` connection parameter has been removed and replaced by the
new `exceptions` client option since exceptions on `-ERR` replies returned by
Redis are not generated by connection classes anymore but instead are thrown
by the client class and other abstractions such as pipeline contexts.
- Added smart support for redis-cluster (Redis v3.0) in addition to the usual
cluster implementation that uses client-side sharding.
- Various namespaces and classes have been renamed to follow rules inspired by
the Symfony2 naming conventions.
- The second argument of the constructor of `Predis\Client` does not accept
strings or instances of `Predis\Profile\ServerProfileInterface` anymore.
To specify a server profile you must explicitly set `profile` in the array
of client options.
- `Predis\Command\ScriptedCommand` internally relies on `EVALSHA` instead of
`EVAL` thus avoiding to send Lua scripts bodies on each request. The client
automatically resends the command falling back to `EVAL` when Redis returns a
`-NOSCRIPT` error. Automatic fallback to `EVAL` does not work with pipelines,
inside a `MULTI / EXEC` context or with plain `EVALSHA` commands.
- Complex responses are no more parsed by connection classes as they must be
processed by consumer classes using the handler associated to the issued
command. This means that executing commands directly on connections only
returns simple Redis types, but nothing changes when using `Predis\Client`
or the provided abstractions for pipelines and transactions.
- Iterators for multi-bulk replies now skip the response parsing method of the
command that generated the response and are passed directly to user code.
Pipeline and transaction objects still consume automatically iterators.
- Cluster and replication connections now extend a new common interface,
`Predis\Connection\AggregatedConnectionInterface`.
- `Predis\Connection\MasterSlaveReplication` now uses an external strategy
class to handle the logic for checking readable / writable commands and Lua
scripts.
- Command pipelines have been optimized for both speed and code cleanness, but
at the cost of bringing a breaking change in the signature of the interface
for pipeline executors.
- Added a new pipeline executor that sends commands wrapped in a MULTI / EXEC
context to make the execution atomic: if a pipeline fails at a certain point
then the whole pipeline is discarded.
- The key-hashing mechanism for commands is now handled externally and is no
more a competence of each command class. This change is neeeded to support
both client-side sharding and Redis cluster.
- `Predis\Options\Option` is now abstract, see `Predis\Option\AbstractOption`.
v0.7.3 (2012-06-01)
================================================================================
- New commands available in the Redis v2.6 profile (dev): `BITOP`, `BITCOUNT`.
- When the number of keys `Predis\Commands\ScriptedCommand` is negative, Predis
will count from the end of the arguments list to calculate the actual number
of keys that will be interpreted as elements for `KEYS` by the underlying
`EVAL` command.
- __FIX__: `examples\CustomDistributionStrategy.php` had a mistyped constructor
call and produced a bad distribution due to an error as pointed in ISSUE #63.
This bug is limited to the above mentioned example and does not affect the
classes implemented in the `Predis\Distribution` namespace.
- __FIX__: `Predis\Commands\ServerEvalSHA::getScriptHash()` was calculating the
hash while it just needs to return the first argument of the command.
- __FIX__: `Predis\Autoloader` has been modified to allow cascading autoloaders
for the `Predis` namespace.
v0.7.2 (2012-04-01)
================================================================================
- Added `2.6` in the server profiles aliases list for the upcoming Redis 2.6.
`2.4` is still the default server profile. `dev` now targets Redis 2.8.
- Connection instances can be serialized and unserialized using `serialize()`
and `unserialize()`. This is handy in certain scenarios such as client-side
clustering or replication to lower the overhead of initializing a connection
object with many sub-connections since unserializing them can be up to 5x
times faster.
- Reworked the default autoloader to make it faster. It is also possible to
prepend it in PHP's autoload stack.
- __FIX__: fixed parsing of the payload returned by `MONITOR` with Redis 2.6.
v0.7.1 (2011-12-27)
================================================================================
- The PEAR channel on PearHub has been deprecated in favour of `pear.nrk.io`.
- Miscellaneous minor fixes.
- Added transparent support for master / slave replication configurations where
write operations are performed on the master server and read operations are
routed to one of the slaves. Please refer to ISSUE #21 for a bit of history
and more details about replication support in Predis.
- The `profile` client option now accepts a callable object used to initialize
a new instance of `Predis\Profiles\IServerProfile`.
- Exposed a method for MULTI / EXEC contexts that adds the ability to execute
instances of Redis commands against transaction objects.
v0.7.0 (2011-12-11)
================================================================================
- Predis now adheres to the PSR-0 standard which means that there is no more a
single file holding all the classes of the library, but multiple files (one
for each class). You can use any PSR-0 compatible autoloader to load Predis
or just leverage the default one shipped with the library by requiring the
`Predis/Autoloader.php` and call `Predis\Autoloader::register()`.
- The default server profile for Redis is now 2.4. The `dev` profile supports
all the features of Redis 2.6 (currently unstable) such as Lua scripting.
- Support for long aliases (method names) for Redis commands has been dropped.
- Redis 1.0 is no more supported. From now on Predis will use only the unified
protocol to serialize commands.
- It is possible to prefix keys transparently on a client-level basis with the
new `prefix` client option.
- An external connection factory is used to initialize new connection instances
and developers can now register their own connection classes using the new
`connections` client option.
- It is possible to connect locally to Redis using UNIX domain sockets. Just
use `unix:///path/to/redis.sock` or a named array just like in the following
example: `array('scheme' => 'unix', 'path' => '/path/to/redis.sock');`.
- If the `phpiredis` extension is loaded by PHP, it is now possible to use an
alternative connection class that leverages it to make Predis faster on many
cases, especially when dealing with big multibulk replies, with the the only
downside that persistent connections are not supported. Please refer to the
documentation to see how to activate this class using the new `connections`
client option.
- Predis is capable to talk with Webdis, albeit with some limitations such as
the lack of pipelining and transactions, just by using the `http` scheme in
in the connection parameters. All is needed is PHP with the `curl` and the
`phpiredis` extensions loaded.
- Way too many changes in the public API to make a list here, we just tried to
make all the Redis commands compatible with previous releases of v0.6 so that
you do not have to worry if you are simply using Predis as a client. Probably
the only breaking changes that should be mentioned here are:
- `throw_on_error` has been renamed to `throw_errors` and it is a connection
parameter instead of a client option, along with `iterable_multibulk`.
- `key_distribution` has been removed from the client options. To customize
the distribution strategy you must provide a callable object to the new
`cluster` client option to configure and then return a new instance of
`Predis\Network\IConnectionCluster`.
- `Predis\Client::create()` has been removed. Just use the constructor to set
up a new instance of `Predis\Client`.
- `Predis\Client::pipelineSafe()` was deprecated in Predis v0.6.1 and now has
finally removed. Use `Predis\Client::pipeline(array('safe' => true))`.
- `Predis\Client::rawCommand()` has been removed due to inconsistencies with
the underlying connection abstractions. You can still get the raw resource
out of a connection with `Predis\Network\IConnectionSingle::getResource()`
so that you can talk directly with Redis.
- The `Predis\MultiBulkCommand` class has been merged into `Predis\Command` and
thus removed. Serialization of commands is now a competence of connections.
- The `Predis\IConnection` interface has been splitted into two new interfaces:
`Predis\Network\IConnectionSingle` and `Predis\Network\IConnectionCluster`.
- The constructor of `Predis\Client` now accepts more type of arguments such as
instances of `Predis\IConnectionParameters` and `Predis\Network\IConnection`.
v0.6.6 (2011-04-01)
================================================================================
- Switched to Redis 2.2 as the default server profile (there are no changes
that would break compatibility with previous releases). Long command names
are no more supported by default but if you need them you can still require
`Predis_Compatibility.php` to avoid breaking compatibility.
- Added a `VERSION` constant to `Predis\Client`.
- Some performance improvements for multibulk replies (parsing them is about
16% faster than the previous version). A few core classes have been heavily
optimized to reduce overhead when creating new instances.
- Predis now uses by default a new protocol reader, more lightweight and
faster than the default handler-based one. Users can revert to the old
protocol reader with the `reader` client option set to `composable`.
This client option can also accept custom reader classes implementing the
new `Predis\IResponseReader` interface.
- Added support for connecting to Redis using UNIX domain sockets (ISSUE #25).
- The `read_write_timeout` connection parameter can now be set to 0 or false
to disable read and write timeouts on connections. The old behaviour of -1
is still intact.
- `ZUNIONSTORE` and `ZINTERSTORE` can accept an array to specify a list of the
source keys to be used to populate the destination key.
- `MGET`, `SINTER`, `SUNION` and `SDIFF` can accept an array to specify a list
of keys. `SINTERSTORE`, `SUNIONSTORE` and `SDIFFSTORE` can also accept an
array to specify the list of source keys.
- `SUBSCRIBE` and `PSUBSCRIBE` can accept a list of channels for subscription.
- __FIX__: some client-side clean-ups for `MULTI/EXEC` were handled incorrectly
in a couple of corner cases (ISSUE #27).
v0.6.5 (2011-02-12)
================================================================================
- __FIX__: due to an untested internal change introduced in v0.6.4, a wrong
handling of bulk reads of zero-length values was producing protocol
desynchronization errors (ISSUE #20).
v0.6.4 (2011-02-12)
================================================================================
- Various performance improvements (15% ~ 25%) especially when dealing with
long multibulk replies or when using clustered connections.
- Added the `on_retry` option to `Predis\MultiExecBlock` that can be used to
specify an external callback (or any callable object) that gets invoked
whenever a transaction is aborted by the server.
- Added inline (p)subscribtion via options when initializing an instance of
`Predis\PubSubContext`.
v0.6.3 (2011-01-01)
================================================================================
- New commands available in the Redis v2.2 profile (dev):
- Strings: `SETRANGE`, `GETRANGE`, `SETBIT`, `GETBIT`
- Lists : `BRPOPLPUSH`
- The abstraction for `MULTI/EXEC` transactions has been dramatically improved
by providing support for check-and-set (CAS) operations when using Redis >=
2.2. Aborted transactions can also be optionally replayed in automatic up
to a user-defined number of times, after which a `Predis\AbortedMultiExec`
exception is thrown.
v0.6.2 (2010-11-28)
================================================================================
- Minor internal improvements and clean ups.
- New commands available in the Redis v2.2 profile (dev):
- Strings: `STRLEN`
- Lists : `LINSERT`, `RPUSHX`, `LPUSHX`
- ZSets : `ZREVRANGEBYSCORE`
- Misc. : `PERSIST`
- WATCH also accepts a single array parameter with the keys that should be
monitored during a transaction.
- Improved the behaviour of `Predis\MultiExecBlock` in certain corner cases.
- Improved parameters checking for the SORT command.
- __FIX__: the `STORE` parameter for the `SORT` command didn't work correctly
when using `0` as the target key (ISSUE #13).
- __FIX__: the methods for `UNWATCH` and `DISCARD` do not break anymore method
chaining with `Predis\MultiExecBlock`.
v0.6.1 (2010-07-11)
================================================================================
- Minor internal improvements and clean ups.
- New commands available in the Redis v2.2 profile (dev):
- Misc. : `WATCH`, `UNWATCH`
- Optional modifiers for `ZRANGE`, `ZREVRANGE` and `ZRANGEBYSCORE` queries are
supported using an associative array passed as the last argument of their
respective methods.
- The `LIMIT` modifier for `ZRANGEBYSCORE` can be specified using either:
- an indexed array: `array($offset, $count)`
- an associative array: `array('offset' => $offset, 'count' => $count)`
- The method `Predis\Client::__construct()` now accepts also instances of
`Predis\ConnectionParameters`.
- `Predis\MultiExecBlock` and `Predis\PubSubContext` now throw an exception
when trying to create their instances using a profile that does not
support the required Redis commands or when the client is connected to
a cluster of connections.
- Various improvements to `Predis\MultiExecBlock`:
- fixes and more consistent behaviour across various usage cases.
- support for `WATCH` and `UNWATCH` when using the current development
profile (Redis v2.2) and aborted transactions.
- New signature for `Predis\Client::multiExec()` which is now able to accept
an array of options for the underlying instance of `Predis\MultiExecBlock`.
Backwards compatibility with previous releases of Predis is ensured.
- New signature for `Predis\Client::pipeline()` which is now able to accept
an array of options for the underlying instance of Predis\CommandPipeline.
Backwards compatibility with previous releases of Predis is ensured.
The method `Predis\Client::pipelineSafe()` is to be considered deprecated.
- __FIX__: The `WEIGHT` modifier for `ZUNIONSTORE` and `ZINTERSTORE` was
handled incorrectly with more than two weights specified.
v0.6.0 (2010-05-24)
================================================================================
- Switched to the new multi-bulk request protocol for all of the commands
in the Redis 1.2 and Redis 2.0 profiles. Inline and bulk requests are now
deprecated as they will be removed in future releases of Redis.
- The default server profile is `2.0` (targeting Redis 2.0.x). If you are
using older versions of Redis, it is highly recommended that you specify
which server profile the client should use (e.g. `1.2` when connecting
to instances of Redis 1.2.x).
- Support for Redis 1.0 is now optional and it is provided by requiring
'Predis_Compatibility.php' before creating an instance of `Predis\Client`.
- New commands added to the Redis 2.0 profile since Predis 0.5.1:
- Strings: `SETEX`, `APPEND`, `SUBSTR`
- ZSets : `ZCOUNT`, `ZRANK`, `ZUNIONSTORE`, `ZINTERSTORE`, `ZREMBYRANK`,
`ZREVRANK`
- Hashes : `HSET`, `HSETNX`, `HMSET`, `HINCRBY`, `HGET`, `HMGET`, `HDEL`,
`HEXISTS`, `HLEN`, `HKEYS`, `HVALS`, `HGETALL`
- PubSub : `PUBLISH`, `SUBSCRIBE`, `UNSUBSCRIBE`
- Misc. : `DISCARD`, `CONFIG`
- Introduced client-level options with the new `Predis\ClientOptions` class.
Options can be passed to the constructor of `Predis\Client` in its second
argument as an array or an instance of `Predis\ClientOptions`. For brevity's
sake and compatibility with older versions, the constructor still accepts
an instance of `Predis\RedisServerProfile` in its second argument. The
currently supported client options are:
- `profile` [default: `2.0` as of Predis 0.6.0]: specifies which server
profile to use when connecting to Redis. This option accepts an instance
of `Predis\RedisServerProfile` or a string that indicates the version.
- `key_distribution` [default: `Predis\Distribution\HashRing`]: specifies
which key distribution strategy to use to distribute keys among the
servers that compose a cluster. This option accepts an instance of
`Predis\Distribution\IDistributionStrategy` so that users can implement
their own key distribution strategy. `Predis\Distribution\KetamaPureRing`
is an alternative distribution strategy providing a pure-PHP implementation
of the same algorithm used by libketama.
- `throw_on_error` [default: `TRUE`]: server errors can optionally be handled
"silently": instead of throwing an exception, the client returns an error
response type.
- `iterable_multibulk` [EXPERIMENTAL - default: `FALSE`]: in addition to the
classic way of fetching a whole multibulk reply into an array, the client
can now optionally stream a multibulk reply down to the user code by using
PHP iterators. It is just a little bit slower, but it can save a lot of
memory in certain scenarios.
- New parameters for connections:
- `alias` [default: not set]: every connection can now be identified by an
alias that is useful to get a specific connections when connected to a
cluster of Redis servers.
- `weight` [default: not set]: allows to balance keys asymmetrically across
multiple servers. This is useful when you have servers with different
amounts of memory to distribute the load of your keys accordingly.
- `connection_async` [default: `FALSE`]: estabilish connections to servers
in a non-blocking way, so that the client is not blocked while the socket
resource performs the actual connection.
- `connection_persistent` [default: `FALSE`]: the underlying socket resource
is left open when a script ends its lifecycle. Persistent connections can
lead to unpredictable or strange behaviours, so they should be used with
extreme care.
- Introduced the `Predis\Pipeline\IPipelineExecutor` interface. Classes that
implements this interface are used internally by the `Predis\CommandPipeline`
class to change the behaviour of the pipeline when writing/reading commands
from one or multiple servers. Here is the list of the default executors:
- `Predis\Pipeline\StandardExecutor`: exceptions generated by server errors
might be thrown depending on the options passed to the client (see the
`throw_on_error` client option). Instead, protocol or network errors always
throw exceptions. This is the default executor for single and clustered
connections and shares the same behaviour of Predis 0.5.x.
- `Predis\Pipeline\SafeExecutor`: exceptions generated by server, protocol
or network errors are not thrown but returned in the response array as
instances of `Predis\ResponseError` or `Predis\CommunicationException`.
- `Predis\Pipeline\SafeClusterExecutor`: this executor shares the same
behaviour of `Predis\Pipeline\SafeExecutor` but it is geared towards
clustered connections.
- Support for PUB/SUB is handled by the new `Predis\PubSubContext` class, which
could also be used to build a callback dispatcher for PUB/SUB scenarios.
- When connected to a cluster of connections, it is now possible to get a
new `Predis\Client` instance for a single connection of the cluster by
passing its alias/index to the new `Predis\Client::getClientFor()` method.
- `Predis\CommandPipeline` and `Predis\MultiExecBlock` return their instances
when invokink commands, thus allowing method chaining in pipelines and
multi-exec blocks.
- `Predis\MultiExecBlock` can handle the new `DISCARD` command.
- Connections now support float values for the `connection_timeout` parameter
to express timeouts with a microsecond resolution.
- __FIX__: TCP connections now respect the read/write timeout parameter when
reading the payload of server responses. Previously, `stream_get_contents()`
was being used internally to read data from a connection but it looks like
PHP does not honour the specified timeout for socket streams when inside
this function.
- __FIX__: The `GET` parameter for the `SORT` command now accepts also multiple
key patterns by passing an array of strings. (ISSUE #1).
* __FIX__: Replies to the `DEL` command return the number of elements deleted
by the server and not 0 or 1 interpreted as a boolean response. (ISSUE #4).
v0.5.1 (2010-01-23)
================================================================================
* `RPOPLPUSH` has been changed from bulk command to inline command in Redis
1.2.1, so `ListPopLastPushHead` now extends `InlineCommand`. The old behavior
is still available via the `ListPopLastPushHeadBulk` class so that you can
override the server profile if you need the old (and uncorrect) behaviour
when connecting to a Redis 1.2.0 instance.
* Added missing support for `BGREWRITEAOF` for Redis >= 1.2.0.
* Implemented a factory method for the `RedisServerProfile` class to ease the
creation of new server profile instances based on a version string.
v0.5.0 (2010-01-09)
================================================================================
* First versioned release of Predis

44
vendor/predis/predis/CONTRIBUTING.md vendored Normal file
View File

@@ -0,0 +1,44 @@
## Filing bug reports ##
Bugs or feature requests can be posted on the [GitHub issues](http://github.com/nrk/predis/issues)
section of the project.
When reporting bugs, in addition to the obvious description of your issue you __must__ always provide
some essential information about your environment such as:
1. version of Predis (check the `VERSION` file or the `Predis\Client::VERSION` constant).
2. version of Redis (check `redis_version` returned by [`INFO`](http://redis.io/commands/info)).
3. version of PHP.
4. name and version of the operating system.
5. when possible, a small snippet of code that reproduces the issue.
__Think about it__: we do not have a crystal ball and cannot predict things or peer into the unknown
so please provide as much details as possible to help us isolating issues and fix them.
__Never__ use GitHub issues to post generic questions about Predis! When you have questions about
how Predis works or how it can be used, please just hop me an email and I will get back to you as
soon as possible.
## Contributing code ##
If you want to work on Predis, it is highly recommended that you first run the test suite in order
to check that everything is OK and report strange behaviours or bugs. When modifying Predis please
make sure that no warnings or notices are emitted by PHP running the interpreter in your development
environment with the `error_reporting` variable set to `E_ALL | E_STRICT`.
The recommended way to contribute to Predis is to fork the project on GitHub, create topic branches
on your newly created repository to fix bugs or add new features (possibly with tests covering your
modifications) and then open a pull request with a description of the applied changes. Obviously you
can use any other Git hosting provider of your preference.
We always aim for consistency in our code base so you should follow basic coding rules as defined by
[PSR-1](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md)
and [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)
and stick with the conventions used in Predis to name classes and interfaces. Indentation should be
done with 4 spaces and code should be wrapped at 100 columns (please try to stay within this limit
even if the above mentioned official coding guidelines set the soft limit to 120 columns).
Please follow these [commit guidelines](http://git-scm.com/book/ch5-2.html#Commit-Guidelines) when
committing your code to Git and always write a meaningful (not necessarily extended) description of
your changes before opening pull requests.

177
vendor/predis/predis/FAQ.md vendored Normal file
View File

@@ -0,0 +1,177 @@
# Some frequently asked questions about Predis #
________________________________________________
### What is the point of Predis? ###
The main point of Predis is about offering a highly customizable and extensible client for Redis,
that can be easily extended by developers while still being reasonabily fast. With Predis you can
swap almost any class with your own custom implementation: you can have custom connection classes,
new distribution strategies for client-side sharding, or handlers to replace or add Redis commands.
All of this can be achieved without messing with the source code of the library and directly in your
own application. Given the fast pace at which Redis is developed and adds new features, this can be
a great asset since it allows developers to add new and still missing features or commands or change
the standard behaviour of the library without the need to break dependencies in production code (at
least to some degree).
### Does Predis support UNIX domain sockets and persistent connections? ###
Yes. Obviously persistent connections actually work 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
usually something that developers prefer to customize depending on their needs and can not be easily
generalized when using Redis because of the many possible access patterns for your data. This does
not mean that it is impossible to have such a feature since you can leverage the extensibility of
this library to define your own serialization-aware commands. You can find more details about how to
do that [on this issue](http://github.com/nrk/predis/issues/29#issuecomment-1202624).
### How can I force Predis to connect to Redis before sending any command? ###
Explicitly connecting to Redis is usually not needed since the client initializes connections lazily
only when they are needed. Admittedly, this behavior can be inconvenient in certain scenarios when
you absolutely need to perform an upfront check to determine if the server is up and running and
eventually catch exceptions on failures. Forcing the client to open the underlying connection can be
done by invoking `Predis\Client::connect()`:
```php
$client = new Predis\Client();
try {
$client->connect();
} catch (Predis\Connection\ConnectionException $exception) {
// We could not connect to Redis! Your handling code goes here.
}
$client->info();
```
### How Predis abstracts Redis commands? ###
The approach used to implement Redis commands is quite simple: by default each command follows the
same signature as defined on the [Redis documentation](http://redis.io/commands) which makes things
pretty easy if you already know how Redis works or you need to look up how to use certain commands.
Alternatively, variadic commands can accept an array for keys or values (depending on the command)
instead of a list of arguments. Commands such as [`RPUSH`](http://redis.io/commands/rpush) and
[`HMSET`](http://redis.io/commands/hmset) are great examples:
```php
$client->rpush('my:list', 'value1', 'value2', 'value3'); // plain method arguments
$client->rpush('my:list', ['value1', 'value2', 'value3']); // single argument array
$client->hmset('my:hash', 'field1', 'value1', 'field2', 'value2'); // plain method arguments
$client->hmset('my:hash', ['field1'=>'value1', 'field2'=>'value2']); // single named array
```
An exception to this rule is [`SORT`](http://redis.io/commands/sort) for which modifiers are passed
[using a named array](tests/Predis/Command/KeySortTest.php#L54-L75).
# Speaking about performances... #
_________________________________________________
### Predis is a pure-PHP implementation: it can not be fast enough! ###
It really depends, but most of the times the answer is: _yes, it is fast enough_. I will give you a
couple of easy numbers with a simple test that uses a single client and is executed by PHP 5.5.6
against a local instance of Redis 2.8 that runs under Ubuntu 13.10 on a Intel Q6600:
```
21000 SET/sec using 12 bytes for both key and value.
21000 GET/sec while retrieving the very same values.
0.130 seconds to fetch 30000 keys using _KEYS *_.
```
How does it compare with [__phpredis__](http://github.com/nicolasff/phpredis), a nice C extension
providing an efficient client for Redis?
```
30100 SET/sec using 12 bytes for both key and value
29400 GET/sec while retrieving the very same values
0.035 seconds to fetch 30000 keys using "KEYS *"".
```
Wow __phpredis__ seems much faster! Well, we are comparing a C extension with a pure-PHP library so
lower numbers are quite expected but there is a fundamental flaw in them: is this really how you are
going to use Redis in your application? Are you really going to send thousands of commands using a
for-loop on each page request using a single client instance? If so... well I guess you are probably
doing something wrong. Also, if you need to `SET` or `GET` multiple keys you should definitely use
commands such as `MSET` and `MGET`. You can also use pipelining to get more performances when this
technique can be used.
There is one more thing: we have tested the overhead of Predis by connecting on a localhost instance
of Redis but how these numbers change when we hit the physical network by connecting to remote Redis
instances?
```
Using Predis:
3200 SET/sec using 12 bytes for both key and value
3200 GET/sec while retrieving the very same values
0.132 seconds to fetch 30000 keys using "KEYS *".
Using phpredis:
3500 SET/sec using 12 bytes for both key and value
3500 GET/sec while retrieving the very same values
0.045 seconds to fetch 30000 keys using "KEYS *".
```
There you go, you get almost the same average numbers and the reason is simple: network latency is a
real performance killer and you cannot do (almost) anything about that. As a disclaimer, remember
that we are measuring the overhead of client libraries implementations and the effects of network
round-trip times, so we are not really measuring how fast Redis is. Redis shines best with thousands
of concurrent clients doing requests! Also, actual performances should be measured according to how
your application will use Redis.
### I am convinced, but performances for multi-bulk responses are still worse ###
Fair enough, but there is an option available if you need even more speed and consists on installing
__[phpiredis](http://github.com/nrk/phpiredis)__ (note the additional _i_ in the name) and let the
client use it. __phpiredis__ is another C extension that wraps __hiredis__ (the official C client
library for Redis) with a thin layer exposing its features to PHP. You can then choose between two
different connection classes:
- `Predis\Connection\PhpiredisStreamConnection` (using native PHP streams).
- `Predis\Connection\PhpiredisSocketConnection` (requires `ext-socket`).
You will now get the benefits of a faster protocol serializer and parser just by adding a couple of
lines of code:
```php
$client = new Predis\Client('tcp://127.0.0.1', array(
'connections' => array(
'tcp' => 'Predis\Connection\PhpiredisStreamConnection',
'unix' => 'Predis\Connection\PhpiredisSocketConnection',
),
));
```
Dead simple. Nothing changes in the way you use the library in your application. So how fast is it
our basic benchmark script now? There are not much improvements for inline or short bulk responses
like the ones returned by `SET` and `GET`, but the speed for parsing multi-bulk responses is now on
par with phpredis:
```
Fatching 30000 keys with _KEYS *_ using Predis paired with phpiredis::
0.035 seconds from a local Redis instance
0.047 seconds from a remote Redis instance
```
### If I need an extension to get better performances, why not using phpredis? ###
Good question. Generically speaking if you need absolute uber-speed using Redis on the localhost and
you do not care about abstractions built around some Redis features such as MULTI / EXEC, or if you
do not need any kind of extensibility or guaranteed backwards compatibility with different versions
of Redis (Predis currently supports from 1.2 up to 2.8 and the current development version), then
using __phpredis__ makes absolutely sense. Otherwise, Predis is perfect for the job and by adding
__phpiredis__ you can get a nice speed bump almost for free.

22
vendor/predis/predis/LICENSE vendored Normal file
View File

@@ -0,0 +1,22 @@
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
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

492
vendor/predis/predis/README.md vendored Normal file
View File

@@ -0,0 +1,492 @@
# 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]
Flexible and feature-complete [Redis](http://redis.io) client for PHP >= 5.3 and HHVM >= 2.3.0.
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).
## Main features ##
- Support for different versions of Redis (from __2.0__ to __3.2__) using profiles.
- 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 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 profiles and override the default ones.
## How to _install_ and use Predis ##
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 ###
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 lacking any autoload facility:
```php
// Prepend a base path if Predis is not available in your "include_path".
require 'Predis/Autoloader.php';
Predis\Autoloader::register();
```
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 ###
When creating a client instance without passing any connection parameter, Predis assumes `127.0.0.1`
and `6379` as default host and port. The default timeout for the `connect()` operation is 5 seconds:
```php
$client = new Predis\Client();
$client->set('foo', 'bar');
$value = $client->get('foo');
```
Connection parameters can be supplied either in the form of URI strings or named arrays. The latter
is the preferred way to supply parameters, but URI strings can be useful when parameters are read
from non-structured or partially-structured sources:
```php
// Parameters passed using a named array:
$client = new Predis\Client([
'scheme' => 'tcp',
'host' => '10.0.0.1',
'port' => 6379,
]);
// Same set of parameters, passed using an URI string:
$client = new Predis\Client('tcp://10.0.0.1:6379');
```
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.
When an array of connection parameters is provided, Predis automatically works in cluster mode using
client-side sharding. Both named arrays and URI strings can be mixed when providing configurations
for each node:
```php
$client = new Predis\Client([
'tcp://10.0.0.1?alias=first-node',
['host' => '10.0.0.2', 'alias' => 'second-node'],
]);
```
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 ###
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:']);
```
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`: specifies the profile to use to match a specific version of Redis.
- `prefix`: prefix string automatically applied to keys found in commands.
- `exceptions`: whether the client should throw or return responses upon Redis errors.
- `connections`: list of connection backends or a connection factory instance.
- `cluster`: specifies a cluster backend (`predis`, `redis` or callable object).
- `replication`: specifies a replication backend (`TRUE`, `sentinel` or callable object).
- `aggregate`: overrides `cluster` and `replication` to provide a custom connections aggregator.
- `parameters`: list of default connection parameters for aggregate connections.
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 ###
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 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 = ['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,
when certain Lua scripts do not perform write operations it is possible to provide an hint to tell
the client to stick with slaves for their execution:
```php
$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2', '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);
}];
$client = new Predis\Client($parameters, $options);
$client->eval($LUA_SCRIPT, 0); // Sticks to slave using `eval`...
$client->evalsha(sha1($LUA_SCRIPT), 0); // ... and `evalsha`, too.
```
The [`examples`](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 ###
Pipelining can help with performances when many commands need to be sent to a server by reducing the
latency introduced by network round-trip timings. Pipelining also works with aggregate connections.
The client can execute the pipeline inside a callable block or return a pipeline instance with the
ability to chain commands thanks to its fluent interface:
```php
// Executes a pipeline inside the given callable block:
$responses = $client->pipeline(function ($pipe) {
for ($i = 0; $i < 1000; $i++) {
$pipe->set("key:$i", str_pad($i, 4, '0', 0));
$pipe->get("key:$i");
}
});
// Returns a pipeline that can be chained thanks to its fluent interface:
$responses = $client->pipeline()->set('foo', 'bar')->get('foo')->execute();
```
### Transactions ###
The client provides an abstraction for Redis transactions based on `MULTI` and `EXEC` with a similar
interface to command pipelines:
```php
// Executes a transaction inside the given callable block:
$responses = $client->transaction(function ($tx) {
$tx->set('foo', 'bar');
$tx->get('foo');
});
// Returns a transaction that can be chained thanks to its fluent interface:
$responses = $client->transaction()->set('foo', 'bar')->get('foo')->execute();
```
This abstraction can perform check-and-set operations thanks to `WATCH` and `UNWATCH` and provides
automatic retries of transactions aborted by Redis when `WATCH`ed keys are touched. For an example
of a transaction using CAS you can see [the following example](examples/transaction_using_cas.php).
### Adding new commands ###
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:
```php
// Define a new command by extending Predis\Command\Command:
class BrandNewRedisCommand extends Predis\Command\Command
{
public function getId()
{
return 'NEWCMD';
}
}
// Inject your command in the current profile:
$client = new Predis\Client();
$client->getProfile()->defineCommand('newcmd', 'BrandNewRedisCommand');
$response = $client->newcmd();
```
There is also a method to send raw commands without filtering their arguments or parsing responses.
Users must provide the list of arguments for the command as an array, following the signatures as
defined by the [Redis documentation for commands](http://redis.io/commands):
```php
$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
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
they were plain Redis commands, but they define Lua scripts that get transmitted to the server for
remote execution. Internally they use [`EVALSHA`](http://redis.io/commands/evalsha) by default and
identify a script by its SHA1 hash to save bandwidth, but [`EVAL`](http://redis.io/commands/eval)
is used as a fall back when needed:
```php
// Define a new script command by extending Predis\Command\ScriptCommand:
class ListPushRandomValue extends Predis\Command\ScriptCommand
{
public function getKeysCount()
{
return 1;
}
public function getScript()
{
return <<<LUA
math.randomseed(ARGV[1])
local rnd = tostring(math.random())
redis.call('lpush', KEYS[1], rnd)
return rnd
LUA;
}
}
// Inject the script command in the current profile:
$client = new Predis\Client();
$client->getProfile()->defineCommand('lpushrand', 'ListPushRandomValue');
$response = $client->lpushrand('random_values', $seed = mt_rand());
```
### Customizable connection backends ###
Predis can use different connection backends to connect to Redis. Two of them leverage a third party
extension such as [phpiredis](https://github.com/nrk/phpiredis) resulting in major performance gains
especially when dealing with big multibulk responses. While one is based on PHP streams, the other
is based on socket resources provided by `ext-socket`. Both support TCP/IP and UNIX domain sockets:
```php
$client = new Predis\Client('tcp://127.0.0.1', [
'connections' => [
'tcp' => 'Predis\Connection\PhpiredisStreamConnection', // PHP stream resources
'unix' => 'Predis\Connection\PhpiredisSocketConnection', // ext-socket resources
],
]);
```
Developers can create their own connection classes to support whole new network backends, extend
existing classes or provide completely different implementations. Connection classes must implement
`Predis\Connection\NodeConnectionInterface` or extend `Predis\Connection\AbstractConnection`:
```php
class MyConnectionClass implements Predis\Connection\NodeConnectionInterface
{
// Implementation goes here...
}
// Use MyConnectionClass to handle connections for the `tcp` scheme:
$client = new Predis\Client('tcp://127.0.0.1', [
'connections' => ['tcp' => 'MyConnectionClass'],
]);
```
For a more in-depth insight on how to create new connection backends you can refer to the actual
implementation of the standard connection classes available in the `Predis\Connection` namespace.
## Development ##
### Reporting bugs and contributing code ###
Contributions to Predis are highly appreciated either in the form of pull requests for new features,
bug fixes, or just bug reports. We only ask you to adhere to a [basic set of rules](CONTRIBUTING.md)
before submitting your changes or filing bugs on the issue tracker to make it easier for everyone to
stay consistent while working on the project.
### Test suite ###
__ATTENTION__: Do not ever run the test suite shipped with Predis against instances of Redis running
in production environments or containing data you are interested in!
Predis has a comprehensive test suite covering every aspect of the library. This test suite performs
integration tests against a running instance of Redis (>= 2.4.0 is required) to verify the correct
behavior of the implementation of each command and automatically skips commands not defined in the
specified Redis profile. If you do not have Redis up and running, integration tests can be disabled.
By default the test suite is configured to execute integration tests using the profile for Redis 3.2
(which is the current stable version of Redis) but can optionally target a Redis instance built from
the `unstable` branch by modifying `phpunit.xml` and setting `REDIS_SERVER_VERSION` to `dev` so that
the development server profile will be used. You can refer to [the tests README](tests/README.md)
for more detailed information about testing Predis.
Predis uses Travis CI for continuous integration and the history for past and current builds can be
found [on its project page](http://travis-ci.org/nrk/predis).
## Other ##
### Project related links ###
- [Source code](https://github.com/nrk/predis)
- [Wiki](https://wiki.github.com/nrk/predis)
- [Issue tracker](https://github.com/nrk/predis/issues)
- [PEAR channel](http://pear.nrk.io)
### Author ###
- [Daniele Alessandri](mailto:suppakilla@gmail.com) ([twitter](http://twitter.com/JoL1hAHN))
### 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
vendor/predis/predis/VERSION vendored Normal file
View File

@@ -0,0 +1 @@
1.1.1

14
vendor/predis/predis/autoload.php vendored Normal file
View File

@@ -0,0 +1,14 @@
<?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__.'/src/Autoloader.php';
Predis\Autoloader::register();

View File

@@ -0,0 +1,275 @@
#!/usr/bin/env php
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
// -------------------------------------------------------------------------- //
// This script can be used to automatically generate a file with the scheleton
// of a test case to test a Redis command by specifying the name of the class
// in the Predis\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):
//
// $ ./bin/generate-command-test --class=StringSet
//
// Here is a list of optional arguments:
//
// --realm: each command has its own realm (commands that operate on strings,
// lists, sets and such) but while this realm is usually inferred from the name
// of the specified class, sometimes it can be useful to override it with a
// custom one.
//
// --output: write the generated test case to the specified path instead of
// the default one.
//
// --overwrite: pre-existing test files are not overwritten unless this option
// is explicitly specified.
// -------------------------------------------------------------------------- //
use Predis\Command\CommandInterface;
use Predis\Command\PrefixableCommandInterface;
class CommandTestCaseGenerator
{
private $options;
public function __construct(array $options)
{
if (!isset($options['class'])) {
throw new RuntimeException("Missing 'class' option.");
}
$this->options = $options;
}
public static function fromCommandLine()
{
$parameters = array(
'c:' => 'class:',
'r::' => 'realm::',
'o::' => 'output::',
'x::' => 'overwrite::'
);
$getops = getopt(implode(array_keys($parameters)), $parameters);
$options = array(
'overwrite' => false,
'tests' => __DIR__.'/../tests/Predis',
);
foreach ($getops as $option => $value) {
switch ($option) {
case 'c':
case 'class':
$options['class'] = $value;
break;
case 'r':
case 'realm':
$options['realm'] = $value;
break;
case 'o':
case 'output':
$options['output'] = $value;
break;
case 'x':
case 'overwrite':
$options['overwrite'] = true;
break;
}
}
if (!isset($options['class'])) {
throw new RuntimeException("Missing 'class' option.");
}
$options['fqn'] = "Predis\\Command\\{$options['class']}";
$options['path'] = "Command/{$options['class']}.php";
$source = __DIR__.'/../src/'.$options['path'];
if (!file_exists($source)) {
throw new RuntimeException("Cannot find class file for {$options['fqn']} in $source.");
}
if (!isset($options['output'])) {
$options['output'] = sprintf("%s/%s", $options['tests'], str_replace('.php', 'Test.php', $options['path']));
}
return new self($options);
}
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'];
}
$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);
}
public function generate()
{
$reflection = new ReflectionClass($class = $this->options['fqn']);
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.");
}
/*
* @var CommandInterface
*/
$instance = $reflection->newInstance();
$buffer = $this->getTestCaseBuffer($instance);
return $buffer;
}
public function save()
{
$options = $this->options;
if (file_exists($options['output']) && !$options['overwrite']) {
throw new RuntimeException("File {$options['output']} already exist. Specify the --overwrite option to overwrite the existing file.");
}
file_put_contents($options['output'], $this->generate());
}
protected function getTestCaseBuffer(CommandInterface $instance)
{
$id = $instance->getId();
$fqn = get_class($instance);
$fqnParts = explode('\\', $fqn);
$class = array_pop($fqnParts) . "Test";
$realm = $this->getTestRealm();
$buffer =<<<PHP
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @group commands
* @group realm-$realm
*/
class $class extends PredisCommandTestCase
{
/**
* {@inheritdoc}
*/
protected function getExpectedCommand()
{
return '$fqn';
}
/**
* {@inheritdoc}
*/
protected function getExpectedId()
{
return '$id';
}
/**
* @group disconnected
*/
public function testFilterArguments()
{
\$this->markTestIncomplete('This test has not been implemented yet.');
\$arguments = array(/* add arguments */);
\$expected = array(/* add arguments */);
\$command = \$this->getCommand();
\$command->setArguments(\$arguments);
\$this->assertSame(\$expected, \$command->getArguments());
}
/**
* @group disconnected
*/
public function testParseResponse()
{
\$this->markTestIncomplete('This test has not been implemented yet.');
\$raw = null;
\$expected = null;
\$command = \$this->getCommand();
\$this->assertSame(\$expected, \$command->parseResponse(\$raw));
}
PHP;
if ($instance instanceof PrefixableCommandInterface) {
$buffer .=<<<PHP
/**
* @group disconnected
*/
public function testPrefixKeys()
{
\$this->markTestIncomplete('This test has not been implemented yet.');
\$arguments = array(/* add arguments */);
\$expected = array(/* add arguments */);
\$command = \$this->getCommandWithArgumentsArray(\$arguments);
\$command->prefixKeys('prefix:');
\$this->assertSame(\$expected, \$command->getArguments());
}
/**
* @group disconnected
*/
public function testPrefixKeysIgnoredOnEmptyArguments()
{
\$command = \$this->getCommand();
\$command->prefixKeys('prefix:');
\$this->assertSame(array(), \$command->getArguments());
}
PHP;
}
return "$buffer}\n";
}
}
// ------------------------------------------------------------------------- //
require __DIR__.'/../autoload.php';
$generator = CommandTestCaseGenerator::fromCommandLine();
$generator->save();

233
vendor/predis/predis/bin/create-pear vendored Normal file
View File

@@ -0,0 +1,233 @@
#!/usr/bin/env php
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
// -------------------------------------------------------------------------- //
// In order to be able to execute this script to create a PEAR package of Predis
// the `pear` binary must be available and executable in your $PATH.
// The parts used to parse author and version strings are taken from Onion (used
// by this library in the past) just to keep on relying on the package.ini file
// to simplify things. We might consider to switch to using the PEAR classes
// directly in the future.
// -------------------------------------------------------------------------- //
function executeWithBackup($file, $callback)
{
$exception = null;
$backup = "$file.backup";
copy($file, $backup);
try {
call_user_func($callback, $file);
} catch (Exception $exception) {
// NOOP
}
unlink($file);
rename($backup, $file);
if ($exception) {
throw $exception;
}
}
function parseAuthor($string)
{
$author = array();
if (preg_match('/^\s*(.+?)\s*(?:"(\S+)"\s*)?<(\S+)>\s*$/x', $string , $regs)) {
if (count($regs) == 4) {
list($_,$name,$user,$email) = $regs;
$author['name'] = $name;
$author['user'] = $user;
$author['email'] = $email;
} elseif (count($regs) == 3) {
list($_,$name,$email) = $regs;
$author['name'] = $name;
$author['email'] = $email;
}
} else {
$author['name'] = $string;
}
return $author;
}
function parseVersion($string)
{
$version_pattern = '([0-9.]+)';
if (preg_match("/^\s*$version_pattern\s*\$/x", $string, $regs)) {
return array('min' => $regs[1] ?: '0.0.0');
} elseif (preg_match("/^\s*[>=]+\s*$version_pattern\s*\$/x", $string, $regs)) {
return array('min' => $regs[1] ?: '0.0.0');
} elseif (preg_match("/^\s*[<=]+\s*$version_pattern\s*\$/x", $string, $regs)) {
return array('max' => $regs[1]);
} elseif (preg_match("/^\s*$version_pattern\s*<=>\s*$version_pattern\s*\$/x", $string, $regs)) {
return array(
'min' => $regs[1] ?: '0.0.0',
'max' => $regs[2],
);
}
return null;
}
function addRolePath($pkg, $path, $role)
{
if (is_dir($path)) {
$dirRoot = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$dirTree = new RecursiveIteratorIterator($dirRoot, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($dirTree as $fileinfo) {
if ($fileinfo->isFile()) {
addPackageFile($pkg, $fileinfo, $role, $path);
}
}
} else {
foreach (glob($path) as $filename) {
addPackageFile($pkg, new SplFileInfo($filename), $role);
}
}
}
function addPackageFile($pkg, $fileinfo, $role, $baseDir = '')
{
$fileNode = $pkg->contents->dir->addChild('file');
$fileNode->addAttribute('name', $filepath = $fileinfo->getPathname());
$fileNode->addAttribute('role', $role);
$fileNode->addAttribute('md5sum', md5_file($filepath));
$installNode = $pkg->phprelease->filelist->addChild('install');
$installNode->addAttribute('name', $filepath);
$installNode->addAttribute('as', !$baseDir ? basename($filepath) : substr($filepath, strlen($baseDir) + 1));
}
function generatePackageXml($packageINI)
{
$XML = <<<XML
<?xml version="1.0"?>
<package packagerversion="1.4.10" version="2.0"
xmlns="http://pear.php.net/dtd/package-2.0"
xmlns:tasks="http://pear.php.net/dtd/tasks-1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
http://pear.php.net/dtd/tasks-1.0.xsd
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd" />
XML;
$cfg = parse_ini_file($packageINI, true);
$pkg = new SimpleXMLElement($XML);
$pkg->name = $cfg['package']['name'];
$pkg->channel = $cfg['package']['channel'];
$pkg->summary = $cfg['package']['desc'];
$pkg->description = $cfg['package']['desc'];
$author = parseAuthor($cfg['package']['author']);
$pkg->addChild('lead');
$pkg->lead->name = $author['name'];
$pkg->lead->user = $author['user'];
$pkg->lead->email = $author['email'];
$pkg->lead->active = 'yes';
$datetime = new DateTime('now');
$pkg->date = $datetime->format('Y-m-d');
$pkg->time = $datetime->format('H:i:s');
$pkg->addChild('version');
$pkg->version->release = $cfg['package']['version'];
$pkg->version->api = $cfg['package']['version'];
$pkg->addChild('stability');
$pkg->stability->release = $cfg['package']['stability'];
$pkg->stability->api = $cfg['package']['stability'];
$pkg->license = $cfg['package']['license'];
$pkg->notes = '-';
$pkg->addChild('contents')->addChild('dir')->addAttribute('name', '/');
$pkg->addChild('dependencies')->addChild('required');
foreach ($cfg['require'] as $required => $version) {
$version = parseVersion($version);
$pkg->dependencies->required->addChild($required);
if (isset($version['min'])) {
$pkg->dependencies->required->$required->min = $version['min'];
}
if (isset($version['max'])) {
$pkg->dependencies->required->$required->min = $version['max'];
}
}
$pkg->addChild('phprelease')->addChild('filelist');
$pathToRole = array(
'doc' => 'doc', 'docs' => 'doc', 'examples' => 'doc',
'lib' => 'php', 'src' => 'php',
'test' => 'test', 'tests' => 'test',
);
foreach (array_merge($pathToRole, $cfg['roles'] ?: array()) as $path => $role) {
addRolePath($pkg, $path, $role);
}
return $pkg;
}
function rewritePackageInstallAs($pkg)
{
foreach ($pkg->phprelease->filelist->install as $file) {
if (preg_match('/^src\//', $file['name'])) {
$file['as'] = "Predis/{$file['as']}";
}
}
}
function savePackageXml($xml)
{
$dom = new DOMDocument("1.0");
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($xml->asXML());
file_put_contents('package.xml', $dom->saveXML());
}
function buildPackage()
{
passthru('pear -q package && rm package.xml');
}
function modifyPhpunitXml($file)
{
$cfg = new SimpleXMLElement($file, null, true);
$cfg[0]['bootstrap'] = str_replace('tests/', '', $cfg[0]['bootstrap']);
$cfg->testsuites->testsuite->directory = str_replace('tests/', '', $cfg->testsuites->testsuite->directory);
$cfg->saveXml($file);
}
// -------------------------------------------------------------------------- //
executeWithBackup(__DIR__.'/../phpunit.xml.dist', function ($file) {
modifyPhpunitXml($file);
$pkg = generatePackageXml('package.ini');
rewritePackageInstallAs($pkg);
savePackageXml($pkg);
buildPackage();
});

71
vendor/predis/predis/bin/create-phar vendored Normal file
View File

@@ -0,0 +1,71 @@
#!/usr/bin/env php
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
// -------------------------------------------------------------------------- //
// In order to be able to execute this script to create a Phar archive of Predis,
// the Phar module must be loaded and the "phar.readonly" directive php.ini must
// be set to "off". You can change the values in the $options array to customize
// the creation of the Phar archive to better suit your needs.
// -------------------------------------------------------------------------- //
$options = array(
'name' => 'predis',
'project_path' => __DIR__ . '/../src',
'compression' => Phar::NONE,
'append_version' => true,
);
function getPharFilename($options)
{
$filename = $options['name'];
// NOTE: do not consider "append_version" with Phar compression do to a bug in
// Phar::compress() when renaming phar archives containing dots in their name.
if ($options['append_version'] && $options['compression'] === Phar::NONE) {
$versionFile = @fopen(__DIR__ . '/../VERSION', 'r');
if ($versionFile === false) {
throw new Exception("Could not locate the VERSION file.");
}
$version = trim(fgets($versionFile));
fclose($versionFile);
$filename .= "_$version";
}
return "$filename.phar";
}
function getPharStub($options)
{
return <<<EOSTUB
<?php
Phar::mapPhar('predis.phar');
spl_autoload_register(function (\$class) {
if (strpos(\$class, 'Predis\\\\') === 0) {
\$file = 'phar://predis.phar/'.strtr(substr(\$class, 7), '\\\', '/').'.php';
if (file_exists(\$file)) {
require \$file;
return true;
}
}
});
__HALT_COMPILER();
EOSTUB;
}
// -------------------------------------------------------------------------- //
$phar = new Phar(getPharFilename($options));
$phar->compress($options['compression']);
$phar->setStub(getPharStub($options));
$phar->buildFromDirectory($options['project_path']);

View File

@@ -0,0 +1,662 @@
#!/usr/bin/env php
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
// -------------------------------------------------------------------------- //
// This script can be used to automatically glue all the .php files of Predis
// into a single monolithic script file that can be used without an autoloader,
// just like the other previous versions of the library.
//
// Much of its complexity is due to the fact that we cannot simply join PHP
// files, but namespaces and classes definitions must follow a precise order
// when dealing with subclassing and inheritance.
//
// The current implementation is pretty naïve, but it should do for now.
// -------------------------------------------------------------------------- //
class CommandLine
{
public static function getOptions()
{
$parameters = array(
's:' => 'source:',
'o:' => 'output:',
'e:' => 'exclude:',
'E:' => 'exclude-classes:',
);
$getops = getopt(implode(array_keys($parameters)), $parameters);
$options = array(
'source' => __DIR__ . "/../src",
'output' => PredisFile::NS_ROOT . '.php',
'exclude' => array(),
);
foreach ($getops as $option => $value) {
switch ($option) {
case 's':
case 'source':
$options['source'] = $value;
break;
case 'o':
case 'output':
$options['output'] = $value;
break;
case 'E':
case 'exclude-classes':
$options['exclude'] = @file($value, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: $value;
break;
case 'e':
case 'exclude':
$options['exclude'] = is_array($value) ? $value : array($value);
break;
}
}
return $options;
}
}
class PredisFile
{
const NS_ROOT = 'Predis';
private $namespaces;
public function __construct()
{
$this->namespaces = array();
}
public static function from($libraryPath, array $exclude = array())
{
$predisFile = new PredisFile();
$libIterator = new RecursiveDirectoryIterator($libraryPath);
foreach (new RecursiveIteratorIterator($libIterator) as $classFile)
{
if (!$classFile->isFile()) {
continue;
}
$namespace = self::NS_ROOT.strtr(str_replace($libraryPath, '', $classFile->getPath()), '/', '\\');
if (in_array(sprintf('%s\\%s', $namespace, $classFile->getBasename('.php')), $exclude)) {
continue;
}
$phpNamespace = $predisFile->getNamespace($namespace);
if ($phpNamespace === false) {
$phpNamespace = new PhpNamespace($namespace);
$predisFile->addNamespace($phpNamespace);
}
$phpClass = new PhpClass($phpNamespace, $classFile);
}
return $predisFile;
}
public function addNamespace(PhpNamespace $namespace)
{
if (isset($this->namespaces[(string)$namespace])) {
throw new InvalidArgumentException("Duplicated namespace");
}
$this->namespaces[(string)$namespace] = $namespace;
}
public function getNamespaces()
{
return $this->namespaces;
}
public function getNamespace($namespace)
{
if (!isset($this->namespaces[$namespace])) {
return false;
}
return $this->namespaces[$namespace];
}
public function getClassByFQN($classFqn)
{
if (($nsLastPos = strrpos($classFqn, '\\')) !== false) {
$namespace = $this->getNamespace(substr($classFqn, 0, $nsLastPos));
if ($namespace === false) {
return null;
}
$className = substr($classFqn, $nsLastPos + 1);
return $namespace->getClass($className);
}
return null;
}
private function calculateDependencyScores(&$classes, $fqn)
{
if (!isset($classes[$fqn])) {
$classes[$fqn] = 0;
}
$classes[$fqn] += 1;
if (($phpClass = $this->getClassByFQN($fqn)) === null) {
throw new RuntimeException(
"Cannot found the class $fqn which is required by other subclasses. Are you missing a file?"
);
}
foreach ($phpClass->getDependencies() as $fqn) {
$this->calculateDependencyScores($classes, $fqn);
}
}
private function getDependencyScores()
{
$classes = array();
foreach ($this->getNamespaces() as $phpNamespace) {
foreach ($phpNamespace->getClasses() as $phpClass) {
$this->calculateDependencyScores($classes, $phpClass->getFQN());
}
}
return $classes;
}
private function getOrderedNamespaces($dependencyScores)
{
$namespaces = array_fill_keys(array_unique(
array_map(
function ($fqn) { return PhpNamespace::extractName($fqn); },
array_keys($dependencyScores)
)
), 0);
foreach ($dependencyScores as $classFqn => $score) {
$namespaces[PhpNamespace::extractName($classFqn)] += $score;
}
arsort($namespaces);
return array_keys($namespaces);
}
private function getOrderedClasses(PhpNamespace $phpNamespace, $classes)
{
$nsClassesFQNs = array_map(function ($cl) { return $cl->getFQN(); }, $phpNamespace->getClasses());
$nsOrderedClasses = array();
foreach ($nsClassesFQNs as $nsClassFQN) {
$nsOrderedClasses[$nsClassFQN] = $classes[$nsClassFQN];
}
arsort($nsOrderedClasses);
return array_keys($nsOrderedClasses);
}
public function getPhpCode()
{
$buffer = array("<?php\n\n", PhpClass::LICENSE_HEADER, "\n\n");
$classes = $this->getDependencyScores();
$namespaces = $this->getOrderedNamespaces($classes);
foreach ($namespaces as $namespace) {
$phpNamespace = $this->getNamespace($namespace);
// generate namespace directive
$buffer[] = $phpNamespace->getPhpCode();
$buffer[] = "\n";
// generate use directives
$useDirectives = $phpNamespace->getUseDirectives();
if (count($useDirectives) > 0) {
$buffer[] = $useDirectives->getPhpCode();
$buffer[] = "\n";
}
// generate classes bodies
$nsClasses = $this->getOrderedClasses($phpNamespace, $classes);
foreach ($nsClasses as $classFQN) {
$buffer[] = $this->getClassByFQN($classFQN)->getPhpCode();
$buffer[] = "\n\n";
}
$buffer[] = "/* " . str_repeat("-", 75) . " */";
$buffer[] = "\n\n";
}
return implode($buffer);
}
public function saveTo($outputFile)
{
// TODO: add more sanity checks
if ($outputFile === null || $outputFile === '') {
throw new InvalidArgumentException('You must specify a valid output file');
}
file_put_contents($outputFile, $this->getPhpCode());
}
}
class PhpNamespace implements IteratorAggregate
{
private $namespace;
private $classes;
public function __construct($namespace)
{
$this->namespace = $namespace;
$this->classes = array();
$this->useDirectives = new PhpUseDirectives($this);
}
public static function extractName($fqn)
{
$nsSepLast = strrpos($fqn, '\\');
if ($nsSepLast === false) {
return $fqn;
}
$ns = substr($fqn, 0, $nsSepLast);
return $ns !== '' ? $ns : null;
}
public function addClass(PhpClass $class)
{
$this->classes[$class->getName()] = $class;
}
public function getClass($className)
{
if (isset($this->classes[$className])) {
return $this->classes[$className];
}
}
public function getClasses()
{
return array_values($this->classes);
}
public function getIterator()
{
return new \ArrayIterator($this->getClasses());
}
public function getUseDirectives()
{
return $this->useDirectives;
}
public function getPhpCode()
{
return "namespace $this->namespace;\n";
}
public function __toString()
{
return $this->namespace;
}
}
class PhpUseDirectives implements Countable, IteratorAggregate
{
private $use;
private $aliases;
private $reverseAliases;
private $namespace;
public function __construct(PhpNamespace $namespace)
{
$this->namespace = $namespace;
$this->use = array();
$this->aliases = array();
$this->reverseAliases = array();
}
public function add($use, $as = null)
{
if (in_array($use, $this->use)) {
return;
}
$rename = null;
$this->use[] = $use;
$aliasedClassName = $as ?: PhpClass::extractName($use);
if (isset($this->aliases[$aliasedClassName])) {
$parentNs = $this->getParentNamespace();
if ($parentNs && false !== $pos = strrpos($parentNs, '\\')) {
$parentNs = substr($parentNs, $pos);
}
$newAlias = "{$parentNs}_{$aliasedClassName}";
$rename = (object) array(
'namespace' => $this->namespace,
'from' => $aliasedClassName,
'to' => $newAlias,
);
$this->aliases[$newAlias] = $use;
$as = $newAlias;
} else {
$this->aliases[$aliasedClassName] = $use;
}
if ($as !== null) {
$this->reverseAliases[$use] = $as;
}
return $rename;
}
public function getList()
{
return $this->use;
}
public function getIterator()
{
return new \ArrayIterator($this->getList());
}
public function getPhpCode()
{
$reverseAliases = $this->reverseAliases;
$reducer = function ($str, $use) use ($reverseAliases) {
if (isset($reverseAliases[$use])) {
return $str .= "use $use as {$reverseAliases[$use]};\n";
} else {
return $str .= "use $use;\n";
}
};
return array_reduce($this->getList(), $reducer, '');
}
public function getNamespace()
{
return $this->namespace;
}
public function getParentNamespace()
{
if (false !== $pos = strrpos($this->namespace, '\\')) {
return substr($this->namespace, 0, $pos);
}
return '';
}
public function getFQN($className)
{
if (($nsSepFirst = strpos($className, '\\')) === false) {
if (isset($this->aliases[$className])) {
return $this->aliases[$className];
}
return (string)$this->getNamespace() . "\\$className";
}
if ($nsSepFirst != 0) {
throw new InvalidArgumentException("Partially qualified names are not supported");
}
return $className;
}
public function count()
{
return count($this->use);
}
}
class PhpClass
{
const LICENSE_HEADER = <<<LICENSE
/*
* 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.
*/
LICENSE;
private $namespace;
private $file;
private $body;
private $implements;
private $extends;
private $name;
public function __construct(PhpNamespace $namespace, SplFileInfo $classFile)
{
$this->namespace = $namespace;
$this->file = $classFile;
$this->implements = array();
$this->extends = array();
$this->extractData();
$namespace->addClass($this);
}
public static function extractName($fqn)
{
$nsSepLast = strrpos($fqn, '\\');
if ($nsSepLast === false) {
return $fqn;
}
return substr($fqn, $nsSepLast + 1);
}
private function extractData()
{
$renames = array();
$useDirectives = $this->getNamespace()->getUseDirectives();
$useExtractor = function ($m) use ($useDirectives, &$renames) {
array_shift($m);
if (isset($m[1])) {
$m[1] = str_replace(" as ", '', $m[1]);
}
if ($rename = call_user_func_array(array($useDirectives, 'add'), $m)) {
$renames[] = $rename;
}
};
$classBuffer = stream_get_contents(fopen($this->getFile()->getPathname(), 'r'));
$classBuffer = str_replace(self::LICENSE_HEADER, '', $classBuffer);
$classBuffer = preg_replace('/<\?php\s?\\n\s?/', '', $classBuffer);
$classBuffer = preg_replace('/\s?\?>\n?/ms', '', $classBuffer);
$classBuffer = preg_replace('/namespace\s+[\w\d_\\\\]+;\s?/', '', $classBuffer);
$classBuffer = preg_replace_callback('/use\s+([\w\d_\\\\]+)(\s+as\s+.*)?;\s?\n?/', $useExtractor, $classBuffer);
foreach ($renames as $rename) {
$classBuffer = str_replace($rename->from, $rename->to, $classBuffer);
}
$this->body = trim($classBuffer);
$this->extractHierarchy();
}
private function extractHierarchy()
{
$implements = array();
$extends = array();
$extractor = function ($iterator, $callback) {
$className = '';
$iterator->seek($iterator->key() + 1);
while ($iterator->valid()) {
$token = $iterator->current();
if (is_string($token)) {
if (preg_match('/\s?,\s?/', $token)) {
$callback(trim($className));
$className = '';
} else if ($token == '{') {
$callback(trim($className));
return;
}
}
switch ($token[0]) {
case T_NS_SEPARATOR:
$className .= '\\';
break;
case T_STRING:
$className .= $token[1];
break;
case T_IMPLEMENTS:
case T_EXTENDS:
$callback(trim($className));
$iterator->seek($iterator->key() - 1);
return;
}
$iterator->next();
}
};
$tokens = token_get_all("<?php\n" . trim($this->getPhpCode()));
$iterator = new ArrayIterator($tokens);
while ($iterator->valid()) {
$token = $iterator->current();
if (is_string($token)) {
$iterator->next();
continue;
}
switch ($token[0]) {
case T_CLASS:
case T_INTERFACE:
$iterator->seek($iterator->key() + 2);
$tk = $iterator->current();
$this->name = $tk[1];
break;
case T_IMPLEMENTS:
$extractor($iterator, function ($fqn) use (&$implements) {
$implements[] = $fqn;
});
break;
case T_EXTENDS:
$extractor($iterator, function ($fqn) use (&$extends) {
$extends[] = $fqn;
});
break;
}
$iterator->next();
}
$this->implements = $this->guessFQN($implements);
$this->extends = $this->guessFQN($extends);
}
public function guessFQN($classes)
{
$useDirectives = $this->getNamespace()->getUseDirectives();
return array_map(array($useDirectives, 'getFQN'), $classes);
}
public function getImplementedInterfaces($all = false)
{
if ($all) {
return $this->implements;
}
return array_filter(
$this->implements,
function ($cn) { return strpos($cn, 'Predis\\') === 0; }
);
}
public function getExtendedClasses($all = false)
{
if ($all) {
return $this->extemds;
}
return array_filter(
$this->extends,
function ($cn) { return strpos($cn, 'Predis\\') === 0; }
);
}
public function getDependencies($all = false)
{
return array_merge(
$this->getImplementedInterfaces($all),
$this->getExtendedClasses($all)
);
}
public function getNamespace()
{
return $this->namespace;
}
public function getFile()
{
return $this->file;
}
public function getName()
{
return $this->name;
}
public function getFQN()
{
return (string)$this->getNamespace() . '\\' . $this->name;
}
public function getPhpCode()
{
return $this->body;
}
public function __toString()
{
return "class " . $this->getName() . '{ ... }';
}
}
/* -------------------------------------------------------------------------- */
$options = CommandLine::getOptions();
$predisFile = PredisFile::from($options['source'], $options['exclude']);
$predisFile->saveTo($options['output']);

31
vendor/predis/predis/composer.json vendored Normal file
View File

@@ -0,0 +1,31 @@
{
"name": "predis/predis",
"type": "library",
"description": "Flexible and feature-complete Redis client for PHP and HHVM",
"keywords": ["nosql", "redis", "predis"],
"homepage": "http://github.com/nrk/predis",
"license": "MIT",
"support": {
"issues": "https://github.com/nrk/predis/issues"
},
"authors": [
{
"name": "Daniele Alessandri",
"email": "suppakilla@gmail.com",
"homepage": "http://clorophilla.net"
}
],
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"phpunit/phpunit": "~4.8"
},
"suggest": {
"ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol",
"ext-curl": "Allows access to Webdis when paired with phpiredis"
},
"autoload": {
"psr-4": {"Predis\\": "src/"}
}
}

View File

@@ -0,0 +1,117 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
// Developers can implement Predis\Distribution\DistributorInterface to create
// their own distributors used by the client to distribute keys among a cluster
// of servers.
use Predis\Cluster\Distributor\DistributorInterface;
use Predis\Cluster\Hash\HashGeneratorInterface;
use Predis\Cluster\PredisStrategy;
use Predis\Connection\Aggregate\PredisCluster;
class NaiveDistributor implements DistributorInterface, HashGeneratorInterface
{
private $nodes;
private $nodesCount;
public function __construct()
{
$this->nodes = array();
$this->nodesCount = 0;
}
public function add($node, $weight = null)
{
$this->nodes[] = $node;
++$this->nodesCount;
}
public function remove($node)
{
$this->nodes = array_filter($this->nodes, function ($n) use ($node) {
return $n !== $node;
});
$this->nodesCount = count($this->nodes);
}
public function getSlot($hash)
{
return $this->nodesCount > 1 ? abs($hash % $this->nodesCount) : 0;
}
public function getBySlot($slot)
{
return isset($this->nodes[$slot]) ? $this->nodes[$slot] : null;
}
public function getByHash($hash)
{
if (!$this->nodesCount) {
throw new RuntimeException('No connections.');
}
$slot = $this->getSlot($hash);
$node = $this->getBySlot($slot);
return $node;
}
public function get($value)
{
$hash = $this->hash($value);
$node = $this->getByHash($hash);
return $node;
}
public function hash($value)
{
return crc32($value);
}
public function getHashGenerator()
{
return $this;
}
}
$options = array(
'cluster' => function () {
$distributor = new NaiveDistributor();
$strategy = new PredisStrategy($distributor);
$cluster = new PredisCluster($strategy);
return $cluster;
},
);
$client = new Predis\Client($multiple_servers, $options);
for ($i = 0; $i < 100; ++$i) {
$client->set("key:$i", str_pad($i, 4, '0', 0));
$client->get("key:$i");
}
$server1 = $client->getClientFor('first')->info();
$server2 = $client->getClientFor('second')->info();
if (isset($server1['Keyspace'], $server2['Keyspace'])) {
$server1 = $server1['Keyspace'];
$server2 = $server2['Keyspace'];
}
printf("Server '%s' has %d keys while server '%s' has %d keys.\n",
'first', $server1['db15']['keys'], 'second', $server2['db15']['keys']
);

View File

@@ -0,0 +1,92 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
// This is an example of how you can easily extend an existing connection class
// and trace the execution of commands for debugging purposes. This can be quite
// useful as a starting poing to understand how your application interacts with
// Redis.
use Predis\Command\CommandInterface;
use Predis\Connection\StreamConnection;
class SimpleDebuggableConnection extends StreamConnection
{
private $tstart = 0;
private $debugBuffer = array();
public function connect()
{
$this->tstart = microtime(true);
parent::connect();
}
private function storeDebug(CommandInterface $command, $direction)
{
$firtsArg = $command->getArgument(0);
$timestamp = round(microtime(true) - $this->tstart, 4);
$debug = $command->getId();
$debug .= isset($firtsArg) ? " $firtsArg " : ' ';
$debug .= "$direction $this";
$debug .= " [{$timestamp}s]";
$this->debugBuffer[] = $debug;
}
public function writeRequest(CommandInterface $command)
{
parent::writeRequest($command);
$this->storeDebug($command, '->');
}
public function readResponse(CommandInterface $command)
{
$response = parent::readResponse($command);
$this->storeDebug($command, '<-');
return $response;
}
public function getDebugBuffer()
{
return $this->debugBuffer;
}
}
$options = array(
'connections' => array(
'tcp' => 'SimpleDebuggableConnection',
),
);
$client = new Predis\Client($single_server, $options);
$client->set('foo', 'bar');
$client->get('foo');
$client->info();
var_export($client->getConnection()->getDebugBuffer());
/* OUTPUT:
array (
0 => 'SELECT 15 -> 127.0.0.1:6379 [0.0008s]',
1 => 'SELECT 15 <- 127.0.0.1:6379 [0.001s]',
2 => 'SET foo -> 127.0.0.1:6379 [0.001s]',
3 => 'SET foo <- 127.0.0.1:6379 [0.0011s]',
4 => 'GET foo -> 127.0.0.1:6379 [0.0013s]',
5 => 'GET foo <- 127.0.0.1:6379 [0.0015s]',
6 => 'INFO -> 127.0.0.1:6379 [0.0019s]',
7 => 'INFO <- 127.0.0.1:6379 [0.0022s]',
)
*/

View File

@@ -0,0 +1,79 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
// This is a basic example on how to use the Predis\DispatcherLoop class.
//
// To see this example in action you can just use redis-cli and publish some
// messages to the 'events' and 'control' channel, e.g.:
// ./redis-cli
// PUBLISH events first
// PUBLISH events second
// PUBLISH events third
// PUBLISH control terminate_dispatcher
// Create a client and disable r/w timeout on the socket
$client = new Predis\Client($single_server + array('read_write_timeout' => 0));
// Return an initialized PubSub consumer instance from the client.
$pubsub = $client->pubSubLoop();
// Create a dispatcher loop instance and attach a bunch of callbacks.
$dispatcher = new Predis\PubSub\DispatcherLoop($pubsub);
// Demonstrate how to use a callable class as a callback for the dispatcher loop.
class EventsListener implements Countable
{
private $events;
public function __construct()
{
$this->events = array();
}
public function count()
{
return count($this->events);
}
public function getEvents()
{
return $this->events;
}
public function __invoke($payload)
{
$this->events[] = $payload;
}
}
// Attach our callable class to the dispatcher.
$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) {
if ($payload === 'terminate_dispatcher') {
$dispatcher->stop();
}
});
// Run the dispatcher loop until the callback attached to the 'control' channel
// receives 'terminate_dispatcher' as a message.
$dispatcher->run();
// Display our achievements!
echo "We received {$events->count()} messages!", PHP_EOL;
// Say goodbye :-)
$version = redis_version($client->info());
echo "Goodbye from Redis $version!", PHP_EOL;

View File

@@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
$client = new Predis\Client($single_server);
// Plain old SET and GET example...
$client->set('library', 'predis');
$response = $client->get('library');
var_export($response); echo PHP_EOL;
/* OUTPUT: 'predis' */
// Redis has the MSET and MGET commands to set or get multiple keys in one go,
// cases like this Predis accepts arguments for variadic commands both as a list
// of arguments or an array containing all of the keys and/or values.
$mkv = array(
'uid:0001' => '1st user',
'uid:0002' => '2nd user',
'uid:0003' => '3rd user',
);
$client->mset($mkv);
$response = $client->mget(array_keys($mkv));
var_export($response); echo PHP_EOL;
/* OUTPUT:
array (
0 => '1st user',
1 => '2nd user',
2 => '3rd user',
) */
// Predis can also send "raw" commands to Redis. The difference between sending
// commands to Redis the usual way and the "raw" way is that in the latter case
// their arguments are not filtered nor responses coming from Redis are parsed.
$response = $client->executeRaw(array(
'MGET', 'uid:0001', 'uid:0002', 'uid:0003',
));
var_export($response); echo PHP_EOL;
/* OUTPUT:
array (
0 => '1st user',
1 => '2nd user',
2 => '3rd user',
) */

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
// Predis can prefix keys found in commands arguments before sending commands to
// Redis, even for complex commands such as SORT, ZUNIONSTORE and ZINTERSTORE.
// Prefixing keys can be useful to create user-level namespaces for you keyspace
// thus reducing the need for separate logical databases in certain scenarios.
$client = new Predis\Client($single_server, array('prefix' => 'nrk:'));
$client->mset(array('foo' => 'bar', 'lol' => 'wut'));
var_export($client->mget('foo', 'lol'));
/*
array (
0 => 'bar',
1 => 'wut',
)
*/
var_export($client->keys('*'));
/*
array (
0 => 'nrk:foo',
1 => 'nrk:lol',
)
*/

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
// This example will not work with versions of Redis < 2.6.
//
// Additionally to the EVAL command defined in the current development profile,
// the Predis\Command\ScriptCommand class can be used to build an higher level
// abstraction for "scriptable" commands so that they will appear just like any
// other command on the client-side. This is a quick example used to implement
// INCREX.
use Predis\Command\ScriptCommand;
class IncrementExistingKeysBy extends ScriptCommand
{
public function getKeysCount()
{
// Tell Predis to use all the arguments but the last one as arguments
// for KEYS. The last one will be used to populate ARGV.
return -1;
}
public function getScript()
{
return <<<LUA
local cmd, insert = redis.call, table.insert
local increment, results = ARGV[1], { }
for idx, key in ipairs(KEYS) do
if cmd('exists', key) == 1 then
insert(results, idx, cmd('incrby', key, increment))
else
insert(results, idx, false)
end
end
return results
LUA;
}
}
$client = new Predis\Client($single_server, array(
'profile' => function ($options) {
$profile = $options->getDefault('profile');
$profile->defineCommand('increxby', 'IncrementExistingKeysBy');
return $profile;
},
));
$client->mset('foo', 10, 'foobar', 100);
var_export($client->increxby('foo', 'foofoo', 'foobar', 50));
/*
array (
0 => 60,
1 => NULL,
2 => 150,
)
*/

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.
*/
require __DIR__.'/shared.php';
// This is a basic example on how to use the Predis\Monitor\Consumer class. You
// can use redis-cli to send commands to the same Redis instance your client is
// connected to, and then type "ECHO QUIT_MONITOR" in redis-cli when you want to
// exit the monitor loop and terminate this script in a graceful way.
// Create a client and disable r/w timeout on the socket.
$client = new Predis\Client($single_server + array('read_write_timeout' => 0));
// Use only one instance of DateTime, we will update the timestamp later.
$timestamp = new DateTime();
foreach (($monitor = $client->monitor()) as $event) {
$timestamp->setTimestamp((int) $event->timestamp);
// If we notice a ECHO command with the message QUIT_MONITOR, we stop the
// monitor consumer and then break the loop.
if ($event->command === 'ECHO' && $event->arguments === '"QUIT_MONITOR"') {
echo 'Exiting the monitor loop...', PHP_EOL;
$monitor->stop();
break;
}
echo "* Received {$event->command} on DB {$event->database} at {$timestamp->format(DateTime::W3C)}", PHP_EOL;
if (isset($event->arguments)) {
echo " Arguments: {$event->arguments}", PHP_EOL;
}
}
// Say goodbye :-)
$version = redis_version($client->info());
echo "Goodbye from Redis $version!", PHP_EOL;

View File

@@ -0,0 +1,45 @@
<?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';
// When you have a whole set of consecutive commands to send to a redis server,
// you can use a pipeline to dramatically improve performances. Pipelines can
// greatly reduce the effects of network round-trips.
$client = new Predis\Client($single_server);
$responses = $client->pipeline(function ($pipe) {
$pipe->flushdb();
$pipe->incrby('counter', 10);
$pipe->incrby('counter', 30);
$pipe->exists('counter');
$pipe->get('counter');
$pipe->mget('does_not_exist', 'counter');
});
var_export($responses);
/* OUTPUT:
array (
0 => Predis\Response\Status::__set_state(array(
'payload' => 'OK',
)),
1 => 10,
2 => 40,
3 => true,
4 => '40',
5 => array (
0 => NULL,
1 => '40',
),
)
*/

View File

@@ -0,0 +1,59 @@
<?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';
// Starting from Redis 2.0 clients can subscribe and listen for events published
// on certain channels using a Publish/Subscribe (PUB/SUB) approach.
// Create a client and disable r/w timeout on the socket
$client = new Predis\Client($single_server + array('read_write_timeout' => 0));
// Initialize a new pubsub consumer.
$pubsub = $client->pubSubLoop();
// Subscribe to your channels
$pubsub->subscribe('control_channel', 'notifications');
// Start processing the pubsup messages. Open a terminal and use redis-cli
// to push messages to the channels. Examples:
// ./redis-cli PUBLISH notifications "this is a test"
// ./redis-cli PUBLISH control_channel quit_loop
foreach ($pubsub as $message) {
switch ($message->kind) {
case 'subscribe':
echo "Subscribed to {$message->channel}", PHP_EOL;
break;
case 'message':
if ($message->channel == 'control_channel') {
if ($message->payload == 'quit_loop') {
echo 'Aborting pubsub loop...', PHP_EOL;
$pubsub->unsubscribe();
} else {
echo "Received an unrecognized command: {$message->payload}.", PHP_EOL;
}
} else {
echo "Received the following message from {$message->channel}:",
PHP_EOL, " {$message->payload}", PHP_EOL, PHP_EOL;
}
break;
}
}
// Always unset the pubsub consumer instance when you are done! The
// class destructor will take care of cleanups and prevent protocol
// desynchronizations between the client and the server.
unset($pubsub);
// Say goodbye :-)
$version = redis_version($client->info());
echo "Goodbye from Redis $version!", PHP_EOL;

View File

@@ -0,0 +1,99 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
use Predis\Collection\Iterator;
// Starting from Redis 2.8, clients can iterate incrementally over collections
// without blocking the server like it happens when a command such as KEYS is
// executed on a Redis instance storing millions of keys. These commands are:
//
// - SCAN (iterates over the keyspace)
// - SSCAN (iterates over members of a set)
// - ZSCAN (iterates over members and ranks of a sorted set)
// - HSCAN (iterates over fields and values of an hash).
// Predis provides a specialized abstraction for each command based on standard
// SPL iterators making it possible to easily consume SCAN-based iterations in
// your PHP code.
//
// See http://redis.io/commands/scan for more details.
//
// Create a client using `2.8` as a server profile (needs Redis 2.8!)
$client = new Predis\Client($single_server, array('profile' => '2.8'));
// Prepare some keys for our example
$client->del('predis:set', 'predis:zset', 'predis:hash');
for ($i = 0; $i < 5; ++$i) {
$client->sadd('predis:set', "member:$i");
$client->zadd('predis:zset', -$i, "member:$i");
$client->hset('predis:hash', "field:$i", "value:$i");
}
// === Keyspace iterator based on SCAN ===
echo 'Scan the keyspace matching only our prefixed keys:', PHP_EOL;
foreach (new Iterator\Keyspace($client, 'predis:*') as $key) {
echo " - $key", PHP_EOL;
}
/* OUTPUT
Scan the keyspace matching only our prefixed keys:
- predis:zset
- predis:set
- predis:hash
*/
// === Set iterator based on SSCAN ===
echo 'Scan members of `predis:set`:', PHP_EOL;
foreach (new Iterator\SetKey($client, 'predis:set') as $member) {
echo " - $member", PHP_EOL;
}
/* OUTPUT
Scan members of `predis:set`:
- member:1
- member:4
- member:0
- member:3
- member:2
*/
// === Sorted set iterator based on ZSCAN ===
echo 'Scan members and ranks of `predis:zset`:', PHP_EOL;
foreach (new Iterator\SortedSetKey($client, 'predis:zset') as $member => $rank) {
echo " - $member [rank: $rank]", PHP_EOL;
}
/* OUTPUT
Scan members and ranks of `predis:zset`:
- member:4 [rank: -4]
- member:3 [rank: -3]
- member:2 [rank: -2]
- member:1 [rank: -1]
- member:0 [rank: 0]
*/
// === Hash iterator based on HSCAN ===
echo 'Scan fields and values of `predis:hash`:', PHP_EOL;
foreach (new Iterator\HashKey($client, 'predis:hash') as $field => $value) {
echo " - $field => $value", PHP_EOL;
}
/* OUTPUT
Scan fields and values of `predis:hash`:
- field:0 => value:0
- field:1 => value:1
- field:2 => value:2
- field:3 => value:3
- field:4 => value:4
*/

View File

@@ -0,0 +1,85 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
// Predis allows to set Lua scripts as read-only operations for replication.
// This works for both EVAL and EVALSHA and also for the client-side abstraction
// built upon them (Predis\Command\ScriptCommand). This example shows a slightly
// more complex configuration that injects a new script command in the server
// profile used by the new client instance and marks it marks it as a read-only
// operation for replication so that it will be executed on slaves.
use Predis\Command\ScriptCommand;
use Predis\Connection\Aggregate\MasterSlaveReplication;
use Predis\Replication\ReplicationStrategy;
// ------------------------------------------------------------------------- //
// Define a new script command that returns all the fields of a variable number
// of hashes with a single roundtrip.
class HashMultipleGetAll extends ScriptCommand
{
const BODY = <<<LUA
local hashes = {}
for _, key in pairs(KEYS) do
table.insert(hashes, key)
table.insert(hashes, redis.call('hgetall', key))
end
return hashes
LUA;
public function getScript()
{
return self::BODY;
}
}
// ------------------------------------------------------------------------- //
$parameters = array(
'tcp://127.0.0.1:6379/?alias=master',
'tcp://127.0.0.1:6380/?alias=slave',
);
$options = array(
'profile' => function ($options, $option) {
$profile = $options->getDefault($option);
$profile->defineCommand('hmgetall', 'HashMultipleGetAll');
return $profile;
},
'replication' => function () {
$strategy = new ReplicationStrategy();
$strategy->setScriptReadOnly(HashMultipleGetAll::BODY);
$replication = new MasterSlaveReplication($strategy);
return $replication;
},
);
// ------------------------------------------------------------------------- //
$client = new Predis\Client($parameters, $options);
// Execute the following commands on the master server using redis-cli:
// $ ./redis-cli HMSET metavars foo bar hoge piyo
// $ ./redis-cli HMSET servers master host1 slave host2
$hashes = $client->hmgetall('metavars', 'servers');
$replication = $client->getConnection();
$stillOnSlave = $replication->getCurrent() === $replication->getConnectionById('slave');
echo 'Is still on slave? ', $stillOnSlave ? 'YES!' : 'NO!', PHP_EOL;
var_export($hashes);

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'.
*/

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
// Predis supports master / slave replication scenarios where write operations
// are performed on the master server and read operations are executed against
// one of the slaves. The behavior of commands or EVAL scripts can be customized
// at will. As soon as a write operation is performed the client switches to the
// master server for all the subsequent requests (either reads and writes).
//
// This example must be executed using the second Redis server configured as the
// slave of the first one (see the "SLAVEOF" command).
//
$parameters = array(
'tcp://127.0.0.1:6379?database=15&alias=master',
'tcp://127.0.0.1:6380?database=15&alias=slave',
);
$options = array('replication' => true);
$client = new Predis\Client($parameters, $options);
// Read operation.
$exists = $client->exists('foo') ? 'yes' : 'no';
$current = $client->getConnection()->getCurrent()->getParameters();
echo "Does 'foo' exist on {$current->alias}? $exists.", PHP_EOL;
// Write operation.
$client->set('foo', 'bar');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "Now 'foo' has been set to 'bar' on {$current->alias}!", PHP_EOL;
// Read operation.
$bar = $client->get('foo');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "We fetched 'foo' from {$current->alias} and its value is '$bar'.", PHP_EOL;
/* OUTPUT:
Does 'foo' exist on slave? yes.
Now 'foo' has been set to 'bar' on master!
We fetched 'foo' from master and its value is 'bar'.
*/

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
// This example demonstrates how to use Predis to save PHP sessions on Redis.
//
// The value of `session.gc_maxlifetime` in `php.ini` will be used by default as
// the TTL for keys holding session data but this value can be overridden when
// creating the session handler instance using the `gc_maxlifetime` option.
//
// NOTE: this class requires PHP >= 5.4 but can be used on PHP 5.3 if a polyfill
// for SessionHandlerInterface is provided either by you or an external package
// like `symfony/http-foundation`.
//
// See http://www.php.net/class.sessionhandlerinterface.php for more details.
//
if (!interface_exists('SessionHandlerInterface')) {
die('ATTENTION: the session handler implemented by Predis requires PHP >= 5.4.0 '.
"or a polyfill for SessionHandlerInterface provided by an external package.\n");
}
// Instantiate a new client just like you would normally do. Using a prefix for
// keys will effectively prefix all session keys with the specified string.
$client = new Predis\Client($single_server, array('prefix' => 'sessions:'));
// Set `gc_maxlifetime` to specify a time-to-live of 5 seconds for session keys.
$handler = new Predis\Session\Handler($client, array('gc_maxlifetime' => 5));
// Register the session handler.
$handler->register();
// We just set a fixed session ID only for the sake of our example.
session_id('example_session_id');
session_start();
if (isset($_SESSION['foo'])) {
echo "Session has `foo` set to {$_SESSION['foo']}", PHP_EOL;
} else {
$_SESSION['foo'] = $value = mt_rand();
echo "Empty session, `foo` has been set with $value", PHP_EOL;
}

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.
*/
require __DIR__.'/../autoload.php';
function redis_version($info)
{
if (isset($info['Server']['redis_version'])) {
return $info['Server']['redis_version'];
} elseif (isset($info['redis_version'])) {
return $info['redis_version'];
} else {
return 'unknown version';
}
}
$single_server = array(
'host' => '127.0.0.1',
'port' => 6379,
'database' => 15,
);
$multiple_servers = array(
array(
'host' => '127.0.0.1',
'port' => 6379,
'database' => 15,
'alias' => 'first',
),
array(
'host' => '127.0.0.1',
'port' => 6380,
'database' => 15,
'alias' => 'second',
),
);

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
// This is an implementation of an atomic client-side ZPOP using the support for
// check-and-set (CAS) operations with MULTI/EXEC transactions, as described in
// "WATCH explained" from http://redis.io/topics/transactions
//
// First, populate your database with a tiny sample data set:
//
// ./redis-cli
// SELECT 15
// ZADD zset 1 a 2 b 3 c
//
// Then execute this script four times and see its output.
//
function zpop($client, $key)
{
$element = null;
$options = array(
'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.
);
$client->transaction($options, function ($tx) use ($key, &$element) {
@list($element) = $tx->zrange($key, 0, 0);
if (isset($element)) {
$tx->multi(); // With CAS, MULTI *must* be explicitly invoked.
$tx->zrem($key, $element);
}
});
return $element;
}
$client = new Predis\Client($single_server);
$zpopped = zpop($client, 'zset');
echo isset($zpopped) ? "ZPOPed $zpopped" : 'Nothing to ZPOP!', PHP_EOL;

36
vendor/predis/predis/package.ini vendored Normal file
View File

@@ -0,0 +1,36 @@
; This file is meant to be used with Onion http://c9s.github.com/Onion/
; For instructions on how to build a PEAR package of Predis please follow
; the instructions at this URL:
;
; https://github.com/c9s/Onion#a-quick-tutorial-for-building-pear-package
;
[package]
name = "Predis"
desc = "Flexible and feature-complete Redis client for PHP and HHVM"
homepage = "http://github.com/nrk/predis"
license = "MIT"
version = "1.1.1"
stability = "stable"
channel = "pear.nrk.io"
author = "Daniele Alessandri \"nrk\" <suppakilla@gmail.com>"
[require]
php = ">= 5.3.9"
pearinstaller = "1.4.1"
[roles]
*.xml.dist = test
*.md = doc
LICENSE = doc
[optional phpiredis]
hint = "Add support for faster protocol handling with phpiredis"
extensions[] = socket
extensions[] = phpiredis
[optional webdis]
hint = "Add support for Webdis"
extensions[] = curl
extensions[] = phpiredis

62
vendor/predis/predis/src/Autoloader.php vendored Normal file
View File

@@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis;
/**
* Implements a lightweight PSR-0 compliant autoloader for Predis.
*
* @author Eric Naeseth <eric@thumbtack.com>
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class Autoloader
{
private $directory;
private $prefix;
private $prefixLength;
/**
* @param string $baseDirectory Base directory where the source files are located.
*/
public function __construct($baseDirectory = __DIR__)
{
$this->directory = $baseDirectory;
$this->prefix = __NAMESPACE__.'\\';
$this->prefixLength = strlen($this->prefix);
}
/**
* Registers the autoloader class with the PHP SPL autoloader.
*
* @param bool $prepend Prepend the autoloader on the stack instead of appending it.
*/
public static function register($prepend = false)
{
spl_autoload_register(array(new self(), 'autoload'), true, $prepend);
}
/**
* Loads a class from a file using its fully qualified name.
*
* @param string $className Fully qualified name of a class.
*/
public function autoload($className)
{
if (0 === strpos($className, $this->prefix)) {
$parts = explode('\\', substr($className, $this->prefixLength));
$filepath = $this->directory.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php';
if (is_file($filepath)) {
require $filepath;
}
}
}
}

547
vendor/predis/predis/src/Client.php vendored Normal file
View File

@@ -0,0 +1,547 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis;
use Predis\Command\CommandInterface;
use Predis\Command\RawCommand;
use Predis\Command\ScriptCommand;
use Predis\Configuration\Options;
use Predis\Configuration\OptionsInterface;
use Predis\Connection\AggregateConnectionInterface;
use Predis\Connection\ConnectionInterface;
use Predis\Connection\ParametersInterface;
use Predis\Monitor\Consumer as MonitorConsumer;
use Predis\Pipeline\Pipeline;
use Predis\PubSub\Consumer as PubSubConsumer;
use Predis\Response\ErrorInterface as ErrorResponseInterface;
use Predis\Response\ResponseInterface;
use Predis\Response\ServerException;
use Predis\Transaction\MultiExec as MultiExecTransaction;
/**
* Client class used for connecting and executing commands on Redis.
*
* This is the main high-level abstraction of Predis upon which various other
* abstractions are built. Internally it aggregates various other classes each
* one with its own responsibility and scope.
*
* {@inheritdoc}
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class Client implements ClientInterface, \IteratorAggregate
{
const VERSION = '1.1.1';
protected $connection;
protected $options;
private $profile;
/**
* @param mixed $parameters Connection parameters for one or more servers.
* @param mixed $options Options to configure some behaviours of the client.
*/
public function __construct($parameters = null, $options = null)
{
$this->options = $this->createOptions($options ?: array());
$this->connection = $this->createConnection($parameters ?: array());
$this->profile = $this->options->profile;
}
/**
* Creates a new instance of Predis\Configuration\Options from different
* types of arguments or simply returns the passed argument if it is an
* instance of Predis\Configuration\OptionsInterface.
*
* @param mixed $options Client options.
*
* @throws \InvalidArgumentException
*
* @return OptionsInterface
*/
protected function createOptions($options)
{
if (is_array($options)) {
return new Options($options);
}
if ($options instanceof OptionsInterface) {
return $options;
}
throw new \InvalidArgumentException('Invalid type for client options.');
}
/**
* Creates single or aggregate connections from different types of arguments
* (string, array) or returns the passed argument if it is an instance of a
* class implementing Predis\Connection\ConnectionInterface.
*
* Accepted types for connection parameters are:
*
* - Instance of Predis\Connection\ConnectionInterface.
* - Instance of Predis\Connection\ParametersInterface.
* - Array
* - String
* - Callable
*
* @param mixed $parameters Connection parameters or connection instance.
*
* @throws \InvalidArgumentException
*
* @return ConnectionInterface
*/
protected function createConnection($parameters)
{
if ($parameters instanceof ConnectionInterface) {
return $parameters;
}
if ($parameters instanceof ParametersInterface || is_string($parameters)) {
return $this->options->connections->create($parameters);
}
if (is_array($parameters)) {
if (!isset($parameters[0])) {
return $this->options->connections->create($parameters);
}
$options = $this->options;
if ($options->defined('aggregate')) {
$initializer = $this->getConnectionInitializerWrapper($options->aggregate);
$connection = $initializer($parameters, $options);
} elseif ($options->defined('replication')) {
$replication = $options->replication;
if ($replication instanceof AggregateConnectionInterface) {
$connection = $replication;
$options->connections->aggregate($connection, $parameters);
} else {
$initializer = $this->getConnectionInitializerWrapper($replication);
$connection = $initializer($parameters, $options);
}
} else {
$connection = $options->cluster;
$options->connections->aggregate($connection, $parameters);
}
return $connection;
}
if (is_callable($parameters)) {
$initializer = $this->getConnectionInitializerWrapper($parameters);
$connection = $initializer($this->options);
return $connection;
}
throw new \InvalidArgumentException('Invalid type for connection parameters.');
}
/**
* Wraps a callable to make sure that its returned value represents a valid
* connection type.
*
* @param mixed $callable
*
* @return \Closure
*/
protected function getConnectionInitializerWrapper($callable)
{
return function () use ($callable) {
$connection = call_user_func_array($callable, func_get_args());
if (!$connection instanceof ConnectionInterface) {
throw new \UnexpectedValueException(
'The callable connection initializer returned an invalid type.'
);
}
return $connection;
};
}
/**
* {@inheritdoc}
*/
public function getProfile()
{
return $this->profile;
}
/**
* {@inheritdoc}
*/
public function getOptions()
{
return $this->options;
}
/**
* 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.
*
* @param string $connectionID Identifier of a connection.
*
* @throws \InvalidArgumentException
*
* @return Client
*/
public function getClientFor($connectionID)
{
if (!$connection = $this->getConnectionById($connectionID)) {
throw new \InvalidArgumentException("Invalid connection ID: $connectionID.");
}
return new static($connection, $this->options);
}
/**
* Opens the underlying connection and connects to the server.
*/
public function connect()
{
$this->connection->connect();
}
/**
* Closes the underlying connection and disconnects from the server.
*/
public function disconnect()
{
$this->connection->disconnect();
}
/**
* Closes the underlying connection and disconnects from the server.
*
* This is the same as `Client::disconnect()` as it does not actually send
* the `QUIT` command to Redis, but simply closes the connection.
*/
public function quit()
{
$this->disconnect();
}
/**
* Returns the current state of the underlying connection.
*
* @return bool
*/
public function isConnected()
{
return $this->connection->isConnected();
}
/**
* {@inheritdoc}
*/
public function getConnection()
{
return $this->connection;
}
/**
* Retrieves the specified connection from the aggregate connection when the
* client is in cluster or replication mode.
*
* @param string $connectionID Index or alias of the single connection.
*
* @throws NotSupportedException
*
* @return Connection\NodeConnectionInterface
*/
public function getConnectionById($connectionID)
{
if (!$this->connection instanceof AggregateConnectionInterface) {
throw new NotSupportedException(
'Retrieving connections by ID is supported only by aggregate connections.'
);
}
return $this->connection->getConnectionById($connectionID);
}
/**
* Executes a command without filtering its arguments, parsing the response,
* applying any prefix to keys or throwing exceptions on Redis errors even
* regardless of client options.
*
* 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.
* @param bool $error Set to TRUE when Redis returned an error response.
*
* @return mixed
*/
public function executeRaw(array $arguments, &$error = null)
{
$error = false;
$response = $this->connection->executeCommand(
new RawCommand($arguments)
);
if ($response instanceof ResponseInterface) {
if ($response instanceof ErrorResponseInterface) {
$error = true;
}
return (string) $response;
}
return $response;
}
/**
* {@inheritdoc}
*/
public function __call($commandID, $arguments)
{
return $this->executeCommand(
$this->createCommand($commandID, $arguments)
);
}
/**
* {@inheritdoc}
*/
public function createCommand($commandID, $arguments = array())
{
return $this->profile->createCommand($commandID, $arguments);
}
/**
* {@inheritdoc}
*/
public function executeCommand(CommandInterface $command)
{
$response = $this->connection->executeCommand($command);
if ($response instanceof ResponseInterface) {
if ($response instanceof ErrorResponseInterface) {
$response = $this->onErrorResponse($command, $response);
}
return $response;
}
return $command->parseResponse($response);
}
/**
* Handles -ERR responses returned by Redis.
*
* @param CommandInterface $command Redis command that generated the error.
* @param ErrorResponseInterface $response Instance of the error response.
*
* @throws ServerException
*
* @return mixed
*/
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);
if (!$response instanceof ResponseInterface) {
$response = $command->parseResponse($response);
}
return $response;
}
if ($this->options->exceptions) {
throw new ServerException($response->getMessage());
}
return $response;
}
/**
* Executes the specified initializer method on `$this` by adjusting the
* actual invokation depending on the arity (0, 1 or 2 arguments). This is
* simply an utility method to create Redis contexts instances since they
* follow a common initialization path.
*
* @param string $initializer Method name.
* @param array $argv Arguments for the method.
*
* @return mixed
*/
private function sharedContextFactory($initializer, $argv = null)
{
switch (count($argv)) {
case 0:
return $this->$initializer();
case 1:
return is_array($argv[0])
? $this->$initializer($argv[0])
: $this->$initializer(null, $argv[0]);
case 2:
list($arg0, $arg1) = $argv;
return $this->$initializer($arg0, $arg1);
default:
return $this->$initializer($this, $argv);
}
}
/**
* Creates a new pipeline context and returns it, or returns the results of
* a pipeline executed inside the optionally provided callable object.
*
* @param mixed ... Array of options, a callable for execution, or both.
*
* @return Pipeline|array
*/
public function pipeline(/* arguments */)
{
return $this->sharedContextFactory('createPipeline', func_get_args());
}
/**
* Actual pipeline context initializer method.
*
* @param array $options Options for the context.
* @param mixed $callable Optional callable used to execute the context.
*
* @return Pipeline|array
*/
protected function createPipeline(array $options = null, $callable = null)
{
if (isset($options['atomic']) && $options['atomic']) {
$class = 'Predis\Pipeline\Atomic';
} elseif (isset($options['fire-and-forget']) && $options['fire-and-forget']) {
$class = 'Predis\Pipeline\FireAndForget';
} else {
$class = 'Predis\Pipeline\Pipeline';
}
/*
* @var ClientContextInterface
*/
$pipeline = new $class($this);
if (isset($callable)) {
return $pipeline->execute($callable);
}
return $pipeline;
}
/**
* Creates a new transaction context and returns it, or returns the results
* of a transaction executed inside the optionally provided callable object.
*
* @param mixed ... Array of options, a callable for execution, or both.
*
* @return MultiExecTransaction|array
*/
public function transaction(/* arguments */)
{
return $this->sharedContextFactory('createTransaction', func_get_args());
}
/**
* Actual transaction context initializer method.
*
* @param array $options Options for the context.
* @param mixed $callable Optional callable used to execute the context.
*
* @return MultiExecTransaction|array
*/
protected function createTransaction(array $options = null, $callable = null)
{
$transaction = new MultiExecTransaction($this, $options);
if (isset($callable)) {
return $transaction->execute($callable);
}
return $transaction;
}
/**
* 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.
*
* @return PubSubConsumer|null
*/
public function pubSubLoop(/* arguments */)
{
return $this->sharedContextFactory('createPubSub', func_get_args());
}
/**
* Actual publish/subscribe context initializer method.
*
* @param array $options Options for the context.
* @param mixed $callable Optional callable used to execute the context.
*
* @return PubSubConsumer|null
*/
protected function createPubSub(array $options = null, $callable = null)
{
$pubsub = new PubSubConsumer($this, $options);
if (!isset($callable)) {
return $pubsub;
}
foreach ($pubsub as $message) {
if (call_user_func($callable, $pubsub, $message) === false) {
$pubsub->stop();
}
}
}
/**
* Creates a new monitor consumer and returns it.
*
* @return MonitorConsumer
*/
public function monitor()
{
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);
}
}

View File

@@ -0,0 +1,198 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis;
use Predis\Command\CommandInterface;
/**
* Interface defining a client-side context such as a pipeline or transaction.
*
* @method $this del(array $keys)
* @method $this dump($key)
* @method $this exists($key)
* @method $this expire($key, $seconds)
* @method $this expireat($key, $timestamp)
* @method $this keys($pattern)
* @method $this move($key, $db)
* @method $this object($subcommand, $key)
* @method $this persist($key)
* @method $this pexpire($key, $milliseconds)
* @method $this pexpireat($key, $timestamp)
* @method $this pttl($key)
* @method $this randomkey()
* @method $this rename($key, $target)
* @method $this renamenx($key, $target)
* @method $this scan($cursor, array $options = null)
* @method $this sort($key, array $options = null)
* @method $this ttl($key)
* @method $this type($key)
* @method $this append($key, $value)
* @method $this bitcount($key, $start = null, $end = null)
* @method $this bitop($operation, $destkey, $key)
* @method $this bitfield($key, $subcommand, ...$subcommandArg)
* @method $this decr($key)
* @method $this decrby($key, $decrement)
* @method $this get($key)
* @method $this getbit($key, $offset)
* @method $this getrange($key, $start, $end)
* @method $this getset($key, $value)
* @method $this incr($key)
* @method $this incrby($key, $increment)
* @method $this incrbyfloat($key, $increment)
* @method $this mget(array $keys)
* @method $this mset(array $dictionary)
* @method $this msetnx(array $dictionary)
* @method $this psetex($key, $milliseconds, $value)
* @method $this set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
* @method $this setbit($key, $offset, $value)
* @method $this setex($key, $seconds, $value)
* @method $this setnx($key, $value)
* @method $this setrange($key, $offset, $value)
* @method $this strlen($key)
* @method $this hdel($key, array $fields)
* @method $this hexists($key, $field)
* @method $this hget($key, $field)
* @method $this hgetall($key)
* @method $this hincrby($key, $field, $increment)
* @method $this hincrbyfloat($key, $field, $increment)
* @method $this hkeys($key)
* @method $this hlen($key)
* @method $this hmget($key, array $fields)
* @method $this hmset($key, array $dictionary)
* @method $this hscan($key, $cursor, array $options = null)
* @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)
* @method $this lindex($key, $index)
* @method $this linsert($key, $whence, $pivot, $value)
* @method $this llen($key)
* @method $this lpop($key)
* @method $this lpush($key, array $values)
* @method $this lpushx($key, $value)
* @method $this lrange($key, $start, $stop)
* @method $this lrem($key, $count, $value)
* @method $this lset($key, $index, $value)
* @method $this ltrim($key, $start, $stop)
* @method $this rpop($key)
* @method $this rpoplpush($source, $destination)
* @method $this rpush($key, array $values)
* @method $this rpushx($key, $value)
* @method $this sadd($key, array $members)
* @method $this scard($key)
* @method $this sdiff(array $keys)
* @method $this sdiffstore($destination, array $keys)
* @method $this sinter(array $keys)
* @method $this sinterstore($destination, array $keys)
* @method $this sismember($key, $member)
* @method $this smembers($key)
* @method $this smove($source, $destination, $member)
* @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)
* @method $this sunion(array $keys)
* @method $this sunionstore($destination, array $keys)
* @method $this zadd($key, array $membersAndScoresDictionary)
* @method $this zcard($key)
* @method $this zcount($key, $min, $max)
* @method $this zincrby($key, $increment, $member)
* @method $this zinterstore($destination, array $keys, array $options = null)
* @method $this zrange($key, $start, $stop, array $options = null)
* @method $this zrangebyscore($key, $min, $max, array $options = null)
* @method $this zrank($key, $member)
* @method $this zrem($key, $member)
* @method $this zremrangebyrank($key, $start, $stop)
* @method $this zremrangebyscore($key, $min, $max)
* @method $this zrevrange($key, $start, $stop, array $options = null)
* @method $this zrevrangebyscore($key, $min, $max, array $options = null)
* @method $this zrevrank($key, $member)
* @method $this zunionstore($destination, array $keys, array $options = null)
* @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)
* @method $this pfmerge($destinationKey, array $sourceKeys)
* @method $this pfcount(array $keys)
* @method $this pubsub($subcommand, $argument)
* @method $this publish($channel, $message)
* @method $this discard()
* @method $this exec()
* @method $this multi()
* @method $this unwatch()
* @method $this watch($key)
* @method $this eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
* @method $this evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
* @method $this script($subcommand, $argument = null)
* @method $this auth($password)
* @method $this echo($message)
* @method $this ping($message = null)
* @method $this select($database)
* @method $this bgrewriteaof()
* @method $this bgsave()
* @method $this client($subcommand, $argument = null)
* @method $this config($subcommand, $argument = null)
* @method $this dbsize()
* @method $this flushall()
* @method $this flushdb()
* @method $this info($section = null)
* @method $this lastsave()
* @method $this save()
* @method $this slaveof($host, $port)
* @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>
*/
interface ClientContextInterface
{
/**
* Sends the specified command instance to Redis.
*
* @param CommandInterface $command Command instance.
*
* @return mixed
*/
public function executeCommand(CommandInterface $command);
/**
* Sends the specified command with its arguments to Redis.
*
* @param string $method Command ID.
* @param array $arguments Arguments for the command.
*
* @return mixed
*/
public function __call($method, $arguments);
/**
* Starts the execution of the context.
*
* @param mixed $callable Optional callback for execution.
*
* @return array
*/
public function execute($callable = null);
}

View File

@@ -0,0 +1,21 @@
<?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;
/**
* Exception class that identifies client-side errors.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ClientException extends PredisException
{
}

View File

@@ -0,0 +1,239 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis;
use Predis\Command\CommandInterface;
use Predis\Configuration\OptionsInterface;
use Predis\Connection\ConnectionInterface;
use Predis\Profile\ProfileInterface;
/**
* Interface defining a client able to execute commands against Redis.
*
* All the commands exposed by the client generally have the same signature as
* described by the Redis documentation, but some of them offer an additional
* and more friendly interface to ease programming which is described in the
* following list of methods:
*
* @method int del(array $keys)
* @method string dump($key)
* @method int exists($key)
* @method int expire($key, $seconds)
* @method int expireat($key, $timestamp)
* @method array keys($pattern)
* @method int move($key, $db)
* @method mixed object($subcommand, $key)
* @method int persist($key)
* @method int pexpire($key, $milliseconds)
* @method int pexpireat($key, $timestamp)
* @method int pttl($key)
* @method string randomkey()
* @method mixed rename($key, $target)
* @method int renamenx($key, $target)
* @method array scan($cursor, array $options = null)
* @method array sort($key, array $options = null)
* @method int ttl($key)
* @method mixed type($key)
* @method int append($key, $value)
* @method int bitcount($key, $start = null, $end = null)
* @method int bitop($operation, $destkey, $key)
* @method array bitfield($key, $subcommand, ...$subcommandArg)
* @method int decr($key)
* @method int decrby($key, $decrement)
* @method string get($key)
* @method int getbit($key, $offset)
* @method string getrange($key, $start, $end)
* @method string getset($key, $value)
* @method int incr($key)
* @method int incrby($key, $increment)
* @method string incrbyfloat($key, $increment)
* @method array mget(array $keys)
* @method mixed mset(array $dictionary)
* @method int msetnx(array $dictionary)
* @method mixed psetex($key, $milliseconds, $value)
* @method mixed set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
* @method int setbit($key, $offset, $value)
* @method int setex($key, $seconds, $value)
* @method int setnx($key, $value)
* @method int setrange($key, $offset, $value)
* @method int strlen($key)
* @method int hdel($key, array $fields)
* @method int hexists($key, $field)
* @method string hget($key, $field)
* @method array hgetall($key)
* @method int hincrby($key, $field, $increment)
* @method string hincrbyfloat($key, $field, $increment)
* @method array hkeys($key)
* @method int hlen($key)
* @method array hmget($key, array $fields)
* @method mixed hmset($key, array $dictionary)
* @method array hscan($key, $cursor, array $options = null)
* @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)
* @method string lindex($key, $index)
* @method int linsert($key, $whence, $pivot, $value)
* @method int llen($key)
* @method string lpop($key)
* @method int lpush($key, array $values)
* @method int lpushx($key, $value)
* @method array lrange($key, $start, $stop)
* @method int lrem($key, $count, $value)
* @method mixed lset($key, $index, $value)
* @method mixed ltrim($key, $start, $stop)
* @method string rpop($key)
* @method string rpoplpush($source, $destination)
* @method int rpush($key, array $values)
* @method int rpushx($key, $value)
* @method int sadd($key, array $members)
* @method int scard($key)
* @method array sdiff(array $keys)
* @method int sdiffstore($destination, array $keys)
* @method array sinter(array $keys)
* @method int sinterstore($destination, array $keys)
* @method int sismember($key, $member)
* @method array smembers($key)
* @method int smove($source, $destination, $member)
* @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)
* @method array sunion(array $keys)
* @method int sunionstore($destination, array $keys)
* @method int zadd($key, array $membersAndScoresDictionary)
* @method int zcard($key)
* @method string zcount($key, $min, $max)
* @method string zincrby($key, $increment, $member)
* @method int zinterstore($destination, array $keys, array $options = null)
* @method array zrange($key, $start, $stop, array $options = null)
* @method array zrangebyscore($key, $min, $max, array $options = null)
* @method int zrank($key, $member)
* @method int zrem($key, $member)
* @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, $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)
* @method mixed pfmerge($destinationKey, array $sourceKeys)
* @method int pfcount(array $keys)
* @method mixed pubsub($subcommand, $argument)
* @method int publish($channel, $message)
* @method mixed discard()
* @method array exec()
* @method mixed multi()
* @method mixed unwatch()
* @method mixed watch($key)
* @method mixed eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
* @method mixed evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
* @method mixed script($subcommand, $argument = null)
* @method mixed auth($password)
* @method string echo($message)
* @method mixed ping($message = null)
* @method mixed select($database)
* @method mixed bgrewriteaof()
* @method mixed bgsave()
* @method mixed client($subcommand, $argument = null)
* @method mixed config($subcommand, $argument = null)
* @method int dbsize()
* @method mixed flushall()
* @method mixed flushdb()
* @method array info($section = null)
* @method int lastsave()
* @method mixed save()
* @method mixed slaveof($host, $port)
* @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.
*
* @return ProfileInterface
*/
public function getProfile();
/**
* Returns the client options specified upon initialization.
*
* @return OptionsInterface
*/
public function getOptions();
/**
* Opens the underlying connection to the server.
*/
public function connect();
/**
* Closes the underlying connection from the server.
*/
public function disconnect();
/**
* Returns the underlying connection instance.
*
* @return ConnectionInterface
*/
public function getConnection();
/**
* Creates a new instance of the specified Redis command.
*
* @param string $method Command ID.
* @param array $arguments Arguments for the command.
*
* @return CommandInterface
*/
public function createCommand($method, $arguments = array());
/**
* Executes the specified Redis command.
*
* @param CommandInterface $command Command instance.
*
* @return mixed
*/
public function executeCommand(CommandInterface $command);
/**
* Creates a Redis command with the specified arguments and sends a request
* to the server.
*
* @param string $method Command ID.
* @param array $arguments Arguments for the command.
*
* @return mixed
*/
public function __call($method, $arguments);
}

View File

@@ -0,0 +1,469 @@
<?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\Cluster;
use Predis\Command\CommandInterface;
use Predis\Command\ScriptCommand;
/**
* Common class implementing the logic needed to support clustering strategies.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
abstract class ClusterStrategy implements StrategyInterface
{
protected $commands;
/**
*
*/
public function __construct()
{
$this->commands = $this->getDefaultCommands();
}
/**
* Returns the default map of supported commands with their handlers.
*
* @return array
*/
protected function getDefaultCommands()
{
$getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument');
$getKeyFromAllArguments = array($this, 'getKeyFromAllArguments');
return array(
/* commands operating on the key space */
'EXISTS' => $getKeyFromAllArguments,
'DEL' => $getKeyFromAllArguments,
'TYPE' => $getKeyFromFirstArgument,
'EXPIRE' => $getKeyFromFirstArgument,
'EXPIREAT' => $getKeyFromFirstArgument,
'PERSIST' => $getKeyFromFirstArgument,
'PEXPIRE' => $getKeyFromFirstArgument,
'PEXPIREAT' => $getKeyFromFirstArgument,
'TTL' => $getKeyFromFirstArgument,
'PTTL' => $getKeyFromFirstArgument,
'SORT' => array($this, 'getKeyFromSortCommand'),
'DUMP' => $getKeyFromFirstArgument,
'RESTORE' => $getKeyFromFirstArgument,
/* commands operating on string values */
'APPEND' => $getKeyFromFirstArgument,
'DECR' => $getKeyFromFirstArgument,
'DECRBY' => $getKeyFromFirstArgument,
'GET' => $getKeyFromFirstArgument,
'GETBIT' => $getKeyFromFirstArgument,
'MGET' => $getKeyFromAllArguments,
'SET' => $getKeyFromFirstArgument,
'GETRANGE' => $getKeyFromFirstArgument,
'GETSET' => $getKeyFromFirstArgument,
'INCR' => $getKeyFromFirstArgument,
'INCRBY' => $getKeyFromFirstArgument,
'INCRBYFLOAT' => $getKeyFromFirstArgument,
'SETBIT' => $getKeyFromFirstArgument,
'SETEX' => $getKeyFromFirstArgument,
'MSET' => array($this, 'getKeyFromInterleavedArguments'),
'MSETNX' => array($this, 'getKeyFromInterleavedArguments'),
'SETNX' => $getKeyFromFirstArgument,
'SETRANGE' => $getKeyFromFirstArgument,
'STRLEN' => $getKeyFromFirstArgument,
'SUBSTR' => $getKeyFromFirstArgument,
'BITOP' => array($this, 'getKeyFromBitOp'),
'BITCOUNT' => $getKeyFromFirstArgument,
'BITFIELD' => $getKeyFromFirstArgument,
/* commands operating on lists */
'LINSERT' => $getKeyFromFirstArgument,
'LINDEX' => $getKeyFromFirstArgument,
'LLEN' => $getKeyFromFirstArgument,
'LPOP' => $getKeyFromFirstArgument,
'RPOP' => $getKeyFromFirstArgument,
'RPOPLPUSH' => $getKeyFromAllArguments,
'BLPOP' => array($this, 'getKeyFromBlockingListCommands'),
'BRPOP' => array($this, 'getKeyFromBlockingListCommands'),
'BRPOPLPUSH' => array($this, 'getKeyFromBlockingListCommands'),
'LPUSH' => $getKeyFromFirstArgument,
'LPUSHX' => $getKeyFromFirstArgument,
'RPUSH' => $getKeyFromFirstArgument,
'RPUSHX' => $getKeyFromFirstArgument,
'LRANGE' => $getKeyFromFirstArgument,
'LREM' => $getKeyFromFirstArgument,
'LSET' => $getKeyFromFirstArgument,
'LTRIM' => $getKeyFromFirstArgument,
/* commands operating on sets */
'SADD' => $getKeyFromFirstArgument,
'SCARD' => $getKeyFromFirstArgument,
'SDIFF' => $getKeyFromAllArguments,
'SDIFFSTORE' => $getKeyFromAllArguments,
'SINTER' => $getKeyFromAllArguments,
'SINTERSTORE' => $getKeyFromAllArguments,
'SUNION' => $getKeyFromAllArguments,
'SUNIONSTORE' => $getKeyFromAllArguments,
'SISMEMBER' => $getKeyFromFirstArgument,
'SMEMBERS' => $getKeyFromFirstArgument,
'SSCAN' => $getKeyFromFirstArgument,
'SPOP' => $getKeyFromFirstArgument,
'SRANDMEMBER' => $getKeyFromFirstArgument,
'SREM' => $getKeyFromFirstArgument,
/* commands operating on sorted sets */
'ZADD' => $getKeyFromFirstArgument,
'ZCARD' => $getKeyFromFirstArgument,
'ZCOUNT' => $getKeyFromFirstArgument,
'ZINCRBY' => $getKeyFromFirstArgument,
'ZINTERSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
'ZRANGE' => $getKeyFromFirstArgument,
'ZRANGEBYSCORE' => $getKeyFromFirstArgument,
'ZRANK' => $getKeyFromFirstArgument,
'ZREM' => $getKeyFromFirstArgument,
'ZREMRANGEBYRANK' => $getKeyFromFirstArgument,
'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument,
'ZREVRANGE' => $getKeyFromFirstArgument,
'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument,
'ZREVRANK' => $getKeyFromFirstArgument,
'ZSCORE' => $getKeyFromFirstArgument,
'ZUNIONSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
'ZSCAN' => $getKeyFromFirstArgument,
'ZLEXCOUNT' => $getKeyFromFirstArgument,
'ZRANGEBYLEX' => $getKeyFromFirstArgument,
'ZREMRANGEBYLEX' => $getKeyFromFirstArgument,
'ZREVRANGEBYLEX' => $getKeyFromFirstArgument,
/* commands operating on hashes */
'HDEL' => $getKeyFromFirstArgument,
'HEXISTS' => $getKeyFromFirstArgument,
'HGET' => $getKeyFromFirstArgument,
'HGETALL' => $getKeyFromFirstArgument,
'HMGET' => $getKeyFromFirstArgument,
'HMSET' => $getKeyFromFirstArgument,
'HINCRBY' => $getKeyFromFirstArgument,
'HINCRBYFLOAT' => $getKeyFromFirstArgument,
'HKEYS' => $getKeyFromFirstArgument,
'HLEN' => $getKeyFromFirstArgument,
'HSET' => $getKeyFromFirstArgument,
'HSETNX' => $getKeyFromFirstArgument,
'HVALS' => $getKeyFromFirstArgument,
'HSCAN' => $getKeyFromFirstArgument,
'HSTRLEN' => $getKeyFromFirstArgument,
/* commands operating on HyperLogLog */
'PFADD' => $getKeyFromFirstArgument,
'PFCOUNT' => $getKeyFromAllArguments,
'PFMERGE' => $getKeyFromAllArguments,
/* 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'),
);
}
/**
* Returns the list of IDs for the supported commands.
*
* @return array
*/
public function getSupportedCommands()
{
return array_keys($this->commands);
}
/**
* Sets an handler for the specified command ID.
*
* The signature of the callback must have a single parameter of type
* Predis\Command\CommandInterface.
*
* When the callback argument is omitted or NULL, the previously associated
* handler for the specified command ID is removed.
*
* @param string $commandID Command ID.
* @param mixed $callback A valid callable object, or NULL to unset the handler.
*
* @throws \InvalidArgumentException
*/
public function setCommandHandler($commandID, $callback = null)
{
$commandID = strtoupper($commandID);
if (!isset($callback)) {
unset($this->commands[$commandID]);
return;
}
if (!is_callable($callback)) {
throw new \InvalidArgumentException(
'The argument must be a callable object or NULL.'
);
}
$this->commands[$commandID] = $callback;
}
/**
* Extracts the key from the first argument of a command instance.
*
* @param CommandInterface $command Command instance.
*
* @return string
*/
protected function getKeyFromFirstArgument(CommandInterface $command)
{
return $command->getArgument(0);
}
/**
* Extracts the key from a command with multiple keys only when all keys in
* the arguments array produce the same hash.
*
* @param CommandInterface $command Command instance.
*
* @return string|null
*/
protected function getKeyFromAllArguments(CommandInterface $command)
{
$arguments = $command->getArguments();
if ($this->checkSameSlotForKeys($arguments)) {
return $arguments[0];
}
}
/**
* Extracts the key from a command with multiple keys only when all keys in
* the arguments array produce the same hash.
*
* @param CommandInterface $command Command instance.
*
* @return string|null
*/
protected function getKeyFromInterleavedArguments(CommandInterface $command)
{
$arguments = $command->getArguments();
$keys = array();
for ($i = 0; $i < count($arguments); $i += 2) {
$keys[] = $arguments[$i];
}
if ($this->checkSameSlotForKeys($keys)) {
return $arguments[0];
}
}
/**
* 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.
*
* @param CommandInterface $command Command instance.
*
* @return string|null
*/
protected function getKeyFromBlockingListCommands(CommandInterface $command)
{
$arguments = $command->getArguments();
if ($this->checkSameSlotForKeys(array_slice($arguments, 0, count($arguments) - 1))) {
return $arguments[0];
}
}
/**
* Extracts the key from BITOP command.
*
* @param CommandInterface $command Command instance.
*
* @return string|null
*/
protected function getKeyFromBitOp(CommandInterface $command)
{
$arguments = $command->getArguments();
if ($this->checkSameSlotForKeys(array_slice($arguments, 1, count($arguments)))) {
return $arguments[1];
}
}
/**
* 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.
*
* @param CommandInterface $command Command instance.
*
* @return string|null
*/
protected function getKeyFromZsetAggregationCommands(CommandInterface $command)
{
$arguments = $command->getArguments();
$keys = array_merge(array($arguments[0]), array_slice($arguments, 2, $arguments[1]));
if ($this->checkSameSlotForKeys($keys)) {
return $arguments[0];
}
}
/**
* Extracts the key from EVAL and EVALSHA commands.
*
* @param CommandInterface $command Command instance.
*
* @return string|null
*/
protected function getKeyFromScriptingCommands(CommandInterface $command)
{
if ($command instanceof ScriptCommand) {
$keys = $command->getKeys();
} else {
$keys = array_slice($args = $command->getArguments(), 2, $args[1]);
}
if ($keys && $this->checkSameSlotForKeys($keys)) {
return $keys[0];
}
}
/**
* {@inheritdoc}
*/
public function getSlot(CommandInterface $command)
{
$slot = $command->getSlot();
if (!isset($slot) && isset($this->commands[$cmdID = $command->getId()])) {
$key = call_user_func($this->commands[$cmdID], $command);
if (isset($key)) {
$slot = $this->getSlotByKey($key);
$command->setSlot($slot);
}
}
return $slot;
}
/**
* Checks if the specified array of keys will generate the same hash.
*
* @param array $keys Array of keys.
*
* @return bool
*/
protected function checkSameSlotForKeys(array $keys)
{
if (!$count = count($keys)) {
return false;
}
$currentSlot = $this->getSlotByKey($keys[0]);
for ($i = 1; $i < $count; ++$i) {
$nextSlot = $this->getSlotByKey($keys[$i]);
if ($currentSlot !== $nextSlot) {
return false;
}
$currentSlot = $nextSlot;
}
return true;
}
/**
* Returns only the hashable part of a key (delimited by "{...}"), or the
* whole key if a key tag is not found in the string.
*
* @param string $key A key.
*
* @return string
*/
protected function extractKeyTag($key)
{
if (false !== $start = strpos($key, '{')) {
if (false !== ($end = strpos($key, '}', $start)) && $end !== ++$start) {
$key = substr($key, $start, $end - $start);
}
}
return $key;
}
}

View File

@@ -0,0 +1,82 @@
<?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\Cluster\Distributor;
use Predis\Cluster\Hash\HashGeneratorInterface;
/**
* A distributor implements the logic to automatically distribute keys among
* several nodes for client-side sharding.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface DistributorInterface
{
/**
* Adds a node to the distributor with an optional weight.
*
* @param mixed $node Node object.
* @param int $weight Weight for the node.
*/
public function add($node, $weight = null);
/**
* Removes a node from the distributor.
*
* @param mixed $node Node object.
*/
public function remove($node);
/**
* Returns the corresponding slot of a node from the distributor using the
* computed hash of a key.
*
* @param mixed $hash
*
* @return mixed
*/
public function getSlot($hash);
/**
* Returns a node from the distributor using its assigned slot ID.
*
* @param mixed $slot
*
* @return mixed|null
*/
public function getBySlot($slot);
/**
* Returns a node from the distributor using the computed hash of a key.
*
* @param mixed $hash
*
* @return mixed
*/
public function getByHash($hash);
/**
* Returns a node from the distributor mapping to the specified value.
*
* @param string $value
*
* @return mixed
*/
public function get($value);
/**
* Returns the underlying hash generator instance.
*
* @return HashGeneratorInterface
*/
public function getHashGenerator();
}

View File

@@ -0,0 +1,21 @@
<?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\Cluster\Distributor;
/**
* Exception class that identifies empty rings.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class EmptyRingException extends \Exception
{
}

View File

@@ -0,0 +1,270 @@
<?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\Cluster\Distributor;
use Predis\Cluster\Hash\HashGeneratorInterface;
/**
* This class implements an hashring-based distributor that uses the same
* algorithm of memcache to distribute keys in a cluster using client-side
* sharding.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
* @author Lorenzo Castelli <lcastelli@gmail.com>
*/
class HashRing implements DistributorInterface, HashGeneratorInterface
{
const DEFAULT_REPLICAS = 128;
const DEFAULT_WEIGHT = 100;
private $ring;
private $ringKeys;
private $ringKeysCount;
private $replicas;
private $nodeHashCallback;
private $nodes = array();
/**
* @param int $replicas Number of replicas in the ring.
* @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes.
*/
public function __construct($replicas = self::DEFAULT_REPLICAS, $nodeHashCallback = null)
{
$this->replicas = $replicas;
$this->nodeHashCallback = $nodeHashCallback;
}
/**
* Adds a node to the ring with an optional weight.
*
* @param mixed $node Node object.
* @param int $weight Weight for the node.
*/
public function add($node, $weight = null)
{
// In case of collisions in the hashes of the nodes, the node added
// last wins, thus the order in which nodes are added is significant.
$this->nodes[] = array(
'object' => $node,
'weight' => (int) $weight ?: $this::DEFAULT_WEIGHT,
);
$this->reset();
}
/**
* {@inheritdoc}
*/
public function remove($node)
{
// A node is removed by resetting the ring so that it's recreated from
// scratch, in order to reassign possible hashes with collisions to the
// right node according to the order in which they were added in the
// first place.
for ($i = 0; $i < count($this->nodes); ++$i) {
if ($this->nodes[$i]['object'] === $node) {
array_splice($this->nodes, $i, 1);
$this->reset();
break;
}
}
}
/**
* Resets the distributor.
*/
private function reset()
{
unset(
$this->ring,
$this->ringKeys,
$this->ringKeysCount
);
}
/**
* Returns the initialization status of the distributor.
*
* @return bool
*/
private function isInitialized()
{
return isset($this->ringKeys);
}
/**
* Calculates the total weight of all the nodes in the distributor.
*
* @return int
*/
private function computeTotalWeight()
{
$totalWeight = 0;
foreach ($this->nodes as $node) {
$totalWeight += $node['weight'];
}
return $totalWeight;
}
/**
* Initializes the distributor.
*/
private function initialize()
{
if ($this->isInitialized()) {
return;
}
if (!$this->nodes) {
throw new EmptyRingException('Cannot initialize an empty hashring.');
}
$this->ring = array();
$totalWeight = $this->computeTotalWeight();
$nodesCount = count($this->nodes);
foreach ($this->nodes as $node) {
$weightRatio = $node['weight'] / $totalWeight;
$this->addNodeToRing($this->ring, $node, $nodesCount, $this->replicas, $weightRatio);
}
ksort($this->ring, SORT_NUMERIC);
$this->ringKeys = array_keys($this->ring);
$this->ringKeysCount = count($this->ringKeys);
}
/**
* Implements the logic needed to add a node to the hashring.
*
* @param array $ring Source hashring.
* @param mixed $node Node object to be added.
* @param int $totalNodes Total number of nodes.
* @param int $replicas Number of replicas in the ring.
* @param float $weightRatio Weight ratio for the node.
*/
protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio)
{
$nodeObject = $node['object'];
$nodeHash = $this->getNodeHash($nodeObject);
$replicas = (int) round($weightRatio * $totalNodes * $replicas);
for ($i = 0; $i < $replicas; ++$i) {
$key = crc32("$nodeHash:$i");
$ring[$key] = $nodeObject;
}
}
/**
* {@inheritdoc}
*/
protected function getNodeHash($nodeObject)
{
if (!isset($this->nodeHashCallback)) {
return (string) $nodeObject;
}
return call_user_func($this->nodeHashCallback, $nodeObject);
}
/**
* {@inheritdoc}
*/
public function hash($value)
{
return crc32($value);
}
/**
* {@inheritdoc}
*/
public function getByHash($hash)
{
return $this->ring[$this->getSlot($hash)];
}
/**
* {@inheritdoc}
*/
public function getBySlot($slot)
{
$this->initialize();
if (isset($this->ring[$slot])) {
return $this->ring[$slot];
}
}
/**
* {@inheritdoc}
*/
public function getSlot($hash)
{
$this->initialize();
$ringKeys = $this->ringKeys;
$upper = $this->ringKeysCount - 1;
$lower = 0;
while ($lower <= $upper) {
$index = ($lower + $upper) >> 1;
$item = $ringKeys[$index];
if ($item > $hash) {
$upper = $index - 1;
} elseif ($item < $hash) {
$lower = $index + 1;
} else {
return $item;
}
}
return $ringKeys[$this->wrapAroundStrategy($upper, $lower, $this->ringKeysCount)];
}
/**
* {@inheritdoc}
*/
public function get($value)
{
$hash = $this->hash($value);
$node = $this->getByHash($hash);
return $node;
}
/**
* Implements a strategy to deal with wrap-around errors during binary searches.
*
* @param int $upper
* @param int $lower
* @param int $ringKeysCount
*
* @return int
*/
protected function wrapAroundStrategy($upper, $lower, $ringKeysCount)
{
// Binary search for the last item in ringkeys with a value less or
// equal to the key. If no such item exists, return the last item.
return $upper >= 0 ? $upper : $ringKeysCount - 1;
}
/**
* {@inheritdoc}
*/
public function getHashGenerator()
{
return $this;
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster\Distributor;
/**
* This class implements an hashring-based distributor that uses the same
* algorithm of libketama to distribute keys in a cluster using client-side
* sharding.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
* @author Lorenzo Castelli <lcastelli@gmail.com>
*/
class KetamaRing extends HashRing
{
const DEFAULT_REPLICAS = 160;
/**
* @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes.
*/
public function __construct($nodeHashCallback = null)
{
parent::__construct($this::DEFAULT_REPLICAS, $nodeHashCallback);
}
/**
* {@inheritdoc}
*/
protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio)
{
$nodeObject = $node['object'];
$nodeHash = $this->getNodeHash($nodeObject);
$replicas = (int) floor($weightRatio * $totalNodes * ($replicas / 4));
for ($i = 0; $i < $replicas; ++$i) {
$unpackedDigest = unpack('V4', md5("$nodeHash-$i", true));
foreach ($unpackedDigest as $key) {
$ring[$key] = $nodeObject;
}
}
}
/**
* {@inheritdoc}
*/
public function hash($value)
{
$hash = unpack('V', md5($value, true));
return $hash[1];
}
/**
* {@inheritdoc}
*/
protected function wrapAroundStrategy($upper, $lower, $ringKeysCount)
{
// Binary search for the first item in ringkeys with a value greater
// or equal to the key. If no such item exists, return the first item.
return $lower < $ringKeysCount ? $lower : 0;
}
}

View File

@@ -0,0 +1,72 @@
<?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\Cluster\Hash;
/**
* Hash generator implementing the CRC-CCITT-16 algorithm used by redis-cluster.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class CRC16 implements HashGeneratorInterface
{
private static $CCITT_16 = array(
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0,
);
/**
* {@inheritdoc}
*/
public function hash($value)
{
// CRC-CCITT-16 algorithm
$crc = 0;
$CCITT_16 = self::$CCITT_16;
$strlen = strlen($value);
for ($i = 0; $i < $strlen; ++$i) {
$crc = (($crc << 8) ^ $CCITT_16[($crc >> 8) ^ ord($value[$i])]) & 0xFFFF;
}
return $crc;
}
}

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\Cluster\Hash;
/**
* An hash generator implements the logic used to calculate the hash of a key to
* distribute operations among Redis nodes.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface HashGeneratorInterface
{
/**
* Generates an hash from a string to be used for distribution.
*
* @param string $value String value.
*
* @return int
*/
public function hash($value);
}

View File

@@ -0,0 +1,79 @@
<?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\Cluster;
use Predis\Cluster\Distributor\DistributorInterface;
use Predis\Cluster\Distributor\HashRing;
/**
* Default cluster strategy used by Predis to handle client-side sharding.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class PredisStrategy extends ClusterStrategy
{
protected $distributor;
/**
* @param DistributorInterface $distributor Optional distributor instance.
*/
public function __construct(DistributorInterface $distributor = null)
{
parent::__construct();
$this->distributor = $distributor ?: new HashRing();
}
/**
* {@inheritdoc}
*/
public function getSlotByKey($key)
{
$key = $this->extractKeyTag($key);
$hash = $this->distributor->hash($key);
$slot = $this->distributor->getSlot($hash);
return $slot;
}
/**
* {@inheritdoc}
*/
protected function checkSameSlotForKeys(array $keys)
{
if (!$count = count($keys)) {
return false;
}
$currentKey = $this->extractKeyTag($keys[0]);
for ($i = 1; $i < $count; ++$i) {
$nextKey = $this->extractKeyTag($keys[$i]);
if ($currentKey !== $nextKey) {
return false;
}
$currentKey = $nextKey;
}
return true;
}
/**
* {@inheritdoc}
*/
public function getDistributor()
{
return $this->distributor;
}
}

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.
*/
namespace Predis\Cluster;
use Predis\Cluster\Hash\CRC16;
use Predis\Cluster\Hash\HashGeneratorInterface;
use Predis\NotSupportedException;
/**
* Default class used by Predis to calculate hashes out of keys of
* commands supported by redis-cluster.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class RedisStrategy extends ClusterStrategy
{
protected $hashGenerator;
/**
* @param HashGeneratorInterface $hashGenerator Hash generator instance.
*/
public function __construct(HashGeneratorInterface $hashGenerator = null)
{
parent::__construct();
$this->hashGenerator = $hashGenerator ?: new CRC16();
}
/**
* {@inheritdoc}
*/
public function getSlotByKey($key)
{
$key = $this->extractKeyTag($key);
$slot = $this->hashGenerator->hash($key) & 0x3FFF;
return $slot;
}
/**
* {@inheritdoc}
*/
public function getDistributor()
{
throw new NotSupportedException(
'This cluster strategy does not provide an external distributor'
);
}
}

View File

@@ -0,0 +1,53 @@
<?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\Cluster;
use Predis\Cluster\Distributor\DistributorInterface;
use Predis\Command\CommandInterface;
/**
* Interface for classes defining the strategy used to calculate an hash out of
* keys extracted from supported commands.
*
* This is mostly useful to support clustering via client-side sharding.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface StrategyInterface
{
/**
* Returns a slot for the given command used for clustering distribution or
* NULL when this is not possible.
*
* @param CommandInterface $command Command instance.
*
* @return int
*/
public function getSlot(CommandInterface $command);
/**
* Returns a slot for the given key used for clustering distribution or NULL
* when this is not possible.
*
* @param string $key Key string.
*
* @return int
*/
public function getSlotByKey($key);
/**
* Returns a distributor instance to be used by the cluster.
*
* @return DistributorInterface
*/
public function getDistributor();
}

View File

@@ -0,0 +1,191 @@
<?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\Collection\Iterator;
use Predis\ClientInterface;
use Predis\NotSupportedException;
/**
* Provides the base implementation for a fully-rewindable PHP iterator that can
* incrementally iterate over cursor-based collections stored on Redis using the
* commands in the `SCAN` family.
*
* Given their incremental nature with multiple fetches, these kind of iterators
* offer limited guarantees about the returned elements because the collection
* can change several times during the iteration process.
*
* @see http://redis.io/commands/scan
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
abstract class CursorBasedIterator implements \Iterator
{
protected $client;
protected $match;
protected $count;
protected $valid;
protected $fetchmore;
protected $elements;
protected $cursor;
protected $position;
protected $current;
/**
* @param ClientInterface $client Client connected to Redis.
* @param string $match Pattern to match during the server-side iteration.
* @param int $count Hint used by Redis to compute the number of results per iteration.
*/
public function __construct(ClientInterface $client, $match = null, $count = null)
{
$this->client = $client;
$this->match = $match;
$this->count = $count;
$this->reset();
}
/**
* Ensures that the client supports the specified Redis command required to
* fetch elements from the server to perform the iteration.
*
* @param ClientInterface $client Client connected to Redis.
* @param string $commandID Command ID.
*
* @throws NotSupportedException
*/
protected function requiredCommand(ClientInterface $client, $commandID)
{
if (!$client->getProfile()->supportsCommand($commandID)) {
throw new NotSupportedException("The current profile does not support '$commandID'.");
}
}
/**
* Resets the inner state of the iterator.
*/
protected function reset()
{
$this->valid = true;
$this->fetchmore = true;
$this->elements = array();
$this->cursor = 0;
$this->position = -1;
$this->current = null;
}
/**
* Returns an array of options for the `SCAN` command.
*
* @return array
*/
protected function getScanOptions()
{
$options = array();
if (strlen($this->match) > 0) {
$options['MATCH'] = $this->match;
}
if ($this->count > 0) {
$options['COUNT'] = $this->count;
}
return $options;
}
/**
* Fetches a new set of elements from the remote collection, effectively
* advancing the iteration process.
*
* @return array
*/
abstract protected function executeCommand();
/**
* Populates the local buffer of elements fetched from the server during
* the iteration.
*/
protected function fetch()
{
list($cursor, $elements) = $this->executeCommand();
if (!$cursor) {
$this->fetchmore = false;
}
$this->cursor = $cursor;
$this->elements = $elements;
}
/**
* Extracts next values for key() and current().
*/
protected function extractNext()
{
++$this->position;
$this->current = array_shift($this->elements);
}
/**
* {@inheritdoc}
*/
public function rewind()
{
$this->reset();
$this->next();
}
/**
* {@inheritdoc}
*/
public function current()
{
return $this->current;
}
/**
* {@inheritdoc}
*/
public function key()
{
return $this->position;
}
/**
* {@inheritdoc}
*/
public function next()
{
tryFetch: {
if (!$this->elements && $this->fetchmore) {
$this->fetch();
}
if ($this->elements) {
$this->extractNext();
} elseif ($this->cursor) {
goto tryFetch;
} else {
$this->valid = false;
}
}
}
/**
* {@inheritdoc}
*/
public function valid()
{
return $this->valid;
}
}

View File

@@ -0,0 +1,60 @@
<?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\Collection\Iterator;
use Predis\ClientInterface;
/**
* Abstracts the iteration of fields and values of an hash by leveraging the
* HSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*
* @link http://redis.io/commands/scan
*/
class HashKey extends CursorBasedIterator
{
protected $key;
/**
* {@inheritdoc}
*/
public function __construct(ClientInterface $client, $key, $match = null, $count = null)
{
$this->requiredCommand($client, 'HSCAN');
parent::__construct($client, $match, $count);
$this->key = $key;
}
/**
* {@inheritdoc}
*/
protected function executeCommand()
{
return $this->client->hscan($this->key, $this->cursor, $this->getScanOptions());
}
/**
* {@inheritdoc}
*/
protected function extractNext()
{
if ($kv = each($this->elements)) {
$this->position = $kv[0];
$this->current = $kv[1];
unset($this->elements[$this->position]);
}
}
}

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\Collection\Iterator;
use Predis\ClientInterface;
/**
* Abstracts the iteration of the keyspace on a Redis instance by leveraging the
* SCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*
* @link http://redis.io/commands/scan
*/
class Keyspace extends CursorBasedIterator
{
/**
* {@inheritdoc}
*/
public function __construct(ClientInterface $client, $match = null, $count = null)
{
$this->requiredCommand($client, 'SCAN');
parent::__construct($client, $match, $count);
}
/**
* {@inheritdoc}
*/
protected function executeCommand()
{
return $this->client->scan($this->cursor, $this->getScanOptions());
}
}

View File

@@ -0,0 +1,176 @@
<?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\Collection\Iterator;
use Predis\ClientInterface;
use Predis\NotSupportedException;
/**
* Abstracts the iteration of items stored in a list by leveraging the LRANGE
* command wrapped in a fully-rewindable PHP iterator.
*
* This iterator tries to emulate the behaviour of cursor-based iterators based
* on the SCAN-family of commands introduced in Redis <= 2.8, meaning that due
* to its incremental nature with multiple fetches it can only offer limited
* guarantees on the returned elements because the collection can change several
* times (trimmed, deleted, overwritten) during the iteration process.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*
* @link http://redis.io/commands/lrange
*/
class ListKey implements \Iterator
{
protected $client;
protected $count;
protected $key;
protected $valid;
protected $fetchmore;
protected $elements;
protected $position;
protected $current;
/**
* @param ClientInterface $client Client connected to Redis.
* @param string $key Redis list key.
* @param int $count Number of items retrieved on each fetch operation.
*
* @throws \InvalidArgumentException
*/
public function __construct(ClientInterface $client, $key, $count = 10)
{
$this->requiredCommand($client, 'LRANGE');
if ((false === $count = filter_var($count, FILTER_VALIDATE_INT)) || $count < 0) {
throw new \InvalidArgumentException('The $count argument must be a positive integer.');
}
$this->client = $client;
$this->key = $key;
$this->count = $count;
$this->reset();
}
/**
* Ensures that the client instance supports the specified Redis command
* required to fetch elements from the server to perform the iteration.
*
* @param ClientInterface $client Client connected to Redis.
* @param string $commandID Command ID.
*
* @throws NotSupportedException
*/
protected function requiredCommand(ClientInterface $client, $commandID)
{
if (!$client->getProfile()->supportsCommand($commandID)) {
throw new NotSupportedException("The current profile does not support '$commandID'.");
}
}
/**
* Resets the inner state of the iterator.
*/
protected function reset()
{
$this->valid = true;
$this->fetchmore = true;
$this->elements = array();
$this->position = -1;
$this->current = null;
}
/**
* Fetches a new set of elements from the remote collection, effectively
* advancing the iteration process.
*
* @return array
*/
protected function executeCommand()
{
return $this->client->lrange($this->key, $this->position + 1, $this->position + $this->count);
}
/**
* Populates the local buffer of elements fetched from the server during the
* iteration.
*/
protected function fetch()
{
$elements = $this->executeCommand();
if (count($elements) < $this->count) {
$this->fetchmore = false;
}
$this->elements = $elements;
}
/**
* Extracts next values for key() and current().
*/
protected function extractNext()
{
++$this->position;
$this->current = array_shift($this->elements);
}
/**
* {@inheritdoc}
*/
public function rewind()
{
$this->reset();
$this->next();
}
/**
* {@inheritdoc}
*/
public function current()
{
return $this->current;
}
/**
* {@inheritdoc}
*/
public function key()
{
return $this->position;
}
/**
* {@inheritdoc}
*/
public function next()
{
if (!$this->elements && $this->fetchmore) {
$this->fetch();
}
if ($this->elements) {
$this->extractNext();
} else {
$this->valid = false;
}
}
/**
* {@inheritdoc}
*/
public function valid()
{
return $this->valid;
}
}

View File

@@ -0,0 +1,47 @@
<?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\Collection\Iterator;
use Predis\ClientInterface;
/**
* Abstracts the iteration of members stored in a set by leveraging the SSCAN
* command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*
* @link http://redis.io/commands/scan
*/
class SetKey extends CursorBasedIterator
{
protected $key;
/**
* {@inheritdoc}
*/
public function __construct(ClientInterface $client, $key, $match = null, $count = null)
{
$this->requiredCommand($client, 'SSCAN');
parent::__construct($client, $match, $count);
$this->key = $key;
}
/**
* {@inheritdoc}
*/
protected function executeCommand()
{
return $this->client->sscan($this->key, $this->cursor, $this->getScanOptions());
}
}

View File

@@ -0,0 +1,60 @@
<?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\Collection\Iterator;
use Predis\ClientInterface;
/**
* Abstracts the iteration of members stored in a sorted set by leveraging the
* ZSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*
* @link http://redis.io/commands/scan
*/
class SortedSetKey extends CursorBasedIterator
{
protected $key;
/**
* {@inheritdoc}
*/
public function __construct(ClientInterface $client, $key, $match = null, $count = null)
{
$this->requiredCommand($client, 'ZSCAN');
parent::__construct($client, $match, $count);
$this->key = $key;
}
/**
* {@inheritdoc}
*/
protected function executeCommand()
{
return $this->client->zscan($this->key, $this->cursor, $this->getScanOptions());
}
/**
* {@inheritdoc}
*/
protected function extractNext()
{
if ($kv = each($this->elements)) {
$this->position = $kv[0];
$this->current = $kv[1];
unset($this->elements[$this->position]);
}
}
}

View File

@@ -0,0 +1,129 @@
<?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;
/**
* Base class for Redis commands.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
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);
unset($this->slot);
}
/**
* {@inheritdoc}
*/
public function setRawArguments(array $arguments)
{
$this->arguments = $arguments;
unset($this->slot);
}
/**
* {@inheritdoc}
*/
public function getArguments()
{
return $this->arguments;
}
/**
* {@inheritdoc}
*/
public function getArgument($index)
{
if (isset($this->arguments[$index])) {
return $this->arguments[$index];
}
}
/**
* {@inheritdoc}
*/
public function setSlot($slot)
{
$this->slot = $slot;
}
/**
* {@inheritdoc}
*/
public function getSlot()
{
if (isset($this->slot)) {
return $this->slot;
}
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return $data;
}
/**
* Normalizes the arguments array passed to a Redis command.
*
* @param array $arguments Arguments for a command.
*
* @return array
*/
public static function normalizeArguments(array $arguments)
{
if (count($arguments) === 1 && is_array($arguments[0])) {
return $arguments[0];
}
return $arguments;
}
/**
* Normalizes the arguments array passed to a variadic Redis command.
*
* @param array $arguments Arguments for a command.
*
* @return array
*/
public static function normalizeVariadic(array $arguments)
{
if (count($arguments) === 2 && is_array($arguments[1])) {
return array_merge(array($arguments[0]), $arguments[1]);
}
return $arguments;
}
}

View File

@@ -0,0 +1,81 @@
<?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;
/**
* Defines an abstraction representing a Redis command.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface CommandInterface
{
/**
* Returns the ID of the Redis command. By convention, command identifiers
* must always be uppercase.
*
* @return string
*/
public function getId();
/**
* Assign the specified slot to the command for clustering distribution.
*
* @param int $slot Slot ID.
*/
public function setSlot($slot);
/**
* Returns the assigned slot of the command for clustering distribution.
*
* @return int|null
*/
public function getSlot();
/**
* Sets the arguments for the command.
*
* @param array $arguments List of arguments.
*/
public function setArguments(array $arguments);
/**
* Sets the raw arguments for the command without processing them.
*
* @param array $arguments List of arguments.
*/
public function setRawArguments(array $arguments);
/**
* Gets the arguments of the command.
*
* @return array
*/
public function getArguments();
/**
* Gets the argument of the command at the specified index.
*
* @param int $index Index of the desired argument.
*
* @return mixed|null
*/
public function getArgument($index);
/**
* Parses a raw response and returns a PHP object.
*
* @param string $data Binary string containing the whole response.
*
* @return mixed
*/
public function parseResponse($data);
}

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;
/**
* @link http://redis.io/commands/auth
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionAuth extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'AUTH';
}
}

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;
/**
* @link http://redis.io/commands/echo
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionEcho extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'ECHO';
}
}

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;
/**
* @link http://redis.io/commands/ping
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionPing extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'PING';
}
}

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;
/**
* @link http://redis.io/commands/quit
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionQuit extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'QUIT';
}
}

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;
/**
* @link http://redis.io/commands/select
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionSelect extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'SELECT';
}
}

View File

@@ -0,0 +1,42 @@
<?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;
/**
* @link http://redis.io/commands/geoadd
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class GeospatialGeoAdd extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'GEOADD';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
if (count($arguments) === 2 && is_array($arguments[1])) {
foreach (array_pop($arguments) as $item) {
$arguments = array_merge($arguments, $item);
}
}
return $arguments;
}
}

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;
/**
* @link http://redis.io/commands/geodist
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class GeospatialGeoDist extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'GEODIST';
}
}

View File

@@ -0,0 +1,41 @@
<?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;
/**
* @link http://redis.io/commands/geohash
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class GeospatialGeoHash extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'GEOHASH';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
if (count($arguments) === 2 && is_array($arguments[1])) {
$members = array_pop($arguments);
$arguments = array_merge($arguments, $members);
}
return $arguments;
}
}

View File

@@ -0,0 +1,41 @@
<?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;
/**
* @link http://redis.io/commands/geopos
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class GeospatialGeoPos extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'GEOPOS';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
if (count($arguments) === 2 && is_array($arguments[1])) {
$members = array_pop($arguments);
$arguments = array_merge($arguments, $members);
}
return $arguments;
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @link http://redis.io/commands/georadius
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class GeospatialGeoRadius extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'GEORADIUS';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(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'];
}
}
return $arguments;
}
}

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;
/**
* @link http://redis.io/commands/georadiusbymember
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class GeospatialGeoRadiusByMember extends GeospatialGeoRadius
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'GEORADIUSBYMEMBER';
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @link http://redis.io/commands/hdel
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashDelete extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'HDEL';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
return self::normalizeVariadic($arguments);
}
}

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;
/**
* @link http://redis.io/commands/hexists
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashExists extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'HEXISTS';
}
}

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;
/**
* @link http://redis.io/commands/hget
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashGet extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'HGET';
}
}

View File

@@ -0,0 +1,42 @@
<?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;
/**
* @link http://redis.io/commands/hgetall
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashGetAll extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'HGETALL';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
$result = array();
for ($i = 0; $i < count($data); ++$i) {
$result[$data[$i]] = $data[++$i];
}
return $result;
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @link http://redis.io/commands/hmget
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashGetMultiple extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'HMGET';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
return self::normalizeVariadic($arguments);
}
}

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;
/**
* @link http://redis.io/commands/hincrby
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashIncrementBy extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'HINCRBY';
}
}

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;
/**
* @link http://redis.io/commands/hincrbyfloat
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashIncrementByFloat extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'HINCRBYFLOAT';
}
}

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;
/**
* @link http://redis.io/commands/hkeys
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashKeys extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'HKEYS';
}
}

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;
/**
* @link http://redis.io/commands/hlen
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashLength extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'HLEN';
}
}

View File

@@ -0,0 +1,85 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @link http://redis.io/commands/hscan
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashScan extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'HSCAN';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
if (count($arguments) === 3 && is_array($arguments[2])) {
$options = $this->prepareOptions(array_pop($arguments));
$arguments = array_merge($arguments, $options);
}
return $arguments;
}
/**
* Returns a list of options and modifiers compatible with Redis.
*
* @param array $options List of options.
*
* @return array
*/
protected function prepareOptions($options)
{
$options = array_change_key_case($options, CASE_UPPER);
$normalized = array();
if (!empty($options['MATCH'])) {
$normalized[] = 'MATCH';
$normalized[] = $options['MATCH'];
}
if (!empty($options['COUNT'])) {
$normalized[] = 'COUNT';
$normalized[] = $options['COUNT'];
}
return $normalized;
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
if (is_array($data)) {
$fields = $data[1];
$result = array();
for ($i = 0; $i < count($fields); ++$i) {
$result[$fields[$i]] = $fields[++$i];
}
$data[1] = $result;
}
return $data;
}
}

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;
/**
* @link http://redis.io/commands/hset
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashSet extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'HSET';
}
}

View File

@@ -0,0 +1,48 @@
<?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;
/**
* @link http://redis.io/commands/hmset
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashSetMultiple extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'HMSET';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
if (count($arguments) === 2 && is_array($arguments[1])) {
$flattenedKVs = array($arguments[0]);
$args = $arguments[1];
foreach ($args as $k => $v) {
$flattenedKVs[] = $k;
$flattenedKVs[] = $v;
}
return $flattenedKVs;
}
return $arguments;
}
}

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;
/**
* @link http://redis.io/commands/hsetnx
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashSetPreserve extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'HSETNX';
}
}

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;
/**
* @link http://redis.io/commands/hstrlen
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashStringLength extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'HSTRLEN';
}
}

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;
/**
* @link http://redis.io/commands/hvals
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashValues extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'HVALS';
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @link http://redis.io/commands/pfadd
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HyperLogLogAdd extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'PFADD';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
return self::normalizeVariadic($arguments);
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @link http://redis.io/commands/pfcount
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HyperLogLogCount extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'PFCOUNT';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
return self::normalizeArguments($arguments);
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @link http://redis.io/commands/pfmerge
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HyperLogLogMerge extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'PFMERGE';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
return self::normalizeArguments($arguments);
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @link http://redis.io/commands/del
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyDelete extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'DEL';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
return self::normalizeArguments($arguments);
}
}

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;
/**
* @link http://redis.io/commands/dump
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyDump extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'DUMP';
}
}

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;
/**
* @link http://redis.io/commands/exists
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyExists extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'EXISTS';
}
}

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;
/**
* @link http://redis.io/commands/expire
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyExpire extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'EXPIRE';
}
}

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;
/**
* @link http://redis.io/commands/expireat
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyExpireAt extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'EXPIREAT';
}
}

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;
/**
* @link http://redis.io/commands/keys
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyKeys extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'KEYS';
}
}

View File

@@ -0,0 +1,50 @@
<?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;
/**
* @link http://redis.io/commands/migrate
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyMigrate extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'MIGRATE';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
if (is_array(end($arguments))) {
foreach (array_pop($arguments) as $modifier => $value) {
$modifier = strtoupper($modifier);
if ($modifier === 'COPY' && $value == true) {
$arguments[] = $modifier;
}
if ($modifier === 'REPLACE' && $value == true) {
$arguments[] = $modifier;
}
}
}
return $arguments;
}
}

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;
/**
* @link http://redis.io/commands/move
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyMove extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'MOVE';
}
}

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;
/**
* @link http://redis.io/commands/persist
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyPersist extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'PERSIST';
}
}

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;
/**
* @link http://redis.io/commands/pexpire
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyPreciseExpire extends KeyExpire
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'PEXPIRE';
}
}

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;
/**
* @link http://redis.io/commands/pexpireat
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyPreciseExpireAt extends KeyExpireAt
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'PEXPIREAT';
}
}

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;
/**
* @link http://redis.io/commands/pttl
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyPreciseTimeToLive extends KeyTimeToLive
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'PTTL';
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @link http://redis.io/commands/randomkey
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyRandom extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'RANDOMKEY';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return $data !== '' ? $data : null;
}
}

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;
/**
* @link http://redis.io/commands/rename
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyRename extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'RENAME';
}
}

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;
/**
* @link http://redis.io/commands/renamenx
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyRenamePreserve extends KeyRename
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'RENAMENX';
}
}

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;
/**
* @link http://redis.io/commands/restore
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyRestore extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'RESTORE';
}
}

View File

@@ -0,0 +1,66 @@
<?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;
/**
* @link http://redis.io/commands/scan
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyScan extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'SCAN';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
if (count($arguments) === 2 && is_array($arguments[1])) {
$options = $this->prepareOptions(array_pop($arguments));
$arguments = array_merge($arguments, $options);
}
return $arguments;
}
/**
* Returns a list of options and modifiers compatible with Redis.
*
* @param array $options List of options.
*
* @return array
*/
protected function prepareOptions($options)
{
$options = array_change_key_case($options, CASE_UPPER);
$normalized = array();
if (!empty($options['MATCH'])) {
$normalized[] = 'MATCH';
$normalized[] = $options['MATCH'];
}
if (!empty($options['COUNT'])) {
$normalized[] = 'COUNT';
$normalized[] = $options['COUNT'];
}
return $normalized;
}
}

View File

@@ -0,0 +1,83 @@
<?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;
/**
* @link http://redis.io/commands/sort
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeySort extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'SORT';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
if (count($arguments) === 1) {
return $arguments;
}
$query = array($arguments[0]);
$sortParams = array_change_key_case($arguments[1], CASE_UPPER);
if (isset($sortParams['BY'])) {
$query[] = 'BY';
$query[] = $sortParams['BY'];
}
if (isset($sortParams['GET'])) {
$getargs = $sortParams['GET'];
if (is_array($getargs)) {
foreach ($getargs as $getarg) {
$query[] = 'GET';
$query[] = $getarg;
}
} else {
$query[] = 'GET';
$query[] = $getargs;
}
}
if (isset($sortParams['LIMIT']) &&
is_array($sortParams['LIMIT']) &&
count($sortParams['LIMIT']) == 2) {
$query[] = 'LIMIT';
$query[] = $sortParams['LIMIT'][0];
$query[] = $sortParams['LIMIT'][1];
}
if (isset($sortParams['SORT'])) {
$query[] = strtoupper($sortParams['SORT']);
}
if (isset($sortParams['ALPHA']) && $sortParams['ALPHA'] == true) {
$query[] = 'ALPHA';
}
if (isset($sortParams['STORE'])) {
$query[] = 'STORE';
$query[] = $sortParams['STORE'];
}
return $query;
}
}

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;
/**
* @link http://redis.io/commands/ttl
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyTimeToLive extends Command
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'TTL';
}
}

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