Compare commits

..

139 Commits

Author SHA1 Message Date
Daniele Alessandri 4123fcd85d Update CHANGELOG and bump VERSION. 2014-08-01 11:43:10 +02:00
Daniele Alessandri 647cd4c502 Update CHANGELOG.
[ci skip]
2014-07-28 20:58:26 +02:00
Daniele Alessandri cab266f359 Move command "COMMAND" to the server profile for Redis 2.8.
This command has been backported from the "unstable" branch of Redis
to the "2.8" one, and it is available since 2.8.13.
2014-07-28 20:56:55 +02:00
Daniele Alessandri cea6354b55 Update README.
[ci skip]
2014-07-28 10:44:00 +02:00
Daniele Alessandri 7155dfdfb4 Update README.
[ci skip]
2014-07-27 15:49:49 +02:00
Daniele Alessandri dea768044c Update README.
[ci skip]
2014-07-25 16:33:03 +02:00
Daniele Alessandri 70d1c116b6 Update README.
[ci skip]
2014-07-25 13:23:04 +02:00
Daniele Alessandri 001fb45a32 Update README.
[ci skip]
2014-07-25 13:15:14 +02:00
Daniele Alessandri 8dd7f40d21 Fix read/write timeouts with PhpiredisStreamConnection.
This is a regression of commit 4db0017, using stream_socket_recvfrom()
bypasses stream wrappers thus read/write timeouts handled by PHP. The
only way to make them work again is to fetch the raw socket from the
stream resource and use socket_import_stream() which is unfortunately
a PHP 5.4+ function.
2014-07-23 12:43:29 +02:00
Daniele Alessandri d5c650d51a Update example to avoid breaking when run against Redis 3.0.
[ci skip]
2014-07-21 18:23:00 +02:00
Daniele Alessandri 7bb85cae1c Fix phpdocs. 2014-07-21 14:41:13 +02:00
Daniele Alessandri 43a6ae181e Fetch updated slots map from node indicated by -MOVED response.
This optimization makes it possible to fetch the slots map directly
from the server indicated by the -MOVED response eliminating the need
to use a random node in the pool, which in turn could require Predis
to open a new and useless connection.
2014-07-21 14:31:37 +02:00
Daniele Alessandri f538442ad9 Minor phpdocs fixes.
[ci skip]
2014-07-21 14:17:06 +02:00
Daniele Alessandri c2d0737e98 Fetch cluster slots from node upon first -MOVED response.
This is a more sane default as it allows users to indicate only a few
servers of the whole cluster composition, while it previously required
a more complex configuration of the client using client options.

This feature can be disabled using the "enableClusterNodes()" method.
2014-07-21 14:14:14 +02:00
Daniele Alessandri d6bd47ca47 Light rewording and reorganization of README.
[ci skip]
2014-07-21 10:43:38 +02:00
Daniele Alessandri 0235ba1a30 New command: COMMAND (Redis 3.0.0).
The command returns the response as is, but it would probably be not a
bad idea to parse it to restructure returned info using named arrays,
making it easier to access single commands and their info.

We will keep it as is for now, and investigate this change later.
2014-07-18 13:26:54 +02:00
Daniele Alessandri 287b4c76ce Rebase me 2014-07-18 13:12:23 +02:00
Daniele Alessandri ba6cfa7887 Add dedicated server profile for Redis 3.0. 2014-07-18 13:10:52 +02:00
Daniele Alessandri adb4d82ea8 Update CHANGELOG. 2014-07-17 16:21:29 +02:00
Daniele Alessandri 8dac9c097e Switch to CLUSTER SLOTS instead of CLUSTER NODES for redis-cluster.
CLUSTER SLOTS returns a structured response easier to handle compared
to the bulk response of CLUSTER NODES which must be parsed making the
whole thing more fragile.

CLUSTER SLOTS has been added in Redis 3.0.0b7.
2014-07-17 16:21:25 +02:00
Daniele Alessandri 0df05ae008 Run php-cs against source code. 2014-07-17 16:04:48 +02:00
Daniele Alessandri c85137924c Update CHANGELOG 2014-07-17 12:48:10 +02:00
Daniele Alessandri b1761c55c5 Use same strategy for both client-side sharding and redis-cluster.
This change is possible because, after a few changes in redis-cluster,
our default cluster strategy used for client side sharding and the one
used for redis-cluster turned out to be exactly the same, except for
the hashing function used to calculate distribution.

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

The only difference between the client-side sharding strategy and the
one used for redis-cluster, aside from the hash function, is in the
rules used for extracting hash tags from keys since empty tags {} are
considered valid by redis-cluster. In v1.0.0 the strategy used for our
client-side sharding cluster will reflect this change, but we will not
change it in v0.8 since it can be considered a breaking changes as it
can potentially affect existing cluster deployments.
2014-07-17 12:38:47 +02:00
Daniele Alessandri 5b8c2e21f6 Update CHANGELOG. 2014-07-17 12:28:51 +02:00
Daniele Alessandri f6e97d4b94 Implement PING in PUB/SUB loop abstraction for Redis 3.0.0. 2014-07-17 12:27:30 +02:00
Daniele Alessandri 18f5cea8e6 Update CHANGELOG.
[ci skip]
2014-07-16 19:12:35 +02:00
Daniele Alessandri 727072c086 Minor changes in checks for transaction support in server profile. 2014-07-16 11:31:15 +02:00
Daniele Alessandri 5fde716098 Back to development.
[ci skip]
2014-07-15 12:38:13 +02:00
Daniele Alessandri 239864d9a0 Update CHANGELOG and bump VERSION. 2014-07-15 11:54:33 +02:00
Daniele Alessandri b006d2306c Rewrite pear packager script.
We still use Onion's package.ini for the configuration of the package
to keep things simple, but we might switch to a more compact solution
in the future since we do not really need much of its features.
2014-07-15 11:39:42 +02:00
Daniele Alessandri 34e394cc10 Reword CHANGELOG. 2014-07-15 11:36:13 +02:00
Daniele Alessandri 79539c3bcf Update CHANGELOG. 2014-07-14 16:26:06 +02:00
Daniele Alessandri 74817b5596 Porperly handle -ERR instead of +QUEUED inside MULTI ... EXEC.
Error responses such as -OOM or -ERR on invalid arguments in commands
are returned immediatly instead of +QUEUED when using a transaction,
which is a condition that had not been tested enough. This condition
led to a bug in which Predis was not invalidating the transaction, so
when trying to create a new transaction Redis returned a "-ERR MULTI
calls can not be nested".

This commit fixes #187.
2014-07-14 16:23:18 +02:00
Daniele Alessandri f967ecd66b Merge remote-tracking branch 'github/pr/184' into v0.8 2014-07-07 11:48:01 +02:00
Daniele Alessandri 261d0b824f Update CHANGELOG. 2014-07-07 11:10:54 +02:00
Daniele Alessandri b77da8476c Backport fix for cursor-based iterator abstraction from PR #182. 2014-07-07 10:58:33 +02:00
Ian Babrou 3fbd29d00b prevent uncaught broken pipe error 2014-07-07 11:30:44 +04:00
Daniele Alessandri 8373663486 Use stream_socket_recvfrom() in PhpiredisStreamConnection.
Similarly to the socket-ext based connection using phpiredis, in our
stream based PhpiredisStreamConnection class we should read data from
the stream using stream_socket_recvfrom() instead of fread() because
the latter could block until a timeout is reached when the read buffer
contains less data then the specified length.

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

This commit fixes issue #180.
2014-06-23 14:25:30 +02:00
Daniele Alessandri 18853d1dbb Just rename a couple of local variables. 2014-06-10 10:58:02 +02:00
Daniele Alessandri 1da827c0ae [tests] Use annotations to specify expected Redis server version.
This change targets only method annotations and aims to make the body
of tests more readable using the @requiresRedisVersion annotation.

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

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

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

Future improvements (currently not needed) include:

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

  * Ability to specify a version range.
2014-06-09 17:42:33 +02:00
Daniele Alessandri aa8fd5cea4 Update CHANGELOG. 2014-06-09 14:50:09 +02:00
Daniele Alessandri 8d353cc223 New command: ZREMRANGEBYLEX (Redis 2.8.9).
Closes #177.
2014-06-09 14:50:02 +02:00
Daniele Alessandri 0a580c9d1b New command: ZRANGEBYLEX (Redis 2.8.9).
Closes #175.
2014-06-09 14:49:54 +02:00
Daniele Alessandri 58d1cc12dc New command: ZLEXCOUNT (Redis 2.8.9).
Closes #176.
2014-06-09 14:49:27 +02:00
Daniele Alessandri a8862f0fdd Update CHANGELOG.
[ci skip]
2014-06-03 16:57:22 +02:00
Daniele Alessandri e148dc831a Fix parsing of the output of CLUSTER NODES to fetch slots map.
There was an error in presence of slaves in the cluster configuration,
see #165 for reference.
2014-06-03 16:54:00 +02:00
Daniele Alessandri c4b592298c Skip integration tests for HyperLogLog commands on Redis < 2.8.9.
Caught this by running the test suite on TravisCI which still runs an
older version of Redis 2.8. We should find a more decent way to handle
differences in profiles when commands get added between patch releases
of Redis, our custom skip method will do for now.
2014-05-30 12:22:12 +02:00
Daniele Alessandri 0e3b105959 New commands: PFADD, PFCOUNT, PFMERGE (Redis 2.8.9).
Backported from the master branch.

Many thanks to @rubensayshi for his initial commits on this in #163,
but I had to start from scratch for the master branch first.
2014-05-30 12:01:17 +02:00
Daniele Alessandri 749bbfe0e3 Update CHANGELOG.
[ci skip]

Meh
2014-05-30 11:41:20 +02:00
Daniele Alessandri c2ae1c612e Merge remote-tracking branch 'github/pr/159' into v0.8 2014-05-28 12:58:03 +02:00
Daniele Alessandri a85f89e2c7 Merge remote-tracking branch 'github/pr/170' into v0.8 2014-05-28 12:48:53 +02:00
thbourlove dbb802f0f1 use phpunit 4.x instead of 3.x 2014-05-26 22:57:40 +08:00
thbourlove 64b79511bd isntall requirement with a better way 2014-05-21 17:43:32 +08:00
thbourlove 5a9e747b01 add phpunit as dev requirement 2014-05-21 17:21:16 +08:00
Alexey Kupershtokh cb0fbd01b4 Enable INCRBYFLOAT in cluster mode 2014-03-28 22:12:43 +07:00
Daniele Alessandri 83df2ef11c Update CHANGELOG and README. 2014-03-25 15:02:22 +01:00
Daniele Alessandri c94e5d3a10 Merge remote-tracking branch 'thbourlove/v0.8' into v0.8 2014-03-25 14:51:22 +01:00
Daniele Alessandri c90fbfc0ff Failures for PHP 5.6 on Travis CI are not allowed anymore. 2014-03-25 14:31:04 +01:00
Daniele Alessandri 21d6f0e7c6 Update CHANGELOG. 2014-03-25 14:29:05 +01:00
Daniele Alessandri f4a950b275 Merge remote-tracking branch 'thbourlove/server_info' into v0.8 2014-03-25 14:26:10 +01:00
thbourlove 5ec51c899f original array format 2014-03-15 01:16:38 +08:00
thbourlove e1d700dd0c fix #154 2014-03-15 00:59:55 +08:00
Daniele Alessandri 809f76a7b3 Fix implementation for hash tags extraction from keys.
We now fully comply with the specifications defined by Redis.
2014-02-11 19:10:09 +01:00
Daniele Alessandri ab6cbe7e10 Add PHP 5.6 to the build matix of Travis-CI.
Builds are allowed to fail for now as PHP 5.6 is still in alpha stage.
2014-02-11 16:34:40 +01:00
Daniele Alessandri 80bcb9f475 Add HHVM to the build matrix of Travis-CI for v0.8.
Travis-CI recently provisioned virtual machines with HHVM 2.4.0 which
fixed the issues preventing Predis v0.8 from working correctly.
2014-02-11 16:29:44 +01:00
Daniele Alessandri c8e671a42a [HHVM] Work around a bug in class_exists() with non-string values. 2014-02-11 16:24:09 +01:00
Daniele Alessandri 838e6af403 [tests] Adapt for testing with HHVM. 2014-02-11 15:58:38 +01:00
Daniele Alessandri ba2ffb612a Add support for key hash tags in redis-cluster (Redis 3.0.0b1).
Multi-keys operations are not allowed even when keys generate the same
hash but this will probably be supported in later betas of Redis which
means we will basically end up reusing the whole strategy used for
client-side sharding.
2014-02-11 15:33:07 +01:00
Daniele Alessandri fdaf73987b Update CHANGELOG. 2014-02-11 12:44:00 +01:00
Daniele Alessandri 50f8922df2 Update README about HHVM compatibility. 2014-02-11 12:44:00 +01:00
Daniele Alessandri 60733eb5c9 Make Predis v0.8 compatible with HHVM 2.4.0.
Since we cannot rely on the PHP version to detect the availability of
socket_import_stream(), we switched to function_exists(). As an added
bonus, using function_exists() is twice faster.
2014-02-11 12:44:00 +01:00
Daniele Alessandri a13dd4e180 Back to development. 2014-02-11 12:44:00 +01:00
thbourlove 1678e486b1 use 2.8 as the default server version 2014-01-25 10:55:47 +08:00
Daniele Alessandri 5f2eea628e Update CHANGELOG and bump VERSION. 2014-01-16 15:10:29 +01:00
Daniele Alessandri 0f01b89d44 Bump year in LICENSE.
[ci skip]
2014-01-10 17:46:58 +01:00
Daniele Alessandri 9d0201ed9e [bin] Add missing executable in the repository for new script. 2014-01-08 17:38:22 +01:00
Daniele Alessandri 0cead0245d [bin] Add helper script to automate PEAR package creation.
Note that this script takes care of customizing `phpunit.xml.dist` so
that tests can be run as soon as the PEAR package has been installed
as requested in #126.
2014-01-08 17:30:54 +01:00
Daniele Alessandri 0a4eed1dff [tests] Use Predis\Autoload from include_path when not in repository.
This is useful only for running the test suite after installing the
PEAR package, see #126 for reference.
2014-01-08 17:22:15 +01:00
Daniele Alessandri d2debfd43e Apply some more minor phpdoc fixes. 2013-12-22 17:51:18 +01:00
Daniele Alessandri 9a1884d960 Merge branch 'v0.8-backport-fixes' into v0.8 2013-12-22 17:35:36 +01:00
Daniele Alessandri a6dd685f8d Run php-cs against source code. 2013-12-22 17:34:50 +01:00
Daniele Alessandri f499d5695a Explicitly return null value. 2013-12-22 17:34:50 +01:00
Daniele Alessandri 23c4b95a0b Remove useless return statement.
Backported from da8a203 (master).
2013-12-22 17:34:50 +01:00
Daniele Alessandri 80cb31514a [tests] Remove unused "use" directive. 2013-12-22 17:34:50 +01:00
Daniele Alessandri 6306509086 [tests] Fix a couple of innocuous glitches. 2013-12-22 17:34:50 +01:00
Daniele Alessandri 2aab474670 Apply more phpdoc fixes.
Backported from 6a577a0 (master).
2013-12-22 17:34:50 +01:00
Daniele Alessandri d628c0f23b [tests] Fix a couple of glitches in our base PHPUnit classes. 2013-12-22 17:34:50 +01:00
Daniele Alessandri ad92618003 [tests] No need to reassign $exception.
Backported from 728d9dc (master).
2013-12-22 17:34:49 +01:00
Daniele Alessandri 0fb7674762 Fix phpdocs for fluent interfaces.
Backported from a092f6d (master).

[ci skip]
2013-12-22 17:34:49 +01:00
Daniele Alessandri e778baa491 Make Pipeline::executeCommand() return $this for fluent interface.
Backported from 3befbb3 (master).
2013-12-22 17:34:49 +01:00
Daniele Alessandri d88a280977 Make PhpiredisConnection::getAddress() static and protected.
This method is mostly an utility method which is the main reason for
it bein static, but can be useful to have it available when extending
this class.

This change was inspired by b9b899e (master) even though the original
commit is meant to fix an issue not affecting v0.8.
2013-12-22 17:34:49 +01:00
Daniele Alessandri eb6cbef4e7 Remove erroneous duplication of INFO in Redis profiles.
This oversight didn't end up in a blatant bug only because the correct
handler definition for INFO replaced the old class in the profile.

Backported from 5065541 (master).
2013-12-22 17:34:49 +01:00
Daniele Alessandri 2aa771004d Add a note about missing break in switch statement.
[ci skip]
2013-12-22 17:34:49 +01:00
Daniele Alessandri 43bf3096a0 Fix some errors in phpdocs.
Backported from 25cd430 (master).
2013-12-22 17:34:40 +01:00
Daniele Alessandri 317fa42891 Improve handling of -MOVED and -ASK responses.
This change improves code reusing and simplifies the internals of
our redis-cluster connection backend.
2013-12-21 12:00:44 +01:00
Daniele Alessandri b45f87caf2 [tests] Add $message as third parameter for our custom assertions. 2013-12-19 11:51:45 +01:00
Daniele Alessandri b7cba07014 Update README.
[ci skip]
2013-12-18 16:09:01 +01:00
Daniele Alessandri 56627f30f8 Run php-cs against test suite. 2013-12-17 13:00:39 +01:00
Daniele Alessandri 9d4524cae7 Run php-cs against codebase. 2013-12-17 12:59:31 +01:00
Daniele Alessandri fd1c6f244f Apply minor change in command serialization. 2013-12-16 18:52:11 +01:00
Daniele Alessandri aa4d0a05ac Update README.
[ci skip]
2013-12-16 17:05:22 +01:00
Daniele Alessandri d45b5f7ec8 Update FAQ.
[ci skip]
2013-12-16 16:59:23 +01:00
Daniele Alessandri 87ca5a1a49 Update testing README.
[ci skip]
2013-12-16 11:45:50 +01:00
Daniele Alessandri e5ec950a11 [bin] Drop extension from scripts in the bin directory. 2013-12-16 11:26:45 +01:00
Daniele Alessandri 3f2c2b9031 [bin] Handle duplicate class names or aliases in same namespace.
This condition can occur when merging multiple class files (with their
own independent "use" directives) in one big file: when imported class
names or aliases clashes, the script now tries to automatically rename
them by defining a new alias in the "use" directive and renaming the
occurences in the buffer containing the class code.

This fix is naive at best, but seems to work fine so we will just live
with it for now.
2013-12-16 11:26:34 +01:00
Daniele Alessandri 88ea08fe83 [bin] Fix broken rendering of "use ... as ..." in output. 2013-12-16 11:26:21 +01:00
Daniele Alessandri bd48225edc Set the test suite to run against Redis 2.8 on Travis CI.
Please note that the distributed phpunit.xml.dist still targets Redis
2.6 to match the default server profile used by Predis v0.8.
2013-12-14 20:47:23 +01:00
Daniele Alessandri ea9809d4e8 Use path to differentiate persistent TCP streams.
Closes #139.

Backported from master (commit e7f2d28).
2013-12-14 14:21:47 +01:00
Daniele Alessandri 09cb6677e2 Improve URI parsing for connection parameters.
Using PHP's "parse_str()" to parse the query string is slightly more
efficient then our own code especially when the number of fields in
the query string grows, with the additional benefit of supporting
arrays for values when brackets are present in fieldnames.

So after this commit, providing this URI string:

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

Is equivalent to providing the following named array:

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

Other improvements are that parsing does not break when the value of a
field contains one or more "=" and empty or incomplete "key=value"
pairs result in an empty string for "key".
2013-12-11 13:10:05 +01:00
Daniele Alessandri c32f6c7152 Add "path" in phpdoc of connections supporting UNIX domain sockets. 2013-12-11 12:21:09 +01:00
Daniele Alessandri 78fd475747 Apply another minor fix to README.
[ci skip]
2013-12-10 22:16:09 +01:00
Daniele Alessandri 86a4343e59 Fix README.
[ci skip]
2013-12-10 22:01:42 +01:00
Daniele Alessandri ecf1cc07e5 Tweak CONTRIBUTING.md.
[ci skip]
2013-12-09 12:22:42 +01:00
Daniele Alessandri 2fc1ea88cd Last minor tweaks in the README.
Hope I won't need to touch README in v0.8 for a while.

[ci skip]
2013-12-09 12:03:47 +01:00
Daniele Alessandri 72e9187662 More README tweaks.
[ci skip]
2013-12-09 11:53:58 +01:00
Daniele Alessandri 98410bbcaa Pad headers lines in CHANGELOG.
[ci skip]
2013-12-09 11:47:57 +01:00
Daniele Alessandri 5ea1d6fe92 Backport rewrite of README from master (v0.9) with necessary changes.
[ci skip]
2013-12-09 11:42:09 +01:00
Daniele Alessandri 73780da52d Backport improvements for redis-cluster from master (v0.9).
List of changes:

  - The cluster connection sends an ASKING command before retrying
    a command on a different node when Redis returns a -ASK response.
  - The cluster connection can be initialized with a partial list of
    nodes, the full slots map will be fetched from Redis itself using
    the CLUSTER NODES command.
  - The slots map can be optionally retrieved from Redis if the server
    returns a -MOVE response, otherwise only the interested slot will
    be permanently reassigned to the new target node.
  - It is possible to specify a set of common parameters applied to
    connections created on the fly when not part of the current pool
    upon -MOVE and -ASK responses returned by Redis for redirections.
  - $cluster->connect() connects to a random connection in the pool
    instead of forcing the connect operation on all the connections.
2013-12-08 15:57:15 +01:00
Daniele Alessandri 381023ad8f Make sure raw commands IDs are normalized to uppercase. 2013-12-02 11:44:40 +01:00
Daniele Alessandri c3c819cc9c [tests] Minor tweak for timeouts. 2013-12-02 11:44:37 +01:00
Daniele Alessandri 987f27556f Merge branch 'backport/test-suite-enhancements' into v0.8 2013-12-01 15:21:05 +01:00
Daniele Alessandri a9b42924e5 [tests] Improve the basic framework of our test suite.
We now have a base test case class for Predis (namely PredisTestCase)
grouping various commonly used utility methods shared by all of the
tests in the suite, greatly improving reusability.

NOTE: Backported from v0.9-dev.
2013-12-01 15:19:51 +01:00
Daniele Alessandri e4e2238f34 Merge branch 'backport/tests-command-constraint' into v0.8 2013-12-01 14:06:31 +01:00
Daniele Alessandri e1f6c8644f [tests] Normalize casing of command identifiers in constraint.
We do not care much about the casing of command IDs in our constraint,
so it makes no difference for use if it is "SET" or "set".
2013-12-01 14:01:37 +01:00
Daniele Alessandri d16a1c8705 [tests] Add a PHPUnit constraint to verify commands.
Commands are verified by checking their IDs and optionally arguments.
Passing a command instance is only a shortcut to set the expected ID
and arguments, commands are never compared for identity but always for
equivalence.
2013-12-01 13:59:27 +01:00
Daniele Alessandri 32aff28ef5 [tests] Fix stupid change. 2013-11-30 14:58:00 +01:00
Daniele Alessandri eb906bb29c Update CHANGELOG.
[ci skip]
2013-11-30 14:53:08 +01:00
Daniele Alessandri 33ef043b8e [tests] Update tests for Predis\Command\AbstractCommand. 2013-11-30 14:48:26 +01:00
Daniele Alessandri 973c9a7031 Add new command class useful to execute "raw" Redis commands.
By raw we mean that input arguments are not filtered and responses are
not parsed, which means arguments must follow the command signature as
defined by Redis and complex responses are left untouched.

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

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

While higher level abstractions built upon `Predis\Client` should just
use commands created by the profile in use, inner parts of the library
might use raw commands to provide certain functionalities making sure
that input and output of commands are always consistent, independent
of the profile.

NOTE: Backported from v0.9.
2013-11-30 14:37:15 +01:00
Daniele Alessandri db55fae961 Fix phpdoc.
[ci skip]
2013-11-30 14:17:59 +01:00
Daniele Alessandri 5192005862 [tests] Fix tests on PHP 5.3.
Stupid PHP 5.3.
2013-11-29 23:41:05 +01:00
Daniele Alessandri cdc6075b01 [tests] Improve tests of TTL. 2013-11-29 23:33:17 +01:00
Daniele Alessandri 623d1cc1a2 [tests] Improve base command test case. 2013-11-29 23:28:23 +01:00
Daniele Alessandri d53069f096 [tests] Add missing tests for SETNX.
For some obscure reason Predis/Command/StringSetPreserve had no tests.
2013-11-29 22:53:20 +01:00
Daniele Alessandri 8565a42605 [tests] Add "count" argument to SRANDMEMBER (since Redis >= 2.6). 2013-11-29 22:46:32 +01:00
Daniele Alessandri aac811f897 Fix phpdoc.
[ci skip]
2013-11-29 11:14:14 +01:00
Daniele Alessandri 4e664c6c90 Address #133 by reusing our own methods.
The main reason behind that code duplication was performance related
as we tried to reduce method calls when possible, even at the cost of
falling into the realm of early optimizations. Apparently we just lose
~400 req/sec on a 21000 req/sec basis ("SET foo bar") using PHP 5.5.3
(packaged by Ubuntu 13.10) on an Intel Q6600, so we will most likely
stick with this change for the sake of best practices.

Backported from v0.9-dev.
2013-11-22 15:17:50 +01:00
Daniele Alessandri b1a51b2ef9 Update CHANGELOG. 2013-11-09 20:00:33 +01:00
Daniele Alessandri de02c81398 Deprecate Predis\Client::multiExec().
This method will be replaced by Predis\Client::transaction() in the
next major release of Predis.
2013-11-09 20:00:29 +01:00
1399 changed files with 43589 additions and 94056 deletions
-6
View File
@@ -1,6 +0,0 @@
[codespell]
skip=./.git
check-hidden=
check-filenames=
builtin=clear,rare,informal,usage,code,names
ignore-words-list=master,masters,slave,slaves,whitelist,cas,exat,smove,SUGGET,sugget,ro
-24
View File
@@ -1,24 +0,0 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
block_comment_start = /*
block_comment = *
block_comment_end = */
[*.php]
max_line_length = 150
[*.{md,yml,yaml,neon,sh}]
indent_size = 2
[tests/**.php]
max_line_length = unset
[{src/ClientInterface.php,src/ClientContextInterface.php}]
max_line_length = unset
-19
View File
@@ -1,19 +0,0 @@
* text=auto
/.github export-ignore
/bin export-ignore
/examples export-ignore
/tests export-ignore
/.codespellrc export-ignore linguist-language=INI
/.editorconfig export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.php-cs-fixer.dist.php export-ignore
/CHANGELOG.md export-ignore linguist-documentation
/CONTRIBUTING.md export-ignore linguist-documentation
/FAQ.md export-ignore linguist-documentation
/VERSION export-ignore
/phpunit.relay.xml export-ignore
/phpunit.xml.dist export-ignore
/phpstan.dist.neon export-ignore
/phpstan-tests.dist.neon export-ignore
-14
View File
@@ -1,14 +0,0 @@
# Default
* @tillkruss
# Redis commands
src/Command/Redis/* @predis/team @tillkruss
# Redis Stack
src/Command/Redis/Json/* @predis/team
src/Command/Redis/Search/* @predis/team
src/Command/Redis/TopK/* @predis/team
src/Command/Redis/TDigest/* @predis/team
src/Command/Redis/BloomFilter/* @predis/team
src/Command/Redis/CuckooFilter/* @predis/team
src/Command/Redis/CountMinSketch/* @predis/team
-6
View File
@@ -1,6 +0,0 @@
The Predis code of conduct is derived from the Ruby code of conduct. Any violations of the code of conduct may be reported to [Till Krüss](https://till.im):
- Participants will be tolerant of opposing views.
- Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
- When interpreting the words and actions of others, participants should always assume good intentions.
- Behavior that can be reasonably considered harassment will not be tolerated.
-2
View File
@@ -1,2 +0,0 @@
github: tillkruss
custom: "https://www.paypal.me/tillkruss"
-31
View File
@@ -1,31 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Run command '...'
2. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Versions (please complete the following information):**
- Predis: [e.g. 1.1.2]
- PHP [e.g. 8.0.0]
- Redis Server [e.g. 6.0.0]
- OS [e.g. Ubuntu 20.10]
**Code sample**
If applicable, a small snippet of code that reproduces the issue.
**Additional context**
Add any other context about the problem here.
-20
View File
@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: feature
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
-49
View File
@@ -1,49 +0,0 @@
name-template: 'v$NEXT_MINOR_VERSION'
tag-template: 'v$NEXT_MINOR_VERSION'
change-template: '- $TITLE (#$NUMBER)'
autolabeler:
- label: 'maintenance'
files:
- '*.md'
- '.github/*'
- label: 'bug'
branch:
- '/bug-.+'
- label: 'maintenance'
branch:
- '/maintenance-.+'
- label: 'feature'
branch:
- '/feature-.+'
categories:
- title: 'Breaking Changes'
labels:
- 'breakingchange'
- title: 'Experimental Features'
labels:
- 'experimental'
- title: 'New Features'
labels:
- 'feature'
- 'enhancement'
- title: 'Bug Fixes'
labels:
- 'fix'
- 'bugfix'
- 'bug'
- 'BUG'
- title: 'Maintenance'
label: 'maintenance'
exclude-labels:
- 'skip-changelog'
template: |
$CHANGES
## Contributors
We'd like to thank all the contributors who worked on this release!
$CONTRIBUTORS
-9
View File
@@ -1,9 +0,0 @@
# Workflows
## Delete runs by workflow name
```bash
gh run list --workflow 'redis-server-tests.yml' --limit 1000 --json databaseId \
| jq '.[].databaseId' \
| xargs -I % gh api --silent -X DELETE /repos/predis/predis/actions/runs/%
```
-167
View File
@@ -1,167 +0,0 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow
name: Linters
on:
push:
branches:
- main
- v2.**
pull_request: null
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
byte_level:
name: Byte-level
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Check file permissions
run: test "$(find . -type f -not -path './.git/*' -executable)" = "./bin/create-command-test"
- name: "Find non-printable ASCII characters"
run: |
! LC_ALL=C.UTF-8 find . -type f -name '*.php' -print0 \
| xargs --null -- grep --perl-regexp --with-filename --line-number '[^ -~ü]'
syntax_errors:
name: Syntax errors
runs-on: ubuntu-latest
steps:
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: "8.1"
coverage: none
tools: parallel-lint
- name: Checkout repository
uses: actions/checkout@v3
- name: Check source code for syntax errors
run: composer exec -- parallel-lint bin/ examples/ src/ tests/
static_analysis:
name: Static Analysis
needs:
- byte_level
- syntax_errors
runs-on: ubuntu-latest
steps:
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: "8.2"
coverage: none
- name: Checkout repository
uses: actions/checkout@v3
- name: Validate Composer configuration
run: composer validate --no-interaction --strict
- name: Install dependencies
uses: ramsey/composer-install@v2
with:
dependency-versions: highest
- name: Check PSR-4 mapping
run: composer dump-autoload --no-interaction --optimize --strict-psr
- name: Perform static analysis
run: composer run phpstan
- name: Perform static analysis on tests
run: composer run phpstan -- --configuration=phpstan-tests.dist.neon
coding_standards:
name: Coding Standards
needs:
- byte_level
- syntax_errors
runs-on: ubuntu-latest
steps:
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: "8.1"
coverage: none
- name: Checkout repository
uses: actions/checkout@v3
- name: Check EditorConfig configuration
run: test -f .editorconfig
- name: Check adherence to EditorConfig
uses: greut/eclint-action@v0
- name: Install dependencies
uses: ramsey/composer-install@v2
with:
dependency-versions: highest
- name: Check coding style
run: composer exec -- php-cs-fixer fix --diff --dry-run --allow-risky=yes --using-cache=no
- name: Search for TODO-s and FIXME-s
run: |
! git grep --extended-regexp --ignore-case '\b(TODO|FIXME)\b' -- ':/' ':!tests/*' ':!*/linters\.yml'
exported_files:
name: Exported files
needs:
- byte_level
- syntax_errors
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Check exported files
run: |
EXPECTED="LICENSE,README.md,autoload.php,composer.json"
CURRENT="$(
git archive HEAD \
| tar --list --exclude="src" --exclude="src/*" --exclude="bin" --exclude="bin/*" --exclude="docker" --exclude="docker/*" \
| paste --serial --delimiters=","
)"
echo "CURRENT =${CURRENT}"
echo "EXPECTED=${EXPECTED}"
test "${CURRENT}" = "${EXPECTED}"
spelling:
name: Spelling
needs:
- byte_level
- syntax_errors
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Cache pip
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-codespell
- name: Install codespell
run: pip install --user 'codespell>=2.2'
- name: Search for misspellings
run: $(python -m site --user-base)/bin/codespell
-25
View File
@@ -1,25 +0,0 @@
name: Release Drafter
on:
push:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
update_release_draft:
name: Update release draft
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v5
with:
config-name: release-drafter-config.yml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-66
View File
@@ -1,66 +0,0 @@
name: Stack
on:
push:
branches:
- main
- v2.**
pull_request:
jobs:
predis:
name: PHP ${{ matrix.php }} (Redis Stack ${{ matrix.redis }})
runs-on: ubuntu-latest
services:
redis:
image: redis/redis-stack-server:${{ matrix.redis }}
options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3
ports:
- 6379:6379
strategy:
fail-fast: false
matrix:
php:
- '7.2'
- '7.3'
- '7.4'
- '8.0'
- '8.1'
- '8.2'
redis:
- latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup PHP with Composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: relay
- name: Get Composer cache directory
id: composer-cache
run: echo "directory=$(composer config cache-dir)" >> $GITHUB_OUTPUT
- name: Cache Composer dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.directory }}
key: tests-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: tests-php-${{ matrix.php }}-composer
- name: Install Composer dependencies
env:
PHP_VERSION: ${{ matrix.php }}
run: composer install --ansi --no-progress --prefer-dist
- name: Run tests
run: vendor/bin/phpunit --group realm-stack
- name: Run tests using Relay
run: vendor/bin/phpunit --group realm-stack -c phpunit.relay.xml
-123
View File
@@ -1,123 +0,0 @@
name: Tests
on:
push:
branches:
- main
- v2.**
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
predis:
name: PHP ${{ matrix.php }} (Redis ${{ matrix.redis }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php:
- '7.2'
- '7.3'
- '7.4'
- '8.0'
- '8.1'
- '8.2'
redis:
- 3
- 4
- 5
- 6
- 7
services:
redis:
image: redis:${{ matrix.redis }}
options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3
ports:
- 6379:6379
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup PHP with Composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: relay
coverage: ${{ (matrix.php == '8.1' && matrix.redis == '7') && 'xdebug' || 'none' }}
- name: Install Composer dependencies
uses: ramsey/composer-install@v2
with:
dependency-versions: highest
composer-options: ${{ matrix.php == '8.0' && '--ignore-platform-reqs' || '' }}
- name: Run tests
if: ${{ matrix.php != '8.1' || matrix.redis != '7' }}
run: vendor/bin/phpunit
- name: Run tests with coverage
if: ${{ matrix.php == '8.1' && matrix.redis == '7' }}
run: vendor/bin/phpunit --coverage-clover build/logs/clover.xml --coverage-filter ./src
- name: Run tests using Relay
if: ${{ matrix.redis >= '6' }}
run: vendor/bin/phpunit -c phpunit.relay.xml
- name: Send coverage to Coveralls
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
if: ${{ env.COVERALLS_REPO_TOKEN && matrix.php == '8.1' && matrix.redis == '7' }}
run: |
wget "https://github.com/php-coveralls/php-coveralls/releases/download/v2.5.3/php-coveralls.phar"
php ./php-coveralls.phar -v
predis-cluster:
name: PHP ${{ matrix.php }} (Redis Cluster latest)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php:
- '7.2'
- '7.3'
- '7.4'
- '8.0'
- '8.1'
- '8.2'
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Run redis cluster
uses: isbang/compose-action@v1.4.1
with:
compose-file: "./docker/unstable_cluster/docker-compose.yml"
- name: Setup PHP with Composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: relay
coverage: ${{ (matrix.php == '8.1') && 'xdebug' || 'none' }}
- name: Install Composer dependencies
uses: ramsey/composer-install@v2
with:
dependency-versions: highest
composer-options: ${{ matrix.php == '8.0' && '--ignore-platform-reqs' || '' }}
- name: Run tests against cluster
run: |
sleep 5 # Timeout to make sure that docker image is setup
vendor/bin/phpunit --group cluster
+6 -7
View File
@@ -1,8 +1,7 @@
/build
/vendor
.php-version
.php_cs.cache
.phpunit.result.cache
.php-cs-fixer.cache
composer.lock
*.tgz
*.phar
phpunit.xml
package.xml
composer.lock
experiments/
vendor/
-34
View File
@@ -1,34 +0,0 @@
<?php
$PREDIS_HEADER = <<<EOS
This file is part of the Predis package.
(c) 2009-2020 Daniele Alessandri
(c) 2021-2023 Till Krüss
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
EOS;
return (new PhpCsFixer\Config)
->setRules([
'@PHP71Migration' => true,
'header_comment' => ['header' => $PREDIS_HEADER],
'@Symfony' => true,
'phpdoc_separation' => false,
'phpdoc_annotation_without_dot' => false,
'no_superfluous_phpdoc_tags' => false,
'no_unneeded_curly_braces' => false,
'global_namespace_import' => true,
'yoda_style' => false,
'single_line_throw' => false,
'concat_space' => ['spacing' => 'one'],
'increment_style' => false,
])
->setFinder(
PhpCsFixer\Finder::create()
->in(__DIR__ . '/bin')
->in(__DIR__ . '/examples')
->in(__DIR__ . '/src')
->in(__DIR__ . '/tests')
);
+23
View File
@@ -0,0 +1,23 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- hhvm
branches:
except:
- v0.5
- v0.6
- php5.2_backport
- documentation
services: redis-server
before_script:
- composer self-update
- composer install --no-interaction --prefer-source --dev
script:
- vendor/bin/phpunit -c phpunit.xml.travisci
matrix:
allow_failures:
- php: hhvm
fast_finish: true
+53
View File
@@ -0,0 +1,53 @@
# Namespaces, interfaces and classes renamed in Predis v0.8 #
____________________________________________
Some namespaces, interfaces and classes in Predis v0.8 have been renamed to follow a common rule inspired
by the naming conventions adopted by the Symfony2 project. This is a list of all the changes:
### Renamed namespaces ###
- `Predis\Network` => `Predis\Connection`
- `Predis\Profiles` => `Predis\Profile`
- `Predis\Iterators` => `Predis\Iterator`
- `Predis\Options` => `Predis\Option`
- `Predis\Commands` => `Predis\Command`
- `Predis\Commands\Processors` => `Predis\Command\Processor`
### Renamed interfaces ###
- `Predis\IReplyObject` => `Predis\ResponseObjectInterface`
- `Predis\IRedisServerError` => `Predis\ResponseErrorInterface`
- `Predis\Options\IOption` => `Predis\Option\OptionInterface`
- `Predis\Options\IClientOptions` => `Predis\Option\ClientOptionsInterface`
- `Predis\Profile\IServerProfile` => `Predis\Profile\ServerProfileInterface`
- `Predis\Pipeline\IPipelineExecutor` => `Predis\Pipeline\PipelineExecutorInterface`
- `Predis\Distribution\INodeKeyGenerator` => `Predis\Distribution\HashGeneratorInterface`
- `Predis\Distribution\IDistributionStrategy` => `Predis\Distribution\DistributionStrategyInterface`
- `Predis\Protocol\IProtocolProcessor` => `Predis\Protocol\ProtocolInterface`
- `Predis\Protocol\IResponseReader` => `Predis\Protocol\ResponseReaderInterface`
- `Predis\Protocol\IResponseHandler` => `Predis\Protocol\ResponseHandlerInterface`
- `Predis\Protocol\ICommandSerializer` => `Predis\Protocol\CommandSerializerInterface`
- `Predis\Protocol\IComposableProtocolProcessor` => `Predis\Protocol\ComposableProtocolInterface`
- `Predis\Network\IConnection` => `Predis\Connection\ConnectionInterface`
- `Predis\Network\IConnectionSingle` => `Predis\Connection\SingleConnectionInterface`
- `Predis\Network\IConnectionComposable` => `Predis\Connection\ComposableConnectionInterface`
- `Predis\Network\IConnectionCluster` => `Predis\Connection\ClusterConnectionInterface`
- `Predis\Network\IConnectionReplication` => `Predis\Connection\ReplicationConnectionInterface`
- `Predis\Commands\ICommand` => `Predis\Command\CommandInterface`
- `Predis\Commands\IPrefixable` => `Predis\Command\PrefixableCommandInterface`
- `Predis\Command\Processor\ICommandProcessor` => `Predis\Command\Processor\CommandProcessorInterface`
- `Predis\Command\Processor\ICommandProcessorChain` => `Predis\Command\Processor\CommandProcessorChainInterface`
- `Predis\Command\Processor\IProcessingSupport` => `Predis\Command\Processor\CommandProcessingInterface`
### Renamed classes ###
- `Predis\Commands\Command` => `Predis\Command\AbstractCommand`
- `Predis\Network\ConnectionBase` => `Predis\Connection\AbstractConnection`
### Classes or interfaces moved to different namespaces ###
- `Predis\MonitorContext` => `Predis\Monitor\MonitorContext`
- `Predis\ConnectionParameters` => `Predis\Connection\ConnectionParameters`
- `Predis\ConnectionParametersInterface` => `Predis\Connection\ConnectionParametersInterface`
- `Predis\ConnectionFactory` => `Predis\Connection\ConnectionFactory`
- `Predis\ConnectionFactoryInterface` => `Predis\Connection\ConnectionFactoryInterface`
+630 -192
View File
@@ -1,247 +1,685 @@
## Changelog
v0.8.7 (2014-08-01)
================================================================================
## v2.2.1 (2023-08-15)
- 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
- Added support for `WAITAOF` command (#1357)
- Added support for `SHUTDOWN` command (#1359)
- Added support for `FUNCTION` command (#1332)
- Added support for new optional `PEXPIRE`, `PEXPIREAT` and `COMMAND`
- Added missing Redis Stack commands to `KeyPrefixProcessor` (#1358)
- Added `COMMAND` to the server profile for Redis 2.8.
### Changed
- Set client name and version when establishing a connection (#1347)
- 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.
## v2.2.0 (2023-06-14)
- 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.
Predis v2.2.0 introduces official support for [Redis Stack](https://redis.io/docs/stack/) as well as a [Relay](https://github.com/cachewerk/relay) integration for substantially [faster read performance](https://github.com/predis/predis/wiki/Using-Relay).
- Implemented support for `PING` in PUB/SUB loop for Redis >= 3.0.0b8.
### Added
- Added support for [Relay](https://github.com/predis/predis/wiki/Using-Relay) (#1263)
- Added support for `FCALL_RO` command (#1191)
- Added support for Redis `JSON`, `Bloom`, `Search` and `TimeSeries` module (#1253)
- Added support for `ACL SETUSER, GETUSER, DRYRUN` commands (#1193)
- 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.
### Changed
- Minor code style and type-hint changes (#1311)
- __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.
### Fixed
- Fixed prefixes for `XTRIM` and `XREVRANGE` commands (#1230)
- Fixed `fclose()` being called on invalid stream resource (#1199)
- Fixed `BitByte` and `ExpireOptions` traits skip processing on null values (#1169)
- Fixed missing `@return` annotations (#1265)
- Fixed `GETDEL` prefixing (#1306)
## v2.1.2 (2023-03-02)
v0.8.6 (2014-07-15)
================================================================================
### Added
- Added stream commands to `KeyPrefixProcessor` (#1051)
- Added `ReplicationStrategy::$loadBalancing` option to disable replica reads (#1168)
- Added support for `FCALL` and `FUNCTIONS` commands (#1049)
- Added support for `PEXPIRETIME` command (#1031)
- Added support for `EXPIRETIME` command (#1029)
- Added support for `EVAL_RO` command (#1032)
- Added support for `LCS` command (#1035)
- Added support for `SORT_RO` command (#1044)
- Added support for `SINTERCARD` command (#1027)
- Added support for `EVALSHA_RO` command (#1034)
- Added support for new arguments for `BITPOS` and `BITCOUNT` commands (#1045)
- Added support for new arguments for `EXPIRE` and `EXPIREAT` commands (#1046)
- Redis 2.8 is now the default server profile as there are no changes that would
break compatibility with previous releases.
### Bug Fixes
- Fixed deprecated function call syntax
- Added `PFADD`, `PFCOUNT`, `PFMERGE` to the server profile for Redis 2.8 for
handling the HyperLogLog data structure introduced in Redis 2.8.9.
### Deprecated
- Further deprecated phpiredis and webdis integration (#1179)
- Added `ZLEXCOUNT`, `ZRANGEBYLEX`, `ZREMRANGEBYLEX` to the server profile for
Redis 2.8 for handling lexicographic operations on members of sorted sets.
### Maintenance
- Applied coding standards
- Pass PHPStan level 2
- Added support for key hash tags when using redis-cluster (Redis 3.0.0b1).
## v2.1.1 (2023-01-17)
- __FIX__: minor tweaks to make Predis compatible with HHVM >= 2.4.0.
### Bug Fixes
- Fix `@template` in `Predis\Client` (#1017)
- Fix support options array in `ZINTERSTORE` and `ZUNIONSTORE` (#1018)
- __FIX__: responses to `INFO` are now properly parsed and will not break when
redis sentinel is being used (ISSUE #154).
### Deprecated
- Deprecated phpiredis and webdis connections
- __FIX__: added missing support for `INCRBYFLOAT` in cluster and replication
configurations (ISSUE #159).
## v2.1.0 (2023-01-16)
- __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).
### New Features
- Implemented `GETEX` command (#872)
- Implemented `GETDEL` command (#869)
- Implemented `COPY` command (#866)
- Implemented `FAILOVER` command (#875)
- Implemented `LMOVE` command (#863)
- Implemented `LMPOP` command (#1013)
- Implemented `HRANDFIELD` command (#870)
- Implemented `SMISMEMBER` command (#871)
- Implemented `ZMPOP` command (#831)
- Implemented `BLMOVE` command (#865)
- Implemented `BLMPOP` command (#1015)
- Implemented `BZMPOP` command (#833)
- Implemented `BZPOPMIN` command (#862)
- Implemented `BZPOPMAX` command (#864)
- Implemented `ZUNION` command (#860)
- Implemented `ZINTER` command (#859)
- Implemented `ZINTERCARD` command (#861)
- Implemented `ZRANGESTORE` command (#829)
- Implemented `ZDIFFSTORE` command (#828)
- Implemented `ZDIFF` command (#826)
- Implemented `ZRANDMEMBER` command (#825)
- Implemented `ZMSCORE` (#823)
- Implemented `GEOSEARCH` command (#867)
- Implemented `GEOSEARCHSTORE` command (#873)
- __FIX__: prevent a stack overflow when iterating over large Redis collections
using our abstraction for cursor-based iterators (ISSUE #182).
### Bug Fixes
- Added annotations to suppress PHP 8.1 return type deprecation warning (#810)
- __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).
### Maintenance
- Added mixin annotations for traits (#835)
- Upgraded to PHPUnit 4.* for the test suite.
## v2.0.3 (2022-10-11)
- Improved PHP 8.2 support
- Call `is_resource()` before reading/writing
- Added partial support for Redis Stream commands
- Fixed Sentinel authentication issue
v0.8.5 (2014-01-16)
================================================================================
## v2.0.2 (2022-09-06)
- 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.
- Fixed PHP 8.2 deprecation notice: Use of "static" in callables
- Added `SCAN`, `SSCAN`, `ZSCAN`, `HSCAN` to the server profile for Redis 2.8.
## v2.0.1 (2022-09-04)
- Implemented PHP iterators for incremental iterations over Redis collections:
- Added retry interval to `RedisCluster` with a default of `10ms`
- Avoid PHP 8.2 warning in `Connection\Parameters`
- Fixed Sentinel reconnect issue in long-running PHP processes
- 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`)
## v2.0.0 (2022-06-08)
- 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.
- Dropped support for PHP 7.1 and older
- 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:
- Accepted values for some client options have changed, this is the new list of
accepted values:
- 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".
- `aggregate`: callable returning an aggregate connection.
- `cluster`: string value (`predis`, `redis`), callable returning an aggregate
connection.
- `replication`: string value (`predis`, `sentinel`), callable returning an
aggregate connection.
- `commands`: command factory, named array mapping command IDs to PHP classes,
callable returning a command factory or a named array.
- `connections`: connection factory, callable object returning a connection
factory, named array mapping URI schemes to PHP classes, string identifying
a supported combination of configurations for the connection factory.
- `prefix`: string value, command processor, callable.
- `exceptions`: boolean.
- Various improvements and fixes to the redis-cluster connection backend:
Note that both the `cluster` and `replication` options now return a closure
acting as initializer instead of an aggregate connection instance.
- __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.
- The `connections` client option now accepts certain string values identifying
certain combinations of configurations for the connection factory. Currently
this is used to provide a short way to configure Predis to load our phpiredis
based connection backends simply, accepted values are:
- List of deprecated methods:
- `phpiredis-stream` maps `Phpiredis\Connection\PhpiredisStreamConnection` to
`tcp`, `redis`, `unix` URI schemes.
- `phpiredis-socket` maps `Phpiredis\Connection\PhpiredisSocketConnection` to
`tcp`, `redis`, `unix` URI schemes.
- `phpiredis-stream` is simply an alias of `phpiredis-stream`.
- `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.
- Added the new `Predis\Cluster\Hash\PhpiredisCRC16` class using ext-phpiredis
to speed-up the generation of the CRC16 hash of keys for redis-cluster. Predis
automatically uses this class when ext-phpiredis is loaded, but it is possible
to configure the hash generator using the new `crc16` client option (accepted
values `predis`, `phpiredis` or an hash generator instance).
- Replication backends now use the `role` parameter instead of `alias` in order
to distinguish the role of a connection. Accepted values are `master`, `slave`
and, for redis-sentinel, `sentinel`. This led to a redesign of how connections
can be retrieved from replication backends: the method getConnectionById() now
retrieves a connection only by its ID (ip:port pair), to get a connection by
its alias there is the new method getConnectionByAlias(). This method is not
supported by the redis-sentinel backend due to its dynamic nature (connections
are retrieved and initialized at runtime from sentinels) but it is possible to
get a single connection from the pool by using its ID. It is also possible to
retrieve a connection by role using the method getConnectionByRole().
v0.8.4 (2013-07-27)
================================================================================
- The concept of connection ID (ip:port pair) and connection alias (the `alias`
parameter) in `Predis\Connection\Cluster\PredisCluster` has been separated.
This change does not affect distribution and it is safe for existing clusters.
- Added `DUMP` and `RESTORE` to the server profile for Redis 2.6.
- Client option classes now live in the `Predis\Configuration\Option` namespace.
- Connection exceptions now report basic host details in their messages.
- Classes for Redis commands have been moved into the new `Predis\Command\Redis`
namespace and each class name mirrors the respective Redis command ID.
- Allow `Predis\Connection\PhpiredisConnection` to use a random IP when a host
actually has several IPs (ISSUE #116).
- The concept of server profiles is gone, the library now uses a single command
factory to create instances of commands classes. The `profile` option has been
replaced by the `commands` option accepting `Predis\Command\FactoryInterface`
to customize the underlying command factory. The default command factory class
used by Predis is `Predis\Command\RedisFactory` and it still allows developers
to define or override commands with their own implementations. In addition to
that, `Predis\Command\RedisFactory` relies on a convention-over-configuration
approach by looking for a suitable class with the same name as the command ID
in the `Predis\Command\Redis` when the internal class map does not contain a
class associated.
- __FIX__: allow `HMSET` when using a cluster of Redis nodes with client-side
sharding or redis-cluster (ISSUE #106).
- The method `Predis\Client::getClientFor($connectionID)` has been replaced by
`getClientBy($selector, $value, $callable = null)` which is more flexible as
it is not limited to picking a connection from the underlying replication or
cluster backend by ID, but allows users to specify a `$selector` that can be
either `id` (the old behavior), `key`, `slot` or `command`. The client uses
duck-typing instead of type-checking to verify that the underlying connection
implements a method that matches the specified selector which means that some
selectors may not be available to all kinds of connection backends.
- __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).
- The method `Predis\Client::getConnectionById($connectionID)` has been removed.
- __FIX__: scripted commands falling back from `EVALSHA` to `EVAL` resulted in
PHP errors when using a prefixed client (ISSUE #109).
- Changed the signature for the constructor of `Predis\Command\RawCommand`.
- __FIX__: `Predis\PubSub\DispatcherLoop` now works properly when using key
prefixing (ISSUE #114).
- The `Predis\Connection\Aggregate` namespace has been split into two separate
namespaces for cluster backends (`Predis\Connection\Cluster`) and replication
backends (`Predis\Connection\Replication`).
- The method `Predis\Connection\AggregateConnectionInterface::getConnection()`
has been renamed to `getConnectionByCommand()`.
v0.8.3 (2013-02-18)
================================================================================
- The methods `switchToMaster()` and `switchToSlave()` have been promoted to be
part of `Predis\Connection\Replication\ReplicationInterface` while the method
`switchTo($connection)` has been removed from it.
- Added `CLIENT SETNAME` and `CLIENT GETNAME` (ISSUE #102).
- The method `Predis\Connection\Cluster\PredisCluster::executeCommandOnNodes()`
has been removed as it is possible to achieve the same by iterating over the
connection or, even better, over the client instance in order to execute the
same command against all of the registered connections.
- 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.
- The class `Predis\CommunicationException` now uses the correct default types
for the `$message` (string) and `$code` (integer) parameters.
- 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).
- The method `onConnectionError()` in `Predis\Connection\AbstractConnection`
class now passes the second argument as an integer value `0` as its default
value instead of `null`.
- 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)).
- Support Pub/Sub and Pipelines when using replication
- 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.
- The class `Predis\Transaction\AbortedMultiExecException` now uses the correct
default types for the `$code` (integer) parameter.
- 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.
- __FIX__: using `strval` in `getScanOptions()` method, part of
`Predis\Collection\Iterator\CursorBasedIterator` to make sure we retrieve the
string value of `$this->match` and not passing `null` to `strlen()` function.
- The `Predis\Helpers` class has been deprecated and it will be removed in
future releases.
- __FIX__: the value returned from `getArgument()` in `isReadOperation()` method,
part of `Predis\Replication\ReplicationStrategy` class, is checked to not pass
`null` to `sha1` function.
- __FIX__: the value returned from `getArgument()` in `parseResponse()`method,
part of `Predis\Command\Redis\SENTINEL` class, is checked to not pass `null`
to `strtolower()` function.
v0.8.2 (2013-02-03)
================================================================================
## v2.0.0-beta.1 (2022-05-26)
- 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`.
Same as v2.0.0
- 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
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.
+64 -40
View File
@@ -1,9 +1,10 @@
# Frequently asked questions about Predis #
# Some frequently asked questions about Predis #
________________________________________________
## What is the point of 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 reasonably fast. With Predis you can
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
@@ -12,34 +13,21 @@ a great asset since it allows developers to add new and still missing features o
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? ##
### 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? ##
### Does Predis support transparent (de)serialization of values? ###
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.
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).
## Does Predis support transparent (de)serialization of values? ##
When using [Relay](https://github.com/cachewerk/relay) as the underlying client, several
serialization and compression algorithms are supported. This slightly increases CPU usage,
but significantly reduces bytes sent over the network and Redis memory usage.
Without Relay, Predis will not serialize data and will never 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 easilygeneralized 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/predis/predis/issues/29#issuecomment-1202624).
## How can I force Predis to connect to Redis before sending any command? ##
### 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
@@ -59,7 +47,7 @@ try {
$client->info();
```
## How Predis abstracts Redis commands? ##
### 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
@@ -79,20 +67,12 @@ $client->hmset('my:hash', ['field1'=>'value1', 'field2'=>'value2']); // single n
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).
## When should I use Relay? ##
If you care about performance, __always__. [Relay](https://github.com/cachewerk/relay) is free to use.
# Speaking about performances... #
_________________________________________________
## When should I use PhpRedis? ###
Predis is fast enough when Redis is located on the same machine as PHP, more on that later.
[PhpRedis](https://github.com/phpredis/phpredis) (and Relay) perform significantly better when
network I/O is involved, due to their ability to compress data by ~75%. Fewer bytes and received
sent over the network [means faster operations](https://akalongman.medium.com/phpredis-vs-predis-comparison-on-real-production-data-a819b48cbadb),
and potentially cost savings when network traffic isn't free (e.g. AWS Elasticache Inter-AZ transfer costs).
## Predis is a pure-PHP implementation: it can not be fast enough! ##
### 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
@@ -104,7 +84,7 @@ against a local instance of Redis 2.8 that runs under Ubuntu 13.10 on a Intel Q6
0.130 seconds to fetch 30000 keys using _KEYS *_.
```
How does it compare with [__PhpRedis__](http://github.com/phpredis/phpredis), a nice C extension
How does it compare with [__phpredis__](http://github.com/nicolasff/phpredis), a nice C extension
providing an efficient client for Redis?
```
@@ -113,7 +93,7 @@ providing an efficient client for Redis?
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
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
@@ -131,7 +111,7 @@ Using Predis:
3200 GET/sec while retrieving the very same values
0.132 seconds to fetch 30000 keys using "KEYS *".
Using PhpRedis:
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 *".
@@ -143,3 +123,47 @@ that we are measuring the overhead of client libraries implementations and the e
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\PhpiredisConnection` (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\PhpiredisConnection',
),
));
```
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.
+19 -19
View File
@@ -1,22 +1,22 @@
MIT License
Copyright (c) 2009-2014 Daniele Alessandri
Copyright (c) 2009-2020 Daniele Alessandri (original work)
Copyright (c) 2021-2023 Till Krüss (modified work)
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:
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 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.
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.
+175 -251
View File
@@ -1,51 +1,56 @@
# Predis #
[![Software license][ico-license]](LICENSE)
[![Latest stable][ico-version-stable]][link-releases]
[![Latest development][ico-version-dev]][link-releases]
[![Monthly installs][ico-downloads-monthly]][link-downloads]
[![Build status][ico-build]][link-actions]
[![Coverage Status][ico-coverage]][link-coverage]
[![Latest Stable Version](https://poser.pugx.org/predis/predis/v/stable.png)](https://packagist.org/packages/predis/predis)
[![Total Downloads](https://poser.pugx.org/predis/predis/downloads.png)](https://packagist.org/packages/predis/predis)
A flexible and feature-complete [Redis](http://redis.io) client for PHP 7.2 and newer.
Predis is a flexible and feature-complete [Redis](http://redis.io) client library for PHP >= 5.3.
More details about this project can be found on the [frequently asked questions](FAQ.md).
By default Predis does not require any additional C extension, but it can be optionally paired with
[phpiredis](https://github.com/nrk/phpiredis) to lower the overhead of serializing and parsing the
Redis protocol. An asynchronous implementation of the client, albeit experimental, is also available
through [Predis\Async](https://github.com/nrk/predis-async).
Predis can be used with [HHVM](http://www.hhvm.com) >= 2.3.0, but there are no guarantees you will
not run into unexpected issues (especially when the JIT compiler is enabled via `Eval.Jit = true`)
due to HHVM being still under heavy development, thus unstable and not yet 100% compatible with PHP.
More details about the project can be found in our [frequently asked questions](FAQ.md) section or
on the online [wiki](https://github.com/nrk/predis/wiki).
## Main features ##
- Support for Redis from __3.0__ to __7.0__.
- 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`.
- Wide range of Redis versions supported (from __1.2__ to __3.0__ and __unstable__) using profiles.
- Cluster of nodes via client-side sharding using consistent hashing or custom distributors.
- Smart support for [redis-cluster](http://redis.io/topics/cluster-tutorial) (Redis >= 3.0).
- Support for master-slave replication configurations (write on master, read from slaves).
- Transparent key prefixing for all known Redis commands.
- Command pipelining (works on both single nodes and aggregate connections).
- Abstraction for Redis transactions (Redis >= 2.0) supporting CAS operations (Redis >= 2.2).
- Abstraction for Lua scripting (Redis >= 2.6) with automatic switching between `EVALSHA` or `EVAL`.
- Abstraction for `SCAN`, `SSCAN`, `ZSCAN` and `HSCAN` (Redis >= 2.8) based on PHP iterators.
- Connections 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.
- Connections to Redis are established lazily by the client upon the first command.
- Support for both TCP/IP and UNIX domain sockets and persistent connections.
- Support for [Webdis](http://webd.is) (both `ext-curl` and `ext-phpiredis` are needed).
- Support for custom connection classes for providing different network or protocol backends.
- Flexible system for defining custom commands and override the default ones.
- Flexible system for defining and registering custom sets of supported commands or profiles.
## How to _install_ and use Predis ##
## How to 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).
Compressed archives of each release are [available on GitHub](https://github.com/predis/predis/releases).
```shell
composer require predis/predis
```
Predis is available on [Packagist](http://packagist.org/packages/predis/predis) which allows a quick
installation using [Composer](http://packagist.org/about-composer). Alternatively, the library can
be found on our [own PEAR channel](http://pear.nrk.io) for a more traditional installation via PEAR.
Ultimately, archives of each release are [available on GitHub](https://github.com/nrk/predis/tags).
### 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:
[PSR-0 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) which makes
it compatible with most PHP frameworks. Autoloading is handled automatically when dependencies are
managed through Composer, but it is also possible to leverage its own autoloader in projects or
scripts not having any autoload facility:
```php
// Prepend a base path if Predis is not available in your "include_path".
@@ -54,11 +59,17 @@ require 'Predis/Autoloader.php';
Predis\Autoloader::register();
```
It is possible to easily create a [phar](http://www.php.net/manual/en/intro.phar.php) archive from
the repository just by launching `bin/create-phar`. The generated phar contains a stub defining an
autoloader function for Predis, so you just need to require the phar to start using the library.
Alternatively it is possible to generate one single PHP file holding every class like older versions
of Predis by launching `bin/create-single-file`, but this practice __is not__ encouraged.
### Connecting to 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:
When not specifying any connection parameter to create a new client, Predis assumes `127.0.0.1` and
`6379` as the default host and port with a connection timeout of 5 seconds:
```php
$client = new Predis\Client();
@@ -66,203 +77,91 @@ $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:
Connection parameters can be supplied either in the form of URI strings or named arrays. While the
latter is the preferred way to supply parameters, URI strings can be useful for quick configurations
or when parameters are read from a non-structured source:
```php
// Parameters passed using a named array:
// Named array of connection parameters:
$client = new Predis\Client([
'scheme' => 'tcp',
'host' => '10.0.0.1',
'port' => 6379,
]);
// Same set of parameters, passed using an URI string:
// Same set of parameters, but using an URI string:
$client = new Predis\Client('tcp://10.0.0.1:6379');
```
Password protected servers can be accessed by adding `password` to the parameters set. When ACLs are
enabled on Redis >= 6.0, both `username` and `password` are required for user authentication.
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.
Predis can aggregate multiple connections when providing an array of connection parameters and the
appropriate option to instruct the client about how to aggregate them (clustering, replication or a
custom aggregation logic). Named arrays and URI strings can be mixed when providing configurations
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'],
], [
'cluster' => 'predis',
'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.
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.
### 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()`:
Various aspects of the client can be configured simply by passing options to the second argument of
`Predis\Client::__construct()`. Options are managed using a mini DI-alike container and their values
are usually lazily initialized only when needed. Predis by default supports the following options:
```php
$client = new Predis\Client($parameters, ['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:
- `prefix`: prefix string applied to every key found in commands.
- `profile`: which profile to use in order to match a specific version of Redis.
- `prefix`: a prefix string that is automatically applied to keys found in commands.
- `exceptions`: whether the client should throw or return responses upon Redis errors.
- `connections`: list of connection backends or a connection factory instance.
- `cluster`: specifies a cluster backend (`predis`, `redis` or callable).
- `replication`: specifies a replication backend (`predis`, `sentinel` or callable).
- `aggregate`: configures the client with a custom aggregate connection (callable).
- `parameters`: list of default connection parameters for aggregate connections.
- `commands`: specifies a command factory instance to use through the library.
- `connections`: connection backends or a connection factory to be used by the client.
- `cluster`: which backend to use for clustering (`predis`, `redis` or custom configuration).
- `replication`: which backend to use for replication (predis or custom configuration).
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.
Users can provide custom option values, they are stored in the options container and can be accessed
later 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 along with the appropriate client option when creating a new client instance.
#### Cluster ####
Predis can be configured to work in clustering mode with a traditional client-side sharding approach
to create a cluster of independent nodes and distribute the keyspace among them. This approach needs
some sort of external health monitoring of nodes and requires the keyspace to be rebalanced manually
when nodes are added or removed:
```php
$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$options = ['cluster' => 'predis'];
$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);
```
Predis is able to aggregate multiple connections which is the base for cluster and replication. By
default the client implements a cluster of nodes using either client-side sharding (default) or a
Redis-backed solution using [redis-cluster](http://redis.io/topics/cluster-tutorial).
As for replication, Predis can handle a single-master and multiple-slaves setup by executing read
operations on slaves and switching to the master for write operations. The replication behavior is
fully configurable.
#### Replication ####
The client can be configured to 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 by setting the `role` parameter
to `master`) and one or more slaves (in this case setting `role` to `slave` for slaves is optional):
The client can be configured to work in a master / slave replication setup by executing read-only
commands on slave nodes and automatically switch to the master node as soon as a command performing
a write operation is executed. This is the basic configuration needed to work with replication:
```php
$parameters = ['tcp://10.0.0.1?role=master', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$options = ['replication' => 'predis'];
// Parameters require one master node specifically marked with `alias=master`.
$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2?alias=slave-01'];
$options = ['replication' => true];
$client = new Predis\Client($parameters, $options);
```
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,
`EVALSHA` represent a corner case in which the client switches to the master node because it is not
able to tell when a Lua script is safe to be executed on slaves. While this is the default behavior,
when certain Lua scripts do not perform write operations it is possible to provide an hint to tell
the client to stick with slaves for their execution:
the client to stick with slaves for their execution.
```php
$parameters = ['tcp://10.0.0.1?role=master', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2?alias=slave-01'];
$options = ['replication' => function () {
// Set scripts that won't trigger a switch from a slave to the master node.
$strategy = new Predis\Replication\ReplicationStrategy();
$strategy->setScriptReadOnly($LUA_SCRIPT);
return new Predis\Connection\Replication\MasterSlaveReplication($strategy);
return new Predis\Connection\Aggregate\MasterSlaveReplication($strategy);
}];
$client = new Predis\Client($parameters, $options);
@@ -270,8 +169,29 @@ $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.
The `examples` directory contains two complete scripts showing how replication can be configured for
[simple](examples/MasterSlaveReplication.php) or [complex](examples/MasterSlaveReplicationComplex.php)
scenarios.
#### Cluster ####
Simply passing an array of connection parameters to the client constructor configures Predis to work
in cluster mode using client-side sharding. If you, on the other hand, want to leverage Redis >= 3.0
nodes coordinated by redis-cluster, then the client must be initialized like this:
```php
$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2'];
$options = ['cluster' => 'redis'];
$client = new Predis\Client($parameters, $options);
```
When using redis-cluster, it is not necessary to pass all of the nodes that compose your cluster but
you can simply specify only a few nodes: Predis will automatically fetch the full and updated slots
map directly from Redis by contacting one of the servers.
__NOTE__: our support for redis-cluster does not currently consider master / slave replication but
this feature will be added in a future release of this library.
### Command pipelines ###
@@ -282,7 +202,7 @@ The client can execute the pipeline inside a callable block or return a pipeline
ability to chain commands thanks to its fluent interface:
```php
// Executes a pipeline inside the given callable block:
// Executes a pipeline inside a given callable block:
$responses = $client->pipeline(function ($pipe) {
for ($i = 0; $i < 1000; $i++) {
$pipe->set("key:$i", str_pad($i, 4, '0', 0));
@@ -290,7 +210,7 @@ $responses = $client->pipeline(function ($pipe) {
}
});
// Returns a pipeline that can be chained thanks to its fluent interface:
// Returns a pipeline instance with fluent interface:
$responses = $client->pipeline()->set('foo', 'bar')->get('foo')->execute();
```
@@ -301,31 +221,34 @@ The client provides an abstraction for Redis transactions based on `MULTI` and `
interface to command pipelines:
```php
// Executes a transaction inside the given callable block:
// Executes a transaction inside a 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:
// Returns a transaction instance with 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).
of a transaction using CAS you can see [the following example](examples/TransactionWithCAS.php).
__NOTE__: the method `transaction()` is available since `v0.8.5`, older versions used `multiExec()`
for the same purpose but it has been deprecated and will be removed in the next major release.
### Adding new commands ###
### Adding new Redis 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 command factory used by the client:
prefer to stick with an older version of the library or provide a different way to filter arguments
or parse responses for specific commands. To achieve that, Predis provides the ability to implement
new command classes to define or override commands in the server profiles used by the client:
```php
// Define a new command by extending Predis\Command\Command:
class BrandNewRedisCommand extends Predis\Command\Command
// Define a new command by extending Predis\Command\AbstractCommand:
class BrandNewRedisCommand extends Predis\Command\AbstractCommand
{
public function getId()
{
@@ -333,39 +256,28 @@ class BrandNewRedisCommand extends Predis\Command\Command
}
}
// Inject your command in the current command factory:
$client = new Predis\Client($parameters, [
'commands' => [
'newcmd' => 'BrandNewRedisCommand',
],
]);
// 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 command factory used by the client and are accessible as if
they were plain Redis commands, but they define Lua scripts that get transmitted to the server for
remote execution. Internally they use [`EVALSHA`](http://redis.io/commands/evalsha) by default and
identify a script by its SHA1 hash to save bandwidth, but [`EVAL`](http://redis.io/commands/eval)
is used as a fall back when needed:
[`EVAL`](http://redis.io/commands/eval) and [`EVALSHA`](http://redis.io/commands/evalsha), Predis
offers script commands as an higher level abstraction aiming to make things simple. Script commands
can be registered in the server profile used by the client and are accessible as if they were plain
Redis commands, but they define Lua scripts that get transmitted to the server for remote execution.
Internally they use [`EVALSHA`](http://redis.io/commands/evalsha) by default and identify a script
by its SHA1 hash to save bandwidth, but [`EVAL`](http://redis.io/commands/eval) is used as a fall
back when needed:
```php
// Define a new script command by extending Predis\Command\ScriptCommand:
class ListPushRandomValue extends Predis\Command\ScriptCommand
// Define a new scriptable command by extending Predis\Command\ScriptedCommand:
class ListPushRandomValue extends Predis\Command\ScriptedCommand
{
public function getKeysCount()
{
@@ -383,12 +295,9 @@ LUA;
}
}
// Inject the script command in the current command factory:
$client = new Predis\Client($parameters, [
'commands' => [
'lpushrand' => 'ListPushRandomValue',
],
]);
// Inject your script command in the current profile:
$client = new Predis\Client();
$client->getProfile()->defineCommand('lpushrand', 'ListPushRandomValue');
$response = $client->lpushrand('random_values', $seed = mt_rand());
```
@@ -396,22 +305,26 @@ $response = $client->lpushrand('random_values', $seed = mt_rand());
### Customizable connection backends ###
Predis can use different connection backends to connect to Redis. The builtin Relay integration
leverages the [Relay](https://github.com/cachewerk/relay) extension for PHP for major performance
gains, by caching a partial replica of the Redis dataset in PHP shared runtime memory.
Predis can use different connection backends to connect to Redis. Two of them leverage a third party
extension such as [phpiredis](https://github.com/nrk/phpiredis) resulting in major performance gains
especially when dealing with big multibulk responses. While one is based on PHP streams, the other
is based on socket resources provided by `ext-socket`. Both support TCP/IP or UNIX domain sockets:
```php
$client = new Predis\Client('tcp://127.0.0.1', [
'connections' => 'relay',
'connections' => [
'tcp' => 'Predis\Connection\PhpiredisStreamConnection', // PHP streams
'unix' => 'Predis\Connection\PhpiredisConnection', // ext-socket
],
]);
```
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`:
existing ones or provide completely different implementations. Connection classes must implement
`Predis\Connection\SingleConnectionInterface` or extend `Predis\Connection\AbstractConnection`:
```php
class MyConnectionClass implements Predis\Connection\NodeConnectionInterface
class MyConnectionClass implements Predis\Connection\SingleConnectionInterface
{
// Implementation goes here...
}
@@ -432,7 +345,9 @@ implementation of the standard connection classes available in the `Predis\Conne
### 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 issue and pull request templates.
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 ###
@@ -440,27 +355,36 @@ bug fixes, or just bug reports. We only ask you to adhere to issue and pull requ
__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 and that can optionally
perform integration tests against a running instance of Redis (required >= 2.4.0 in order to verify
the correct behavior of the implementation of each command. Integration tests for unsupported Redis
commands are automatically skipped. If you do not have Redis up and running, integration tests can
be disabled. See [the tests README](tests/README.md) for more details about testing this library.
Predis has a comprehensive test suite covering every aspect of the library. This test suite performs
integration tests against a running instance of Redis (>= 2.4.0 is required) to verify the correct
behavior of the implementation of each command and automatically skips commands not defined in the
specified Redis profile. If you do not have Redis up and running, integration tests can be disabled.
By default the test suite is configured to execute integration tests using the profile for Redis 2.8
(which is the current stable version of Redis) but can optionally target a Redis instance built from
the `unstable` branch by modifying `phpunit.xml` and setting `REDIS_SERVER_VERSION` to `dev` so that
the development server profile will be used. You can refer to [the tests README](tests/README.md)
for more detailed information about testing Predis.
Predis uses Travis CI for continuous integration and the history for past and current builds can be
found [on its project page](http://travis-ci.org/nrk/predis).
## Other ##
### Project related links ###
- [Source code](https://github.com/nrk/predis)
- [Wiki](https://wiki.github.com/nrk/predis)
- [Issue tracker](https://github.com/nrk/predis/issues)
- [PEAR channel](http://pear.nrk.io)
### Author ###
- [Daniele Alessandri](mailto:suppakilla@gmail.com) ([twitter](http://twitter.com/JoL1hAHN))
Predis uses GitHub Actions for continuous integration and the history for past and current builds can be
found [on its actions page](https://github.com/predis/predis/actions).
### 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/predis/predis.svg?style=flat-square
[ico-version-stable]: https://img.shields.io/github/v/tag/predis/predis?label=stable&style=flat-square
[ico-version-dev]: https://img.shields.io/github/v/tag/predis/predis?include_prereleases&label=pre-release&style=flat-square
[ico-downloads-monthly]: https://img.shields.io/packagist/dm/predis/predis.svg?style=flat-square
[ico-build]: https://img.shields.io/github/actions/workflow/status/predis/predis/tests.yml?branch=main&style=flat-square
[ico-coverage]: https://img.shields.io/coverallsCoverage/github/predis/predis?style=flat-square
[link-releases]: https://github.com/predis/predis/releases
[link-actions]: https://github.com/predis/predis/actions
[link-downloads]: https://packagist.org/packages/predis/predis/stats
[link-coverage]: https://coveralls.io/github/predis/predis
+1 -1
View File
@@ -1 +1 @@
2.2.1
0.8.7
+3 -1
View File
@@ -3,10 +3,12 @@
/*
* 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';
require __DIR__.'/lib/Predis/Autoloader.php';
Predis\Autoloader::register();
+29 -32
View File
@@ -4,6 +4,8 @@
/*
* 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.
*/
@@ -13,9 +15,9 @@
// 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\Redis\StringSet class):
// Predis\Command\StringSet class):
//
// $ ./bin/generate-command-test --class=StringSet
// $ ./bin/generate-command-test.php --class=StringSet
//
// Here is a list of optional arguments:
//
@@ -38,16 +40,11 @@ class CommandTestCaseGenerator
{
private $options;
public function __construct(array $options)
public function __construct(Array $options)
{
if (!isset($options['class'])) {
throw new RuntimeException("Missing 'class' option.");
}
if (!isset($options['realm'])) {
throw new RuntimeException("Missing 'realm' option.");
}
$this->options = $options;
}
@@ -64,7 +61,7 @@ class CommandTestCaseGenerator
$options = array(
'overwrite' => false,
'tests' => __DIR__.'/../tests/Predis',
'tests' => __DIR__.'/../tests',
);
foreach ($getops as $option => $value) {
@@ -95,20 +92,16 @@ class CommandTestCaseGenerator
throw new RuntimeException("Missing 'class' option.");
}
if (!isset($options['realm'])) {
throw new RuntimeException("Missing 'realm' option.");
}
$options['fqn'] = "Predis\\Command\\{$options['class']}";
$options['path'] = "Predis/Command/{$options['class']}.php";
$options['fqn'] = "Predis\\Command\\Redis\\{$options['class']}";
$options['path'] = "Command/Redis/{$options['class']}.php";
$source = __DIR__.'/../src/'.$options['path'];
$source = __DIR__.'/../lib/'.$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']));
$options['output'] = sprintf("%s/%s", $options['tests'], str_replace('.php', 'Test.php', $options['path']));
}
return new self($options);
@@ -116,11 +109,18 @@ class CommandTestCaseGenerator
protected function getTestRealm()
{
if (empty($this->options['realm'])) {
throw new RuntimeException('Invalid value for realm has been specified (empty).');
if (isset($this->options['realm'])) {
if (!$this->options['realm']) {
throw new RuntimeException('Invalid value for realm has been sepcified (empty).');
}
return $this->options['realm'];
}
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()
@@ -130,16 +130,11 @@ class CommandTestCaseGenerator
if (!$reflection->isInstantiable()) {
throw new RuntimeException("Class $class must be instantiable, abstract classes or interfaces are not allowed.");
}
if (!$reflection->implementsInterface('Predis\Command\CommandInterface')) {
throw new RuntimeException("Class $class must implement Predis\Command\CommandInterface.");
}
/*
* @var CommandInterface
*/
$instance = $reflection->newInstance();
$buffer = $this->getTestCaseBuffer($instance);
return $buffer;
@@ -168,11 +163,13 @@ class CommandTestCaseGenerator
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Redis;
namespace Predis\Command;
/**
* @group commands
@@ -183,7 +180,7 @@ class $class extends PredisCommandTestCase
/**
* {@inheritdoc}
*/
protected function getExpectedCommand(): string
protected function getExpectedCommand()
{
return '$fqn';
}
@@ -191,7 +188,7 @@ class $class extends PredisCommandTestCase
/**
* {@inheritdoc}
*/
protected function getExpectedId(): string
protected function getExpectedId()
{
return '$id';
}
@@ -199,7 +196,7 @@ class $class extends PredisCommandTestCase
/**
* @group disconnected
*/
public function testFilterArguments(): void
public function testFilterArguments()
{
\$this->markTestIncomplete('This test has not been implemented yet.');
@@ -215,7 +212,7 @@ class $class extends PredisCommandTestCase
/**
* @group disconnected
*/
public function testParseResponse(): void
public function testParseResponse()
{
\$this->markTestIncomplete('This test has not been implemented yet.');
@@ -235,7 +232,7 @@ PHP;
/**
* @group disconnected
*/
public function testPrefixKeys(): void
public function testPrefixKeys()
{
\$this->markTestIncomplete('This test has not been implemented yet.');
@@ -251,7 +248,7 @@ PHP;
/**
* @group disconnected
*/
public function testPrefixKeysIgnoredOnEmptyArguments(): void
public function testPrefixKeysIgnoredOnEmptyArguments()
{
\$command = \$this->getCommand();
\$command->prefixKeys('prefix:');
+221
View File
@@ -0,0 +1,221 @@
#!/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($orig,$name,$user,$email) = $regs;
$author['name'] = $name;
$author['user'] = $user;
$author['email'] = $email;
} elseif (count($regs) == 3) {
list($orig,$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],
);
}
}
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 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');
savePackageXml($pkg);
buildPackage();
});
+71
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__ . '/../lib/',
'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(\$class, '\\\', '/').'.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']);
+663
View File
@@ -0,0 +1,663 @@
#!/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__ . "/../lib/",
'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())
{
$nsroot = self::NS_ROOT;
$predisFile = new PredisFile();
$libIterator = new RecursiveDirectoryIterator("$libraryPath$nsroot");
foreach (new RecursiveIteratorIterator($libIterator) as $classFile)
{
if (!$classFile->isFile()) {
continue;
}
$namespace = 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']);
+11 -34
View File
@@ -1,51 +1,28 @@
{
"name": "predis/predis",
"type": "library",
"description": "A flexible and feature-complete Redis client for PHP.",
"description": "Flexible and feature-complete PHP client library for Redis",
"keywords": ["nosql", "redis", "predis"],
"homepage": "http://github.com/predis/predis",
"homepage": "http://github.com/nrk/predis",
"license": "MIT",
"support": {
"issues": "https://github.com/predis/predis/issues"
},
"authors": [
{
"name": "Till Krüss",
"homepage": "https://till.im",
"role": "Maintainer"
}
],
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/tillkruss"
"name": "Daniele Alessandri",
"email": "suppakilla@gmail.com",
"homepage": "http://clorophilla.net"
}
],
"require": {
"php": "^7.2 || ^8.0"
"php": ">=5.3.2"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.3",
"phpstan/phpstan": "^1.9",
"phpunit/phpunit": "^8.0 || ~9.4.4"
"phpunit/phpunit": "~4.0"
},
"suggest": {
"ext-relay": "Faster connection with in-memory caching (>=0.6.2)"
},
"scripts": {
"phpstan": "phpstan analyse",
"style": "php-cs-fixer fix --diff --dry-run",
"style:fix": "php-cs-fixer fix"
"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/"
}
},
"config": {
"sort-packages": true,
"preferred-install": "dist"
},
"minimum-stability": "dev",
"prefer-stable": true
"psr-0": {"Predis": "lib/"}
}
}
-7
View File
@@ -1,7 +0,0 @@
FROM redis/redis-stack-server:latest as rss
COPY create_cluster.sh /create_cluster.sh
RUN ls -R /opt/redis-stack
RUN chmod a+x /create_cluster.sh
ENTRYPOINT [ "/create_cluster.sh"]
-47
View File
@@ -1,47 +0,0 @@
#! /bin/bash
mkdir -p /nodes
touch /nodes/nodemap
if [ -z ${START_PORT} ]; then
START_PORT=6372
fi
if [ -z ${END_PORT} ]; then
END_PORT=6377
fi
if [ ! -z "$3" ]; then
START_PORT=$2
START_PORT=$3
fi
echo "STARTING: ${START_PORT}"
echo "ENDING: ${END_PORT}"
for PORT in `seq ${START_PORT} ${END_PORT}`; do
mkdir -p /nodes/$PORT
if [[ -e /redis.conf ]]; then
cp /redis.conf /nodes/$PORT/redis.conf
else
touch /nodes/$PORT/redis.conf
fi
cat << EOF >> /nodes/$PORT/redis.conf
port ${PORT}
cluster-enabled yes
daemonize yes
logfile /redis.log
dir /nodes/$PORT
EOF
set -x
/opt/redis-stack/bin/redis-server /nodes/$PORT/redis.conf
sleep 1
if [ $? -ne 0 ]; then
echo "Redis failed to start, exiting."
continue
fi
echo 127.0.0.1:$PORT >> /nodes/nodemap
done
if [ -z "${REDIS_PASSWORD}" ]; then
echo yes | /opt/redis-stack/bin/redis-cli --cluster create `seq -f 127.0.0.1:%g ${START_PORT} ${END_PORT}` --cluster-replicas 1
else
echo yes | opt/redis-stack/bin/redis-cli -a ${REDIS_PASSWORD} --cluster create `seq -f 127.0.0.1:%g ${START_PORT} ${END_PORT}` --cluster-replicas 1
fi
tail -f /redis.log
@@ -1,17 +0,0 @@
version: "3.9"
services:
cluster:
container_name: redis-cluster
build:
context: .
dockerfile: Dockerfile
ports:
- "6372:6372"
- "6373:6373"
- "6374:6374"
- "6375:6375"
- "6376:6376"
- "6377:6378"
volumes:
- "./redis.conf:/redis.conf:ro"
-9
View File
@@ -1,9 +0,0 @@
# Redis Cluster config file will be shared across all nodes.
# Do not change the following configurations that are already set:
# port, cluster-enabled, daemonize, logfile, dir
protected-mode no
loadmodule /opt/redis-stack/lib/redisearch.so
loadmodule /opt/redis-stack/lib/redisgraph.so
loadmodule /opt/redis-stack/lib/redistimeseries.so
loadmodule /opt/redis-stack/lib/rejson.so
loadmodule /opt/redis-stack/lib/redisbloom.so
@@ -1,28 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
require __DIR__ . '/../../shared.php';
// Example of JSON.DEBUG command usage:
// 1. Set JSON object
$client = new Client();
$client->jsonset('key', '$', '{"key1":"value1","key2":"value2"}');
// 2. Dump information about json memory usage in bytes
$response = $client->jsondebug->memory('key', '$');
echo 'Response:' . "\n";
print_r($response);
-52
View File
@@ -1,52 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\Search\AggregateArguments;
use Predis\Command\Argument\Search\CreateArguments;
use Predis\Command\Argument\Search\SchemaFields\AbstractField;
use Predis\Command\Argument\Search\SchemaFields\NumericField;
use Predis\Command\Argument\Search\SchemaFields\TextField;
require __DIR__ . '/../../shared.php';
// Example of FT.AGGREGATE command usage:
// 1. Create index
$client = new Client();
$ftCreateArguments = (new CreateArguments())->prefix(['user:']);
$schema = [
new TextField('name'),
new TextField('country'),
new NumericField('dob', '', AbstractField::SORTABLE),
];
$client->ftcreate('idx', $schema, $ftCreateArguments);
// 2. Add documents
$client->hset('user:0', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600);
$client->hset('user:1', 'name', 'Vlad', 'country', 'Israel', 'dob', 782265600);
$client->hset('user:2', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600);
// 3. Execute aggregation query
$ftAggregateArguments = (new AggregateArguments())
->apply('year(@dob)', 'birth')
->groupBy('@country', '@birth')
->reduce('COUNT', true, 'country_birth_Vlad_count')
->sortBy(0, '@birth', 'DESC');
$response = $client->ftaggregate('idx', '@name: "Vlad"', $ftAggregateArguments);
// Response grouped by user country and birth year, with users count in each group, sorted by birth year from DESC.
echo 'Response:' . "\n";
print_r($response);
-41
View File
@@ -1,41 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\Search\SchemaFields\TextField;
require __DIR__ . '/../../shared.php';
// Example of FT.ALTER command usage:
// 1. Create index
$client = new Client();
$schema = [
new TextField('text_field'),
];
$client->ftcreate('index_alter', $schema);
echo 'Default index attributes:' . "\n";
$defaultAttributes = $client->ftinfo('index_alter');
print_r($defaultAttributes[7]);
// 2. Add additional attribute to existing index
$schema = [
new TextField('new_field_name'),
];
$client->ftalter('index_alter', $schema);
echo 'Updated index attributes:' . "\n";
$updatedAttributes = $client->ftinfo('index_alter');
print_r($updatedAttributes[7]);
@@ -1,25 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
require __DIR__ . '/../../shared.php';
// Example of FT.CONFIG HELP command usage:
// 1. Dump helpful information about FT.CONFIG MAXEXPANSIONS option
$client = new Client();
echo 'Response:' . "\n";
print_r(
$client->ftconfig->help('MAXEXPANSIONS')
);
@@ -1,29 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
require __DIR__ . '/../../shared.php';
// Example of FT.CONFIG SET command usage:
// 1. Change default timeout setting
$client = new Client();
echo 'Response:' . "\n";
print_r(
$client->ftconfig->set('TIMEOUT', 42)
);
print_r(
$client->ftconfig->get('TIMEOUT')
);
@@ -1,54 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\Search\AggregateArguments;
use Predis\Command\Argument\Search\CreateArguments;
use Predis\Command\Argument\Search\SchemaFields\AbstractField;
use Predis\Command\Argument\Search\SchemaFields\NumericField;
use Predis\Command\Argument\Search\SchemaFields\TextField;
require __DIR__ . '/../../shared.php';
// Example of FT.CURSOR DEL command usage
// 1. Create index
$client = new Client();
$ftCreateArguments = (new CreateArguments())->prefix(['user:']);
$schema = [
new TextField('name'),
new TextField('country'),
new NumericField('dob', '', AbstractField::SORTABLE),
];
$client->ftcreate('index_cursor_del', $schema, $ftCreateArguments);
// 2. Add documents
$client->hset('user:0', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600);
$client->hset('user:1', 'name', 'Vlad', 'country', 'Israel', 'dob', 782265600);
$client->hset('user:2', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600);
// 3. Execute aggregation query
$ftAggregateArguments = (new AggregateArguments())
->apply('year(@dob)', 'birth')
->groupBy('@country', '@birth')
->reduce('COUNT', true, 'country_birth_Vlad_count')
->sortBy(0, '@birth', 'DESC')
->withCursor(1);
[$_, $cursor] = $client->ftaggregate('index_cursor_del', '@name: "Vlad"', $ftAggregateArguments);
// 4. Explicitly remove returned cursor
$client->ftcursor->del('index_cursor_del', $cursor);
echo "Cursor with cursorId - {$cursor} was removed";
@@ -1,62 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\Search\AggregateArguments;
use Predis\Command\Argument\Search\CreateArguments;
use Predis\Command\Argument\Search\SchemaFields\AbstractField;
use Predis\Command\Argument\Search\SchemaFields\NumericField;
use Predis\Command\Argument\Search\SchemaFields\TextField;
require __DIR__ . '/../../shared.php';
// Example of FT.CURSOR READ command usage
// 1. Create index
$client = new Client();
$ftCreateArguments = (new CreateArguments())->prefix(['user:']);
$schema = [
new TextField('name'),
new TextField('country'),
new NumericField('dob', '', AbstractField::SORTABLE),
];
$client->ftcreate('index_cursor_read', $schema, $ftCreateArguments);
// 2. Add documents
$client->hset('user:0', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600);
$client->hset('user:1', 'name', 'Vlad', 'country', 'Israel', 'dob', 782265600);
$client->hset('user:2', 'name', 'Vlad', 'country', 'Ukraine', 'dob', 813801600);
// 3. Execute aggregation query
$ftAggregateArguments = (new AggregateArguments())
->apply('year(@dob)', 'birth')
->groupBy('@country', '@birth')
->reduce('COUNT', true, 'country_birth_Vlad_count')
->sortBy(0, '@birth', 'DESC')
->withCursor(1);
[$response, $cursor] = $client->ftaggregate('index_cursor_read', '@name: "Vlad"', $ftAggregateArguments);
// 4. Processing response in loop until cursorId exists
$actualResponse = [];
$cursors = [];
while ($cursor) {
$actualResponse[] = $response[1];
$cursors[] = $cursor;
[$response, $cursor] = $client->ftcursor->read('index_cursor_read', $cursor);
}
echo "Response: \n";
print_r($actualResponse);
-32
View File
@@ -1,32 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\Search\SchemaFields\TextField;
require __DIR__ . '/../../shared.php';
// Example of FT.EXPLAIN command usage:
// 1. Create index
$client = new Client();
$schema = [
new TextField('text_field'),
];
$client->ftcreate('index_explain', $schema);
// 2. Run query explanations
$response = $client->ftexplain('index_explain', '(foo bar)|(hello world) @date:[100 200]|@date:[500 +inf]');
echo 'Response:' . "\n";
print_r($response);
-38
View File
@@ -1,38 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\Search\ProfileArguments;
use Predis\Command\Argument\Search\SchemaFields\TextField;
require __DIR__ . '/../../shared.php';
// Example of FT.PROFILE command usage:
// 1. Create index
$client = new Client();
$schema = [
new TextField('text_field'),
];
$client->ftcreate('index_profile', $schema);
// 2. Create FT.PROFILE command arguments
$arguments = (new ProfileArguments())
->search()
->query('query');
// 3. Run profile query
$response = $client->ftprofile('index_profile', $arguments);
echo 'Response:' . "\n";
print_r($response);
@@ -1,40 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\Search\SchemaFields\TextField;
use Predis\Command\Argument\Search\SpellcheckArguments;
require __DIR__ . '/../../shared.php';
// Example of FT.SPELLCHECK command usage:
// 1. Create index
$client = new Client();
$schema = [
new TextField('text_field'),
];
$client->ftcreate('index_spellcheck', $schema);
// 2. Add dictionary with terms
$client->ftdictadd('dict', 'hello', 'help');
// 3. Perform spelling correction query
$response = $client->ftspellcheck(
'index_spellcheck',
'held',
(new SpellcheckArguments())->distance(2)->terms('dict')
);
echo 'Response:' . "\n";
print_r($response);
@@ -1,39 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\Search\SugAddArguments;
use Predis\Command\Argument\Search\SugGetArguments;
require __DIR__ . '/../../shared.php';
// Example of FT.SUGADD, FT.SUGGET, FT.SUGDEL, FT.SUGLEN commands usage:
// 1. Add suggestion to key with payload
$client = new Client();
$client->ftsugadd('key', 'hello', 2, (new SugAddArguments())->payload('payload'));
echo 'Suggestions dictionary length: ' . $client->ftsuglen('key') . "\n";
// 2. Perform fuzzy search by prefix to get previous suggestion with payload
$response = $client->ftsugget('key', 'hellp', (new SugGetArguments())->fuzzy()->withPayloads());
echo 'Suggestion for "hellp" prefix:' . "\n";
print_r($response);
// 3. Removes previous suggestion from key
$client->ftsugdel('key', 'hello');
$response = $client->ftsugget('key', 'hello');
echo 'Suggestions, after removing "hello" suggestion:' . "\n";
print_r($response);
-35
View File
@@ -1,35 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\Search\SchemaFields\TextField;
require __DIR__ . '/../../shared.php';
// Example of FT.SYNDUMP command usage:
// 1. Create index
$client = new Client();
$schema = [
new TextField('text_field'),
];
$client->ftcreate('index_syndump', $schema);
// 2. Add synonyms group with terms
$client->ftsynupdate('index_syndump', 'synonym1', null, 'term1', 'term2');
// 3. Dump terms with synonyms
$response = $client->ftsyndump('index_syndump');
echo 'Response:' . "\n";
print_r($response);
-32
View File
@@ -1,32 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\Search\SchemaFields\TextField;
require __DIR__ . '/../../shared.php';
// Example of FT.SYNUPDATE command usage:
// 1. Create index
$client = new Client();
$schema = [
new TextField('text_field'),
];
$client->ftcreate('index_synupdate', $schema);
// 2. Add synonyms into synonym group
$response = $client->ftsynupdate('index_synupdate', 'synonym1', null, 'term1', 'term2');
echo 'Response:' . "\n";
print_r($response);
-37
View File
@@ -1,37 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\Search\CreateArguments;
use Predis\Command\Argument\Search\SchemaFields\TagField;
require __DIR__ . '/../../shared.php';
// Example of FT.TAGVALS command usage:
// 1. Create index
$client = new Client();
$schema = [
new TagField('tag_field'),
];
$client->ftcreate('index_tagvals', $schema, (new CreateArguments())->prefix(['prefix:']));
// 2. Add indexed tags
$client->hset('prefix:1', 'tag_field', 'Hello, World');
$client->hset('prefix:2', 'tag_field', 'Hey, World');
// 3. Unique tags value query
$response = $client->fttagvals('index_tagvals', 'tag_field');
echo 'Response:' . "\n";
print_r($response);
-38
View File
@@ -1,38 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\AddArguments;
use Predis\Command\Argument\TimeSeries\CommonArguments;
use Predis\Command\Argument\TimeSeries\CreateArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.ADD command usage:
// 1. Create time series
$client = new Client();
$arguments = (new CreateArguments())
->retentionMsecs(60000)
->duplicatePolicy(CommonArguments::POLICY_MAX)
->labels('sensor_id', 2, 'area_id', 32);
$client->tscreate('temperature:2:32', $arguments);
// 2. Add sample into newly created time series
$addArguments = (new AddArguments())
->retentionMsecs(31536000000);
$response = $client->tsadd('temperature:2:32', 123123123123, 27, $addArguments);
echo "Timeseries was added with timestamp: {$response}";
-41
View File
@@ -1,41 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\AlterArguments;
use Predis\Command\Argument\TimeSeries\CommonArguments;
use Predis\Command\Argument\TimeSeries\CreateArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.ALTER command usage:
// 1. Create time series
$client = new Client();
$arguments = (new CreateArguments())
->retentionMsecs(60000)
->duplicatePolicy(CommonArguments::POLICY_MAX)
->labels('sensor_id', 2, 'area_id', 32);
$response = $client->tscreate('temperature:2:32', $arguments);
echo "Time series creation status: {$response}\n";
// 2. Update Duplicate policy for time series above
$arguments = (new AlterArguments())
->duplicatePolicy(CommonArguments::POLICY_FIRST);
$response = $client->tsalter('temperature:2:32', $arguments);
$output = ($response == 'OK') ? 'Duplicate policy was successfully updated' : $response;
echo $output;
@@ -1,31 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\CommonArguments;
use Predis\Command\Argument\TimeSeries\CreateArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.CREATE command usage:
// Create time series
$client = new Client();
$arguments = (new CreateArguments())
->retentionMsecs(60000)
->duplicatePolicy(CommonArguments::POLICY_MAX)
->labels('sensor_id', 2, 'area_id', 32);
$response = $client->tscreate('temperature:2:32', $arguments);
echo "Time series creation status: {$response}";
@@ -1,33 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\CreateArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.CREATERULE command usage:
// 1. Create time series
$client = new Client();
$arguments = (new CreateArguments())
->labels('type', 'temp', 'location', 'TLV');
$createResponse = $client->tscreate('temp:TLV', $arguments);
echo "Original time series creation status: {$createResponse}\n";
$createResponse = $client->tscreate('dailyAvgTemp:TLV', $arguments);
echo "Compacted time series creation status: {$createResponse}\n";
$createRuleResponse = $client->tscreaterule('temp:TLV', 'dailyAvgTemp:TLV', 'avg', 1000);
echo "Compacted rule for compacted time series creation status: {$createRuleResponse}\n";
@@ -1,45 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\AddArguments;
use Predis\Command\Argument\TimeSeries\CommonArguments;
use Predis\Command\Argument\TimeSeries\CreateArguments;
use Predis\Command\Argument\TimeSeries\DecrByArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.DECRBY command usage:
// 1. Create time series
$client = new Client();
$arguments = (new CreateArguments())
->retentionMsecs(60000)
->duplicatePolicy(CommonArguments::POLICY_MAX)
->labels('sensor_id', 2, 'area_id', 32);
$client->tscreate('temperature:2:32', $arguments);
// 2. Add sample into newly created time series
$addArguments = (new AddArguments())
->retentionMsecs(31536000000);
$response = $client->tsadd('temperature:2:32', 123123123123, 27, $addArguments);
echo "Timeseries was added with timestamp: {$response}\n";
// 3. Increasing value and timestamp
$client->tsdecrby('temperature:2:32', 1, (new DecrByArguments())->timestamp(123123123124));
$response = $client->tsget('temperature:2:32');
echo "Decreased value to - {$response[1]} and timestamp to {$response[0]}";
-47
View File
@@ -1,47 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\CommonArguments;
use Predis\Command\Argument\TimeSeries\CreateArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.GET command usage:
// 1. Create time series
$client = new Client();
$arguments = (new CreateArguments())
->retentionMsecs(60000)
->duplicatePolicy(CommonArguments::POLICY_MAX)
->labels('sensor_id', 2, 'area_id', 32);
$client->tscreate('temperature:2:32', $arguments);
// 2. Add samples into time series
$client->tsadd('temperature:2:32', 123123123123, 27);
$client->tsadd('temperature:2:32', 123123123124, 28);
$client->tsadd('temperature:2:32', 123123123125, 29);
$response = $client->tsget('temperature:2:32');
echo "Sample with highest timestamp - {$response[0]} and value {$response[1]}\n";
// 3. Removes 2 samples with the highest timestamps
$response = $client->tsdel('temperature:2:32', 123123123124, 123123123125);
echo "Removed {$response} samples from timeseries.\n";
// 3. Retrieve a timestamp with the highest timestamp
$response = $client->tsget('temperature:2:32');
echo "New sample with highest timestamp - {$response[0]} and value {$response[1]}";
@@ -1,37 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\CreateArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.CREATERULE command usage:
// 1. Create time series
$client = new Client();
$arguments = (new CreateArguments())
->labels('type', 'temp', 'location', 'TLV');
$createResponse = $client->tscreate('temp:TLV', $arguments);
echo "Original time series creation status: {$createResponse}\n";
$createResponse = $client->tscreate('dailyAvgTemp:TLV', $arguments);
echo "Compacted time series creation status: {$createResponse}\n";
$createRuleResponse = $client->tscreaterule('temp:TLV', 'dailyAvgTemp:TLV', 'avg', 1000);
echo "Compacted rule for compacted time series creation status: {$createRuleResponse}\n";
// 2. Remove compaction rule
$deleteRuleResponse = $client->tsdeleterule('temp:TLV', 'dailyAvgTemp:TLV');
echo "Compacted rule for compacted time series deletion status: {$deleteRuleResponse}\n";
-38
View File
@@ -1,38 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\CommonArguments;
use Predis\Command\Argument\TimeSeries\CreateArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.GET command usage:
// 1. Create time series
$client = new Client();
$arguments = (new CreateArguments())
->retentionMsecs(60000)
->duplicatePolicy(CommonArguments::POLICY_MAX)
->labels('sensor_id', 2, 'area_id', 32);
$client->tscreate('temperature:2:32', $arguments);
// 2. Add samples into time series
$client->tsadd('temperature:2:32', 123123123123, 27);
$client->tsadd('temperature:2:32', 123123123124, 28);
$client->tsadd('temperature:2:32', 123123123125, 29);
// 3. Retrieve a timestamp with highest timestamp
$response = $client->tsget('temperature:2:32');
echo "Sample with highest timestamp - {$response[0]} and value - {$response[1]}";
@@ -1,45 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\AddArguments;
use Predis\Command\Argument\TimeSeries\CommonArguments;
use Predis\Command\Argument\TimeSeries\CreateArguments;
use Predis\Command\Argument\TimeSeries\IncrByArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.INCRBY command usage:
// 1. Create time series
$client = new Client();
$arguments = (new CreateArguments())
->retentionMsecs(60000)
->duplicatePolicy(CommonArguments::POLICY_MAX)
->labels('sensor_id', 2, 'area_id', 32);
$client->tscreate('temperature:2:32', $arguments);
// 2. Add sample into newly created time series
$addArguments = (new AddArguments())
->retentionMsecs(31536000000);
$response = $client->tsadd('temperature:2:32', 123123123123, 27, $addArguments);
echo "Timeseries was added with timestamp: {$response}\n";
// 3. Increasing value and timestamp
$client->tsincrby('temperature:2:32', 1, (new IncrByArguments())->timestamp(123123123124));
$response = $client->tsget('temperature:2:32');
echo "Increased value to - {$response[1]} and timestamp to {$response[0]}";
-32
View File
@@ -1,32 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\CommonArguments;
use Predis\Command\Argument\TimeSeries\CreateArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.CREATE command usage:
// 1. Create time series
$client = new Client();
$arguments = (new CreateArguments())
->retentionMsecs(60000)
->duplicatePolicy(CommonArguments::POLICY_MAX)
->labels('sensor_id', 2, 'area_id', 32);
$client->tscreate('temperature:2:32', $arguments);
// 2. Show info about given time series
print_r($client->tsinfo('temperature:2:32'));
-36
View File
@@ -1,36 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\CommonArguments;
use Predis\Command\Argument\TimeSeries\CreateArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.MADD command usage:
// 1. Create time series
$client = new Client();
$arguments = (new CreateArguments())
->retentionMsecs(60000)
->duplicatePolicy(CommonArguments::POLICY_MAX)
->labels('sensor_id', 2, 'area_id', 32);
$client->tscreate('temperature:2:32', $arguments);
$client->tscreate('temperature:2:33', $arguments);
// 2. Add samples into few time series
$response = $client->tsmadd('temperature:2:32', 123123123123, 27, 'temperature:2:33', 123123123124, 28);
$stringResponse = implode(', ', $response);
echo "Samples was added to time series - timestamps: {$stringResponse}";
-41
View File
@@ -1,41 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\CommonArguments;
use Predis\Command\Argument\TimeSeries\CreateArguments;
use Predis\Command\Argument\TimeSeries\MGetArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.MGET command usage:
// 1. Create time series
$client = new Client();
$arguments = (new CreateArguments())
->retentionMsecs(60000)
->duplicatePolicy(CommonArguments::POLICY_MAX)
->labels('type', 'temp', 'sensor_id', 2, 'area_id', 32);
$client->tscreate('temperature:2:32', $arguments);
$client->tscreate('temperature:2:33', $arguments);
// 2. Add samples into time series
$client->tsadd('temperature:2:32', 123123123123, 27);
$client->tsadd('temperature:2:33', 123123123124, 27);
// 3. Get sample from multiple time series matching given filter expression, with selected labels only
$response = $client->tsmget((new MGetArguments())->selectedLabels('type'), 'type=temp');
echo "Sample from time series, with label = 'type':\n";
print_r($response);
@@ -1,48 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\CreateArguments;
use Predis\Command\Argument\TimeSeries\MRangeArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.MRANGE command usage:
// 1. Create time series
$client = new Client();
$response = $client->tscreate('stock:A', (new CreateArguments())->labels('type', 'stock', 'name', 'A'));
echo "Time series A creation status: {$response}\n";
$response = $client->tscreate('stock:B', (new CreateArguments())->labels('type', 'stock', 'name', 'B'));
echo "Time series B creation status: {$response}\n";
// 2. Add samples into both time series
$response = $client->tsmadd('stock:A', 1000, 100, 'stock:A', 1010, 110, 'stock:A', 1020, 120);
$stringResponse = implode(', ', $response);
echo "Added samples into time series A with following timestamps: {$stringResponse}\n";
$response = $client->tsmadd('stock:B', 1000, 120, 'stock:B', 1010, 110, 'stock:B', 1020, 100);
$stringResponse = implode(', ', $response);
echo "Added samples into time series B with following timestamps: {$stringResponse}\n";
// 3. Query range across both time series filtered by "type" and grouped by max type
$mrangeArguments = (new MRangeArguments())
->withLabels()
->filter('type=stock')
->groupBy('type', 'max');
$response = $client->tsmrange('-', '+', $mrangeArguments);
echo "Response:\n";
print_r($response);
@@ -1,48 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\CreateArguments;
use Predis\Command\Argument\TimeSeries\MRangeArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.MREVRANGE command usage:
// 1. Create time series
$client = new Client();
$response = $client->tscreate('stock:A', (new CreateArguments())->labels('type', 'stock', 'name', 'A'));
echo "Time series A creation status: {$response}\n";
$response = $client->tscreate('stock:B', (new CreateArguments())->labels('type', 'stock', 'name', 'B'));
echo "Time series B creation status: {$response}\n";
// 2. Add samples into both time series
$response = $client->tsmadd('stock:A', 1000, 100, 'stock:A', 1010, 110, 'stock:A', 1020, 120);
$stringResponse = implode(', ', $response);
echo "Added samples into time series A with following timestamps: {$stringResponse}\n";
$response = $client->tsmadd('stock:B', 1000, 120, 'stock:B', 1010, 110, 'stock:B', 1020, 100);
$stringResponse = implode(', ', $response);
echo "Added samples into time series B with following timestamps: {$stringResponse}\n";
// 3. Query range across both time series filtered by "type" and grouped by max type
$mrangeArguments = (new MRangeArguments())
->withLabels()
->filter('type=stock')
->groupBy('type', 'max');
$response = $client->tsmrevrange('-', '+', $mrangeArguments);
echo "Response:\n";
print_r($response);
@@ -1,36 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\CreateArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.QUERYINDEX command usage:
// 1. Create time series
$client = new Client();
$arguments = (new CreateArguments())
->labels('type', 'temp', 'location', 'TLV');
$createResponse = $client->tscreate('temp:TLV', $arguments);
echo "Time series with location TLV creation status: {$createResponse}\n";
$anotherArguments = (new CreateArguments())
->labels('type', 'temp', 'location', 'JER');
$createResponse = $client->tscreate('temp:JER', $anotherArguments);
echo "Time series with location JER creation status: {$createResponse}\n";
echo "Returns all keys with location=TLV:\n";
print_r($client->tsqueryindex('location=TLV'));
-40
View File
@@ -1,40 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\CreateArguments;
use Predis\Command\Argument\TimeSeries\RangeArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.RANGE command usage:
// 1. Create time series
$client = new Client();
$createArguments = (new CreateArguments())->labels('type', 'temp', 'location', 'TLV');
$createResponse = $client->tscreate('temp:TLV', $createArguments);
echo "Time series creation status: {$createResponse}\n";
// 2. Add samples into time series
$maddResponse = $client->tsmadd('temp:TLV', 1000, 30, 'temp:TLV', 1010, 35, 'temp:TLV', 1020, 9999, 'temp:TLV', 1030, 40);
$stringResponse = implode(', ', $maddResponse);
echo "Samples was added with following timestamps: {$stringResponse}\n";
// 3. Query samples by values in the given range
$rangeArguments = (new RangeArguments())->filterByValue(-100, 100);
$rangeResponse = $client->tsrange('temp:TLV', '-', '+', $rangeArguments);
echo "Samples with temperature in range -100 to 100 degrees:\n";
print_r($rangeResponse);
@@ -1,40 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\TimeSeries\CreateArguments;
use Predis\Command\Argument\TimeSeries\RangeArguments;
require __DIR__ . '/../../shared.php';
// Example of TS.REVRANGE command usage:
// 1. Create time series
$client = new Client();
$createArguments = (new CreateArguments())->labels('type', 'temp', 'location', 'TLV');
$createResponse = $client->tscreate('temp:TLV', $createArguments);
echo "Time series creation status: {$createResponse}\n";
// 2. Add samples into time series
$maddResponse = $client->tsmadd('temp:TLV', 1000, 30, 'temp:TLV', 1010, 35, 'temp:TLV', 1020, 9999, 'temp:TLV', 1030, 40);
$stringResponse = implode(', ', $maddResponse);
echo "Samples was added with following timestamps: {$stringResponse}\n";
// 3. Query samples by values in the given range
$rangeArguments = (new RangeArguments())->filterByValue(-100, 100);
$rangeResponse = $client->tsrevrange('temp:TLV', '-', '+', $rangeArguments);
echo "Samples with temperature in range -100 to 100 degrees in reverse order:\n";
print_r($rangeResponse);
-36
View File
@@ -1,36 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
require __DIR__ . '/../shared.php';
// Example of ACL DRYRUN command usage:
// 1. Set user with permissions to call only 'SET' command.
$client = new Client($single_server);
$response = $client->acl->setUser('Test_dry', '+SET', '~*');
$created = ($response == 'OK') ? 'Yes' : 'No';
echo "User with username 'Test' was created: {$created}. Permissions only to use SET command\n";
// 2. Dry run 'SET' command under 'Test_dry' user
$response = $client->acl->dryRun('Test_dry', 'SET', 'foo', 'bar');
echo 'Dry run "SET" command.' . "\n";
echo 'Response: ' . $response . "\n";
// 3. Dry run 'GET' command under 'Test_dry' user
$response = $client->acl->dryRun('Test_dry', 'GET', 'foo');
echo 'Dry run "GET" command.' . "\n";
echo 'Response: ' . $response;
-28
View File
@@ -1,28 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
require __DIR__ . '/../shared.php';
// Example of ACL GETUSER command usage:
// 1. Set user
$client = new Client($single_server);
$response = $client->acl->setUser('Test');
// 2. Retrieve user rules:
echo 'Rules: ' . "\n";
print_r(
$client->acl->getUser('Test')
);
-24
View File
@@ -1,24 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
require __DIR__ . '/../shared.php';
// Example of ACL SETUSER command usage:
// 1. Set user
$client = new Client($single_server);
$response = $client->acl->setUser('Test');
$created = ($response == 'OK') ? 'Yes' : 'No';
echo "User with username 'Test' was created: {$created}";
-43
View File
@@ -1,43 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
require __DIR__ . '/../shared.php';
// Example of FCALL_RO command usage:
// 1. Set key-value pair
$client = new Client($single_server);
$client->set('foo', 'bar');
echo "Set key 'foo' with value 'bar'\n";
// 2. Load redis function with 'no-writes' flag
$client->function->load(
"#!lua name=mylib
redis.register_function{
function_name='myfunc',
callback=function(keys, args) return redis.call('GET', keys[1]) end,
flags={'no-writes'}
}"
);
echo 'Loaded custom function that perform GET command against provided key.' . "\n";
// 3. Call function above with given key
$response = $client->fcall_ro('myfunc', ['foo']);
echo "Function returned value against provided key 'foo' is '{$response}'";
// 4. Delete test library
$client->function->delete('mylib');
-41
View File
@@ -1,41 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
require __DIR__ . '/../shared.php';
// Example of WAITAOF command usage:
// 1. Enable appendonly mode if it's not (command works only in appendonly mode)
$client = new Client($single_server);
$info = $client->info();
$enabled = false;
if ($info['Persistence']['aof_enabled'] === '0') {
$client->config('set', 'appendonly', 'yes');
$enabled = true;
}
// 2. Set key value pair
$response = $client->set('foo', 'bar');
echo "Key-value pair set status: {$response}\n";
// 3. Run WAITAOF command to make sure that all previous writes was fsynced
$response = $client->waitaof(1, 0, 0);
echo "Quantity of local instances that was fsynced - {$response[0]}, quantity of replicas - {$response[1]}";
// 4. Disable appendonly mode if it was enabled during script execution
if ($enabled) {
$client->config('set', 'appendonly', 'no');
}
+94
View File
@@ -0,0 +1,94 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require 'SharedConfigurations.php';
// Developers can customize the distribution strategy used by the client
// to distribute keys among a cluster of servers simply by creating a class
// that implements Predis\Distribution\DistributionStrategyInterface.
use Predis\Connection\PredisCluster;
use Predis\Cluster\Distribution\DistributionStrategyInterface;
use Predis\Cluster\Hash\HashGeneratorInterface;
class NaiveDistributionStrategy implements DistributionStrategyInterface, 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 get($key)
{
if (0 === $count = $this->nodesCount) {
throw new RuntimeException('No connections');
}
return $this->nodes[$count > 1 ? abs($key % $count) : 0];
}
public function hash($value)
{
return crc32($value);
}
public function getHashGenerator()
{
return $this;
}
}
$options = array(
'cluster' => function () {
$distributor = new NaiveDistributionStrategy();
$cluster = new PredisCluster($distributor);
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']
);
+78
View File
@@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require 'SharedConfigurations.php';
/*
This 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));
// Create a Predis\DispatcherLoop instance and attach a bunch of callbacks.
$dispatcher = new Predis\PubSub\DispatcherLoop($client);
// Demonstrate how to use a callable class as a callback for Predis\DispatcherLoop.
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!\n";
// Say goodbye :-)
$info = $client->info();
print_r("Goodbye from Redis v{$info['redis_version']}!\n");
+37
View File
@@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require 'SharedConfigurations.php';
// Predis ships with a KeyPrefixProcessor class that is used to transparently
// prefix each key before sending commands to Redis, even for complex commands
// such as SORT, ZUNIONSTORE and ZINTERSTORE. Key prefixes are useful to create
// user-level namespaces for you keyspace, thus eliminating the need for separate
// logical databases.
$client = new Predis\Client($single_server, array('prefix' => 'nrk:'));
$client->mset(array('foo' => 'bar', 'lol' => 'wut'));
var_dump($client->mget('foo', 'lol'));
/*
array(2) {
[0]=> string(3) "bar"
[1]=> string(3) "wut"
}
*/
var_dump($client->keys('*'));
/*
array(2) {
[0]=> string(7) "nrk:foo"
[1]=> string(7) "nrk:lol"
}
*/
+52
View File
@@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require 'SharedConfigurations.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 behaviour of commands or EVAL scripts can be customized at will.
// As soon as a write operation is performed, all the subsequent requests (reads
// or writes) will be served by the master server.
//
// This example must be executed with the second Redis server acting as the slave
// of the first one using 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.\n";
// Write operation.
$client->set('foo', 'bar');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "Now 'foo' has been set to 'bar' on {$current->alias}!\n";
// Read operation.
$bar = $client->get('foo');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "We just fetched 'foo' from {$current->alias} and its value is '$bar'.\n";
/* OUTPUT:
Does 'foo' exist on slave? yes.
Now 'foo' has been set to 'bar' on master!
We just fetched 'foo' from master and its value is 'bar'.
*/
@@ -3,41 +3,40 @@
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
* (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';
require 'SharedConfigurations.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 command
// factory used by the client and marks it as a read-only operation so that it
// will be executed on slaves.
// Predis allows to set Lua scripts as read-only operations in the context of
// replication. This works for both EVAL and EVALSHA and also for the client-side
// abstraction built upon them (Predis\Command\ScriptedCommand). This example
// shows a slightly more complex configuration that injects a new scripted 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\Replication\MasterSlaveReplication;
use Predis\Command\ScriptedCommand;
use Predis\Connection\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.
// Define a new scripted command that returns all the fields
// of a variable number of hashes with a single roundtrip.
class HashMultipleGetAll extends ScriptCommand
class HashMultipleGetAll extends ScriptedCommand
{
public const BODY = <<<LUA
const BODY = <<<EOS
local hashes = {}
for _, key in pairs(KEYS) do
table.insert(hashes, key)
table.insert(hashes, redis.call('hgetall', key))
end
return hashes
LUA;
EOS;
public function getScript()
{
@@ -47,16 +46,19 @@ LUA;
// ------------------------------------------------------------------------- //
$parameters = [
'tcp://127.0.0.1:6381?role=master&database=15',
'tcp://127.0.0.1:6382?role=slave&alias=slave-01&database=15',
];
$parameters = array(
'tcp://127.0.0.1:6379/?alias=master',
'tcp://127.0.0.1:6380/?alias=slave',
);
$options = [
'commands' => [
'hmgetall' => 'HashMultipleGetAll',
],
'replication' => function () {
$options = array(
'profile' => function ($options, $option) {
$profile = $options->getDefault($option);
$profile->defineCommand('hmgetall', 'HashMultipleGetAll');
return $profile;
},
'replication' => function ($options) {
$strategy = new ReplicationStrategy();
$strategy->setScriptReadOnly(HashMultipleGetAll::BODY);
@@ -64,7 +66,7 @@ $options = [
return $replication;
},
];
);
// ------------------------------------------------------------------------- //
@@ -77,7 +79,7 @@ $client = new Predis\Client($parameters, $options);
$hashes = $client->hmgetall('metavars', 'servers');
$replication = $client->getConnection();
$stillOnSlave = $replication->getCurrent() === $replication->getConnectionByAlias('slave-01');
$stillOnSlave = $replication->getCurrent() === $replication->getConnectionById('slave');
echo 'Is still on slave? ', $stillOnSlave ? 'YES!' : 'NO!', PHP_EOL;
echo "Is still on slave? ", $stillOnSlave ? 'YES' : 'NO', "!\n";
var_export($hashes);
@@ -3,22 +3,21 @@
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
* (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';
require 'SharedConfigurations.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
// This is a basic example on how to use the Predis\MonitorContext class.
// You can use redis-cli to send commands to the same Redis instance your client is
// 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 + ['read_write_timeout' => 0]);
$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();
@@ -26,20 +25,20 @@ $timestamp = new DateTime();
foreach (($monitor = $client->monitor()) as $event) {
$timestamp->setTimestamp((int) $event->timestamp);
// If we notice a ECHO command with the message QUIT_MONITOR, we stop the
// monitor consumer and then break the loop.
// If we notice a ECHO command with the message QUIT_MONITOR, we close the
// monitor context and then break the loop.
if ($event->command === 'ECHO' && $event->arguments === '"QUIT_MONITOR"') {
echo 'Exiting the monitor loop...', PHP_EOL;
$monitor->stop();
echo "Exiting the monitor loop...\n";
$monitor->closeContext();
break;
}
echo "* Received {$event->command} on DB {$event->database} at {$timestamp->format(DateTime::W3C)}", PHP_EOL;
echo "* Received {$event->command} on DB {$event->database} at {$timestamp->format(DateTime::W3C)}\n";
if (isset($event->arguments)) {
echo " Arguments: {$event->arguments}", PHP_EOL;
echo " Arguments: {$event->arguments}\n";
}
}
// Say goodbye :-)
$version = redis_version($client->info());
echo "Goodbye from Redis $version!", PHP_EOL;
$info = $client->info();
print_r("Goodbye from Redis v{$info['redis_version']}!\n");
+56
View File
@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require 'SharedConfigurations.php';
// Operations such as LRANGE, ZRANGE and others can potentially generate replies
// containing a huge number of items. In some corner cases, such replies might
// end up exhausting the maximum allowed memory allocated for a PHP process.
// Multibulk iterators can be handy because they allow you to stream multibulk
// replies using plain old PHP iterators, making it possible to iterate them with
// a classic `foreach` loop and avoiding to consume an excessive amount of memory.
//
// PS: please note that multibulk iterators are supported only by the standard
// connection backend class (Predis\Connection\StreamConnection) and not the
// phpiredis-based one (Predis\Connection\PhpiredisConnection).
// Create a client and force the connection to use iterable multibulk responses.
$client = new Predis\Client($single_server + array('iterable_multibulk' => true));
// Prepare an hash with some fields and their respective values.
$client->hmset('metavars', array('foo' => 'bar', 'hoge' => 'piyo', 'lol' => 'wut'));
// By default multibulk iterators iterate over the reply as a list of items...
foreach ($client->hgetall('metavars') as $index => $item) {
echo "[$index] $item\n";
}
/* OUTPUT:
[0] foo
[1] bar
[2] hoge
[3] piyo
[4] lol
[5] wut
*/
// ... but certain multibulk replies are better represented as lists of tuples.
foreach ($client->hgetall('metavars')->asTuple() as $index => $kv) {
list($key, $value) = $kv;
echo "[$index] $key => $value\n";
}
/* OUTPUT:
[0] foo => bar
[1] hoge => piyo
[2] lol => wut
*/
+38
View File
@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require 'SharedConfigurations.php';
// redis can set keys and their relative values in one go
// using MSET, then the same values can be retrieved with
// a single command using MGET.
$mkv = array(
'usr:0001' => 'First user',
'usr:0002' => 'Second user',
'usr:0003' => 'Third user'
);
$client = new Predis\Client($single_server);
$client->mset($mkv);
$retval = $client->mget(array_keys($mkv));
print_r($retval);
/* OUTPUT:
Array
(
[0] => First user
[1] => Second user
[2] => Third user
)
*/
+47
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.
*/
require 'SharedConfigurations.php';
// When you have a whole set of consecutive commands to send to
// a redis server, you can use a pipeline to improve performances.
$client = new Predis\Client($single_server);
$replies = $client->pipeline(function ($pipe) {
$pipe->ping();
$pipe->flushdb();
$pipe->incrby('counter', 10);
$pipe->incrby('counter', 30);
$pipe->exists('counter');
$pipe->get('counter');
$pipe->mget('does_not_exist', 'counter');
});
print_r($replies);
/* OUTPUT:
Array
(
[0] => 1
[1] => 1
[2] => 10
[3] => 40
[4] => 1
[5] => 40
[6] => Array
(
[0] =>
[1] => 40
)
)
*/
@@ -3,58 +3,57 @@
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
* (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';
require 'SharedConfigurations.php';
// Starting from Redis 2.0 clients can subscribe and listen for events published
// on certain channels using a Publish/Subscribe (PUB/SUB) approach.
// Redis 2.0 features new commands that allow clients to subscribe for
// events published on certain channels (PUBSUB).
// Create a client and disable r/w timeout on the socket
$client = new Predis\Client($single_server + ['read_write_timeout' => 0]);
$client = new Predis\Client($single_server + array('read_write_timeout' => 0));
// Initialize a new pubsub consumer.
// Initialize a new pubsub context
$pubsub = $client->pubSubLoop();
// Subscribe to your channels
$pubsub->subscribe('control_channel', 'notifications');
// Start processing the pubsub messages. Open a terminal and use redis-cli
// 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
// ./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;
echo "Subscribed to {$message->channel}\n";
break;
case 'message':
if ($message->channel == 'control_channel') {
if ($message->payload == 'quit_loop') {
echo 'Aborting pubsub loop...', PHP_EOL;
echo "Aborting pubsub loop...\n";
$pubsub->unsubscribe();
} else {
echo "Received an unrecognized command: {$message->payload}.", PHP_EOL;
echo "Received an unrecognized command: {$message->payload}.\n";
}
} else {
echo "Received the following message from {$message->channel}:",
PHP_EOL, " {$message->payload}", PHP_EOL, PHP_EOL;
echo "Received the following message from {$message->channel}:\n",
" {$message->payload}\n\n";
}
break;
}
}
// Always unset the pubsub consumer instance when you are done! The
// Always unset the pubsub context 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;
$info = $client->info();
print_r("Goodbye from Redis v{$info['redis_version']}!\n");
@@ -3,38 +3,35 @@
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
* (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';
require 'SharedConfigurations.php';
use Predis\Collection\Iterator;
// Starting with Redis 2.8, clients can iterate incrementally over collections
// without blocking the server like it happens when a command such as KEYS is
// executed on a Redis instance storing millions of keys. These commands are:
//
// - 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.
// Redis 2.8 features new commands allowing clients to incrementally
// iterate 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) and HSCAN (iterates over fields and values
// of an hash). Predis provides a specialized abstraction for each command
// based on SPL iterators making it possible to easily consume SCAN-based
// iterations in your PHP code.
//
// See http://redis.io/commands/scan for more details.
//
$client = new Predis\Client($single_server);
// 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) {
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");
@@ -48,9 +45,9 @@ foreach (new Iterator\Keyspace($client, 'predis:*') as $key) {
/* OUTPUT
Scan the keyspace matching only our prefixed keys:
- predis:zset
- predis:set
- predis:hash
- predis:zset
- predis:set
- predis:hash
*/
// === Set iterator based on SSCAN ===
@@ -61,11 +58,11 @@ foreach (new Iterator\SetKey($client, 'predis:set') as $member) {
/* OUTPUT
Scan members of `predis:set`:
- member:1
- member:4
- member:0
- member:3
- member:2
- member:1
- member:4
- member:0
- member:3
- member:2
*/
// === Sorted set iterator based on ZSCAN ===
@@ -76,11 +73,11 @@ foreach (new Iterator\SortedSetKey($client, 'predis:zset') as $member => $rank)
/* 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]
- 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 ===
@@ -91,9 +88,9 @@ foreach (new Iterator\HashKey($client, 'predis:hash') as $field => $value) {
/* 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
- field:0 => value:0
- field:1 => value:1
- field:2 => value:2
- field:3 => value:3
- field:4 => value:4
*/
+66
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.
*/
require 'SharedConfigurations.php';
// This example will not work with versions of Redis < 2.6.
//
// Additionally to the EVAL command defined in the current development profile, the new
// Predis\Command\ScriptedCommand base class can be used to build an higher abstraction
// for our "scripted" commands so that they will appear just like any other command on
// the client-side. This is a quick example used to implement INCREX.
use Predis\Command\ScriptedCommand;
class IncrementExistingKeysBy extends ScriptedCommand
{
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);
$client->getProfile()->defineCommand('increxby', 'IncrementExistingKeysBy');
$client->mset('foo', 10, 'foobar', 100);
var_export($client->increxby('foo', 'foofoo', 'foobar', 50));
/*
array (
0 => 60,
1 => NULL,
2 => 150,
)
*/
+39
View File
@@ -0,0 +1,39 @@
<?php
require 'SharedConfigurations.php';
// This example demonstrates how to leverage Predis to save PHP sessions on Redis.
//
// The value of `session.gc_maxlifetime` in `php.ini` will be used by default as the
// the TTL for keys holding session data on Redis, but this value can be overridden
// when creating the session handler instance with the `gc_maxlifetime` option.
//
// Note that this class needs PHP >= 5.4 but can be used on PHP 5.3 if a polyfill for
// SessionHandlerInterface (see http://www.php.net/class.sessionhandlerinterface.php)
// is provided either by you or an external package like `symfony/http-foundation`.
if (!interface_exists('SessionHandlerInterface')) {
die("ATTENTION: the session handler implemented by Predis needs PHP >= 5.4.0 or a polyfill ".
"for \SessionHandlerInterface either provided by you or an external package.\n");
}
// Instantiate a new client just like you would normally do. We'll prefix our session keys here.
$client = new Predis\Client($single_server, array('prefix' => 'sessions:'));
// Set `gc_maxlifetime` so that a session will be expired after 5 seconds since last access.
$handler = new Predis\Session\SessionHandler($client, array('gc_maxlifetime' => 5));
// Register our session handler (it uses `session_set_save_handler()` internally).
$handler->register();
// Set a fixed session ID just for the sake of our example.
session_id('example_session_id');
session_start();
if (isset($_SESSION['foo'])) {
echo "Session has `foo` set to {$_SESSION['foo']}\n";
} else {
$_SESSION['foo'] = $value = mt_rand();
echo "Empty session, `foo` has been set with $value\n";
}
+33
View File
@@ -0,0 +1,33 @@
<?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';
$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',
),
);
@@ -3,19 +3,13 @@
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
* (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 point to understand how your application interacts with
// Redis.
require 'SharedConfigurations.php';
use Predis\Command\CommandInterface;
use Predis\Connection\StreamConnection;
@@ -23,7 +17,7 @@ use Predis\Connection\StreamConnection;
class SimpleDebuggableConnection extends StreamConnection
{
private $tstart = 0;
private $debugBuffer = [];
private $debugBuffer = array();
public function connect()
{
@@ -34,10 +28,10 @@ class SimpleDebuggableConnection extends StreamConnection
private function storeDebug(CommandInterface $command, $direction)
{
$firtsArg = $command->getArgument(0);
$firtsArg = $command->getArgument(0);
$timestamp = round(microtime(true) - $this->tstart, 4);
$debug = $command->getId();
$debug = $command->getId();
$debug .= isset($firtsArg) ? " $firtsArg " : ' ';
$debug .= "$direction $this";
$debug .= " [{$timestamp}s]";
@@ -45,19 +39,19 @@ class SimpleDebuggableConnection extends StreamConnection
$this->debugBuffer[] = $debug;
}
public function writeRequest(CommandInterface $command)
public function writeCommand(CommandInterface $command)
{
parent::writeRequest($command);
parent::writeCommand($command);
$this->storeDebug($command, '->');
}
public function readResponse(CommandInterface $command)
{
$response = parent::readResponse($command);
$reply = parent::readResponse($command);
$this->storeDebug($command, '<-');
return $response;
return $reply;
}
public function getDebugBuffer()
@@ -66,28 +60,29 @@ class SimpleDebuggableConnection extends StreamConnection
}
}
$options = [
'connections' => [
$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());
print_r($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]',
Array
(
[0] => SELECT 15 -> 127.0.0.1:6379 [0.0008s]
[1] => SELECT 15 <- 127.0.0.1:6379 [0.0012s]
[2] => SET foo -> 127.0.0.1:6379 [0.0014s]
[3] => SET foo <- 127.0.0.1:6379 [0.0014s]
[4] => GET foo -> 127.0.0.1:6379 [0.0016s]
[5] => GET foo <- 127.0.0.1:6379 [0.0018s]
[6] => INFO -> 127.0.0.1:6379 [0.002s]
[7] => INFO <- 127.0.0.1:6379 [0.0025s]
)
*/
+25
View File
@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require 'SharedConfigurations.php';
// simple set and get scenario
$client = new Predis\Client($single_server);
$client->set('library', 'predis');
$retval = $client->get('library');
var_dump($retval);
/* OUTPUT
string(6) "predis"
*/
@@ -3,14 +3,13 @@
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
* (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';
require 'SharedConfigurations.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
@@ -20,23 +19,22 @@ require __DIR__ . '/shared.php';
//
// ./redis-cli
// SELECT 15
// ZADD zset 1 a 2 b 3 c
//
// Then execute this script four times and see its output.
//
// ZADD zset 1 a
// ZADD zset 2 b
// ZADD zset 3 c
function zpop($client, $key)
{
$element = null;
$options = [
'cas' => true, // Initialize with support for CAS operations
$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) {
@[$element] = $tx->zrange($key, 0, 0);
@list($element) = $tx->zrange($key, 0, 0);
if (isset($element)) {
$tx->multi(); // With CAS, MULTI *must* be explicitly invoked.
@@ -50,4 +48,4 @@ function zpop($client, $key)
$client = new Predis\Client($single_server);
$zpopped = zpop($client, 'zset');
echo isset($zpopped) ? "ZPOPed $zpopped" : 'Nothing to ZPOP!', PHP_EOL;
echo isset($zpopped) ? "ZPOPed $zpopped" : "Nothing to ZPOP!", "\n";
-118
View File
@@ -1,118 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* 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\Cluster\PredisCluster;
class NaiveDistributor implements DistributorInterface, HashGeneratorInterface
{
private $nodes;
private $nodesCount;
public function __construct()
{
$this->nodes = [];
$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 $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 = [
'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->getClientBy('alias', 'first')->info();
$server2 = $client->getClientBy('alias', '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']
);
-80
View File
@@ -1,80 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* 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 + ['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 = [];
}
public function count()
{
return count($this->events);
}
public function getEvents()
{
return $this->events;
}
public function __invoke($payload, $dispatcher)
{
$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, $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;
-63
View File
@@ -1,63 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* 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 = [
'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([
'MGET', 'uid:0001', 'uid:0002', 'uid:0003',
]);
var_export($response);
echo PHP_EOL;
/* OUTPUT:
array (
0 => '1st user',
1 => '2nd user',
2 => '3rd user',
)
*/
-37
View File
@@ -1,37 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* 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, ['prefix' => 'nrk:']);
$client->mset(['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',
)
*/
-68
View File
@@ -1,68 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* 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, the Predis\Command\ScriptCommand class can
// be used to leverage an higher level abstraction for Lua scripting that makes
// scripts appear just like any other command on the client-side. This is basic
// example on how a script-based INCREX command can be defined:
use Predis\Command\ScriptCommand;
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, [
'commands' => [
'increxby' => 'IncrementExistingKeysBy',
],
]);
$client->mset('foo', 10, 'foobar', 100);
var_export($client->increxby('foo', 'foofoo', 'foobar', 50));
/*
array (
0 => 60,
1 => NULL,
2 => 150,
)
*/
-46
View File
@@ -1,46 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* 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',
),
)
*/
-51
View File
@@ -1,51 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__ . '/shared.php';
// Enable igbinary serializer as well as LZ4 compression
$options = [
'serializer' => 'igbinary',
'compression' => 'lz4',
];
$client = new Predis\Client($single_server + $options, [
'connections' => 'relay',
]);
$quote = (object) [
'author' => 'Jean-Luc Picard',
'text' => 'I look forward to your report Mr. Broccoli.',
];
// Serialize object and apply LZ4 compression, then write key to Redis
$client->set('quote', $client->pack($quote));
// NOTE: In Predis v3.x serialization and compression will happen
// automatically without the need to call `pack()` and `unpack()`
// Retrieve raw binary value from Redis
$raw = $client->get('quote');
// Decompress and unserialize binary value
$data = $client->unpack($raw);
var_dump($quote == $data); // true
var_dump($data);
/*
object(stdClass)#11 (2) {
["author"]=>string(15) "Jean-Luc Picard"
["text"]=>string(43) "I look forward to your report Mr. Broccoli."
}
*/
-55
View File
@@ -1,55 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__ . '/shared.php';
$options = [
'timeout' => 1.0,
'read_write_timeout' => 1.0,
// Relay specific options
'cache' => true,
// 'compression' => 'lz4',
// 'serializer' => 'igbinary',
];
$client = new Predis\Client($single_server + $options, [
'connections' => 'relay',
]);
// Write key to Redis
$client->set('torpedo', mt_rand());
// Retrieve key from Redis
$client->get('torpedo');
// Retrieve key from Relay (without talking to Redis)
// This key is now available to all PHP workers in this FPM pool
$client->get('torpedo');
// For debugging only:
var_export(
$client->getConnection()->getClient()->_getKeys()
);
/*
array (
'torpedo' => array (
0 => array (
'type' => 'string',
'local-len' => 10,
'remote-len' => 10,
'size' => 10,
),
),
)
*/
-61
View File
@@ -1,61 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__ . '/shared.php';
$key = null;
$client = new Predis\Client($single_server, [
'connections' => 'relay',
]);
/** @var Predis\Connection\RelayConnection $relay */
$relay = $client->getConnection();
// establish connection
$client->ping();
// register `FLUSH*` callback
$relay->onFlushed(
static function (Relay\Event $event) use (&$key) {
echo 'Redis was flushed, unsetting $key...' . PHP_EOL;
$key = null;
}
);
// register `INVALIDATE` callback
$relay->onInvalidated(
static function (Relay\Event $event) use (&$key) {
if ($event->key === 'library') {
echo "The `{$event->key}` key was invalidated, unsetting \$key..." . PHP_EOL;
$key = null;
}
}
);
// Write key to Redis
$client->set('library', mt_rand());
// Retrieve key once from Redis, then cached in Relay and $key
$key = $client->get('library');
while (true) {
echo '$key is: ' . var_export($key, true) . PHP_EOL;
// To trigger our event callbacks, we need to either interact with Relay:
$client->get(mt_rand());
// ... or alternatively dispatch events directly on Relay:
$relay->dispatchEvents();
sleep(1);
}
-59
View File
@@ -1,59 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__ . '/shared.php';
// Create a Relay client and disable r/w timeout on the connection
$client = new Predis\Client(
$single_server + ['read_write_timeout' => 0],
['connections' => 'relay']
);
// Initialize a new pubsub consumer.
$pubsub = $client->pubSubLoop();
// When using Relay you cannot use foreach-loops to iterate
// over messages instead use a callback function
$poorMansKafka = function ($message, $client) {
switch ($message->kind) {
case 'subscribe':
echo "Subscribed to {$message->channel}", PHP_EOL;
break;
case 'message':
case 'pmessage':
if ($message->channel == 'control_channel') {
if ($message->payload == 'quit_loop') {
echo 'Aborting pubsub loop...', PHP_EOL;
$client->unsubscribe();
} else {
echo "Received an unrecognized command: {$message->payload}.", PHP_EOL;
}
} else {
echo "Received the message from `{$message->channel}` channel:",
PHP_EOL, " {$message->payload}", PHP_EOL, PHP_EOL;
}
}
};
// Subscribe to your channels and start processing the messages.
$pubsub->subscribe('control_channel', 'notifications', $poorMansKafka);
// 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
// When using Relay, there is no need to unset the pubsub consumer instance when you are done
// Say goodbye :-)
$version = redis_version($client->info());
echo "Goodbye from Redis $version!", PHP_EOL;
-59
View File
@@ -1,59 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* 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 = [
'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, [
'replication' => 'sentinel',
'service' => 'mymaster',
]);
// Read operation.
$exists = $client->exists('foo') ? 'yes' : 'no';
$current = $client->getConnection()->getCurrent()->getParameters();
echo "Does 'foo' exist on {$current->role}? $exists.", PHP_EOL;
// Write operation.
$client->set('foo', 'bar');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "Now 'foo' has been set to 'bar' on {$current->role}!", PHP_EOL;
// Read operation.
$bar = $client->get('foo');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "We fetched 'foo' from {$current->role} 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'.
*/
-53
View File
@@ -1,53 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* 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 = [
'tcp://127.0.0.1:6381?role=master&database=15',
'tcp://127.0.0.1:6382?role=slave&database=15',
];
$options = ['replication' => 'predis'];
$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->role}? $exists.", PHP_EOL;
// Write operation.
$client->set('foo', 'bar');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "Now 'foo' has been set to 'bar' on {$current->role}!", PHP_EOL;
// Read operation.
$bar = $client->get('foo');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "We fetched 'foo' from {$current->role} 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'.
*/
-53
View File
@@ -1,53 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* 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')) {
exit('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, ['prefix' => 'sessions:']);
// Set `gc_maxlifetime` to specify a time-to-live of 5 seconds for session keys.
$handler = new Predis\Session\Handler($client, ['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;
}

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