* Added new hash-field expiration commands
* Changed argument default value
* Marked test as skipped
* Removed version restriction
* Updated CHANGELOG.md
* Added testing with 8.0
* Removed old service
* Moved checkout stage above image
* Changed service name
* Override config options for Redis < 7
* Exported to github env
* Print major version output
* Added double quotes
* Test fixes
* Merge cluster and standalone infrastructure
* Removed separate cluster job and merge together with standalone
* Merge stack tests into main test job
* Fixed annotation condition
* tweaks
* Moved docker-compose.yml to root folder
* Added compose file to expected
* fixed in v0.10.1
* move docker file
* Added -all profile in docker-compose
* Removed redis official image, reduce test matrix, moved docker-compose.yml
* Revert docker-compose moving
* Added test coverage to verify compatibility with 8.0
* Mark tests as relay-incompatible
* Codestyle fix
* Marked as relay-incompatible
---------
Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
* Added testing with 8.0
* Removed old service
* Moved checkout stage above image
* Changed service name
* Override config options for Redis < 7
* Exported to github env
* Print major version output
* Added double quotes
* Test fixes
* Merge cluster and standalone infrastructure
* Removed separate cluster job and merge together with standalone
* Merge stack tests into main test job
* Fixed annotation condition
* tweaks
* Moved docker-compose.yml to root folder
* Added compose file to expected
* fixed in v0.10.1
* move docker file
* Added -all profile in docker-compose
* Removed redis official image, reduce test matrix, moved docker-compose.yml
* Revert docker-compose moving
* test php 8.0 and redis 4.0
* Revert changes related to official image
---------
Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
Added documentation clarifying that predis is compatible with Valkey. This information could be really helpful for developers using Valkey in their projects, saving them time and ensuring they are confident in their choice of predis.
* Added support for INDEXMISSING and INDEXEMPTY arguments
* Added version restriction for test
* Codestyle fixes
* Updated test case to remove DIALECT 5
* Updated another test case
* Added correct versions restriction
---------
Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
* Added test case for enhanced exact matching
* Added version restriction for test
* Updated test case to use Tag Field instead
* Codestyle fix
* Added edge to stack matrix
* Updated test case to switch DIALECT
* Updated version restriction
* Added support for hash field expiration commands
* Added support for HSETF, HGETF commands
* Removed non-printable ASCII chars from docs
* Removed more non-printable ASCII
* Fixed code style issues
* Extended codespell ignore words list
* Added FIELDS argument, fixes related to comments
* Codestyle fixes
* Fixed typo
* Removed HSETF, HGETF commands
* Added 7.4-rc1 to test matrix
* Fixed test with new response types
* Updated tests against 7.4-rc2 image
* Added support for new IGNORE filter
* Fixed arguments to be mandatory
* Added more test cases
* Codestyle fixes
* Added missing test case
* Updated test cases
* Added additional timeout after index creation
* Fixed broken test and removed non-related functionality
* Fixed tests version requirements
* Updated error message
* [2.x] Added Redis 7.4 commands interface changes
* Removed RESP3 test case
* Added 7.4-rc1 to test matrix
* Added 7.4.0-rc1 to stack matrix
* Added version restriction for test
* Updated stack image to edge
* Added GEOSHAPE field and test polygon search
* Added version restriction for test
* Added edge to stack amtrix
* Added same values assertion
* Codestyle changes
* Added new assertion, fixed RESP3 JSON responses and versions
* Removed expectations for error messages
* Changed tests group for RESP3 not supported tests
* Removed 3.x test case
* Added missing test decorators
* Fixed typo
* Removed redisgraph module upload, as not a part of a redis-stack anymore
* Added client metadata on server connection
* Added try...catch around server exception to supress CLIENT command errors
* Added exclusion for Relay connection
* Added support for tests running against redis cluster
* Test coverage
* Added comment about master nodes
* Codestyle fix
* Revert changes
* Revert DBNUM
* Added cluster endpoints to relay tests env configuration
* Exclude cluster tests from relay tests environment
* Removed TODO comment
* Changed cluster image version to unstable
* Updated configuration to match unstable cluster
* Fixed path
* Updated cluster CI configuration
* Removed redundant flag
* Removed backslash
* Updated file path
* Updated file path variable
* Added docker cluster initialization as additional step
* Run cluster tests as separate workflow
* Codestyle fixes
* Updated exported files
* Added additional timeout so cluster image could be settled
* Added support for different cluster image, use docker compose for cluster tests CI
* Remove unused flag
* Removed variable from volume path
* Added sleep timeout to allow docker setup after running
* Added timeout before tests run
* Updated linter settings
* Include indent changes for.sh files
* Added missing coverage
* Revert expected files and mark docker folder as exclusion
* Specify folder itself as excluded
* Moved cluster tests as separate job in tests.yml
* Updated name to contain cluster word
---------
Co-authored-by: Chayim <chayim@users.noreply.github.com>
* Fixed bug with incorrect multiple words processing
* Convert subcommand string to lower case
* Update SubcommandStrategyResolver.php
* Added test coverage
* Codestyle fixes
---------
Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
* Add option for disabling reads to slaves in replication topologies
(cherry picked from commit e132c312a9)
* remove empty line
---------
Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
* Added support for container commands FUNCTION LOAD, FUNCTION DELETE and FCALL
* Changed ContainerInterface and AbstractContainer
* Re-implement logic of abstract methods
---------
Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
* Add complete CI
* Fix CI
* Fix spelling
* Fix indentation
* Fix CI
* Fix CI
* Revert disabling unit tests
* Add coverage driver to CI
* Start coverage debugging
* Fix debugging
ignored, and an empty message aborts the commit.
* Stop debugging
* Move TODO-s from source to GitHub issues
* Ignore too long lines in certain files
* Revert requiring php-parallel-lint/php-parallel-lint
* formatting
* try shorter formatting
* formatting
* fix syntax
* try two paths?
* Update .editorconfig
Co-authored-by: Viktor Szépe <viktor@szepe.net>
* indentation
* make it a group
Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
* Fix almost all PHPStan level 2 problems
* Fix master loop and remove empty line
* Fix variadic functions
* Fix CS
* Disable xdebug in CI and improve a condition
* Fix CI fix
* Added support for SINTERCARD command
* Codestyle fixes, added support for php-cs-fixer
* Moved php-cs-fixer dependency to dev
* Removed trailed comma
* Added support for php 7.2
* update scripts
Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
* Added arrayable arguments classes
* Updated count trait to accept additional modifier argument
* Added new With traits, moved into separate directory
* Added traits for geo command arguments resolving
* Remove BaseWith trait, not working with traits nesting
* Added AscDesc trait
* Rename AscDesc traint into Sorting
* Fixed trait keyword
* Removed unnecessary traits
* Updated count trait to handle default argument value
* Fixed With traits offset check condition
* Added GeoFrom, GeoBy traits
* Fixes for Count and Sorting traits
* Fixed variable names in with traits
* [WIP] Added GEOSEARCH command support, without test coverage
* Changed functionality to support only lower case units (Redis 6.0 support)
* Changed namespace for WITHSCORES trait
* Added more test coverage, added response parsing
* Added test coverage for tratis
* Added support for FAILOVER command
Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
* Added arrayable arguments classes
* Updated count trait to accept additional modifier argument
* Added new With traits, moved into separate directory
* Added traits for geo command arguments resolving
* Remove BaseWith trait, not working with traits nesting
* Added AscDesc trait
* Rename AscDesc traint into Sorting
* Fixed trait keyword
* Removed unnecessary traits
* Updated count trait to handle default argument value
* Fixed With traits offset check condition
* Added GeoFrom, GeoBy traits
* Fixes for Count and Sorting traits
* Fixed variable names in with traits
* [WIP] Added GEOSEARCH command support, without test coverage
* Changed functionality to support only lower case units (Redis 6.0 support)
* Changed namespace for WITHSCORES trait
* Added more test coverage, added response parsing
* Added support for GEOSEARCHSTORE command
* Added test coverage for tratis
Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
Co-authored-by: Chayim <chayim@users.noreply.github.com>
* Added arrayable arguments classes
* Updated count trait to accept additional modifier argument
* Added new With traits, moved into separate directory
* Added traits for geo command arguments resolving
* Remove BaseWith trait, not working with traits nesting
* Added AscDesc trait
* Rename AscDesc traint into Sorting
* Fixed trait keyword
* Removed unnecessary traits
* Updated count trait to handle default argument value
* Fixed With traits offset check condition
* Added GeoFrom, GeoBy traits
* Fixes for Count and Sorting traits
* Fixed variable names in with traits
* [WIP] Added GEOSEARCH command support, without test coverage
* Changed functionality to support only lower case units (Redis 6.0 support)
* Changed namespace for WITHSCORES trait
* Added more test coverage, added response parsing
* Added test coverage for tratis
Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
Co-authored-by: Chayim <chayim@users.noreply.github.com>
* Refactored zinterstore, zunionstore commands and command traits
* Merge conflicts resolve, update cluster strategy test with new arguments
* Updated assertion in case if command executed faster then duration minimal threshold
* Added support for ZINTER command
* Added command link and description
Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
* Refactored zinterstore, zunionstore commands and command traits
* Merge conflicts resolve, update cluster strategy test with new arguments
* Updated assertion in case if command executed faster then duration minimal threshold
* Updated Keys trait to handle cases when no numkeys modifier needed
* Added support for BZPOPMIN command
* Added command link and description
* Added support for BZPOPMAX command
* Added absract command to fix inheritance chain
Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
* Refactored zinterstore, zunionstore commands and command traits
* Merge conflicts resolve, update cluster strategy test with new arguments
* Updated assertion in case if command executed faster then duration minimal threshold
* Added support for ZUNION command
* Added command link and description
Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
* Refactored zinterstore, zunionstore commands and command traits
* Merge conflicts resolve, update cluster strategy test with new arguments
* Updated assertion in case if command executed faster then duration minimal threshold
* Updated Keys trait to handle cases when no numkeys modifier needed
* Added support for BZPOPMIN command
* Added command link and description
Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
* Refactored zinterstore, zunionstore commands and command traits
* Merge conflicts resolve, update cluster strategy test with new arguments
* Updated assertion in case if command executed faster then duration minimal threshold
* Updated Limit trait to handle integer values
* Updated Limit trait to return previous arguments if limit argument isn't set
* Added support for ZINTERCARD command
Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
* Added support for ZRANGESTORE command
* Added missing test coverage, added new filter method
* Added test coverage for new traits
* Updated offset for unexpected value tests
* Added missing offset
* Removed version annotation for inconsistency
* Update ZDIFF.php
* Update ZRANGESTORE.php
Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
* Added support for ZDIFF, added new WITHSCORE trait
* Added trait to resolve numkeys argument
* Added trait for unpacking keys array
* Added static binding for Numkeys trait
* Added ZDIFFSTORE command support
* Changed access modifier type to more strict
* Removed version annotation for inconsistency
* Update ZDIFFSTORE.php
Co-authored-by: Vladislav <vladislav@Admins-MacBook-Pro.local>
Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
* Added support for ZDIFF, added new WITHSCORE trait
* Added trait to resolve numkeys argument
* Added trait for unpacking keys array
* Added static binding for Numkeys trait
* Changed access modifier type to more strict
Co-authored-by: Vladislav <vladislav@Admins-MacBook-Pro.local>
Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
* Added support for ZRANDMEMBER, added test coverage
* Changed key to more specific on exception testing
Co-authored-by: Vladislav <vladislav@Admins-MacBook-Pro.local>
* added annotations to suppress PHP 8.1 return type deprecation warning for `IteratorAggregate::toIterator()`
* move import
Co-authored-by: Josh England <joshua.england@trinet.com>
Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
* set default nopass if password is not provided
* remove debug output
* added tests for noauth sentinel
* Update SentinelReplication.php
Co-authored-by: Mehmet Tolga Avcioglu <mehmet.avcioglu@pusula.net.tr>
Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
* use static::class . '::<method>' instead of 'static::method'
* extract concatenation so it can be improved easier at a later point
one improvement at some point could be to replace static::class . '::<method>' with static::<method>(...) in PHP 8.1+
Co-authored-by: Alexandru Patranescu <alexandru.patranescu@payu.com>
* - init 8.1 tests
- adding `#[\ReturnTypeWillChange]`
- Fix deprecation message: strlen(): Passing null to parameter #1 ($string) of type string is deprecated
- null is not allowed
- cleanup
- default values should be same type
* Mirror `v1.1` fixes for these files
* add null as type too since we also accept null as default value
Signed-off-by: Zaher Ghaibeh <zaher@zaher.dev>
* revert back to assertNull
* Update CHANGELOG
* Update CHANGELOG.md
Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
Password-based authentication for sentinels has been added in Redis 5.
Predis was actively ignoring any "password" parameter for sentinels when
creating connections to them to avoid issues when this parameter is set
in the default "parameters" array passed via client options, as they are
applied to **every** connection created by Predis (see #346).
We need to find a better way to specify a common password for sentinels
to be handled in a different way than the ones for Redis nodes. For now
each sentinel node protected by password must have an explicit password
set in its parameters list even if this password, by design, is the same
for all sentinels. Since we cannot use default "parameters" as explained
above but we still need to pass a common value for all sentinels an idea
could be using a dedicated client option like we did with "service", but
we will see later.
In this commit we also explicitly reset any "username" parameter as it
would trigger an `AUTH $username $password` but sentinels do not support
ACL authentication.
Fixes#594.
While "replication" do accept values evaluating to TRUE, the same cannot
be said for values evaluating to FALSE. TRUE is used to tell the client
that we want replication handled using the default backend for unmanaged
replication setups. For using redis-sentinel the "sentinel" string value
must be passed.
Setting "replication" to FALSE led to a failure (and a PHP warning) on
client initialization because this condition was not handled properly.
Being able to do so would not make sense anyway: when the client does
not need to be set up to rely on replication, users simply have to omit
the option. Furthermore, users must always specify either "replication"
or "cluster" and not both with one of them set to FALSE.
Unfortunately options for aggregate connections in Predis v1.1 are a bit
of a mess, they did not scale well with the addition of new features and
are also quite inconsistent (e.g. "cluster" does not accept TRUE).
This has been largely fixed in Predis v2.0-dev but required implementing
a few breaking changes. It also means that this change does not need to
be ported to the main branch.
Addresses #381 using a different approach.
Basically assertMatchesRegularExpression() replaces assertRegExp() which
has been deprecated since PHPUnit 9.1 and will be removed in PHPUnit 10,
unfortunately we still depend on PHPUnit 8.4 to support PHP 7.2 and this
version does not have assertMatchesRegularExpression() so we implemented
it in our base testcase class with a fallback to the old assertRegExp()
when tests are executed on older versions of PHPUnit.
We still have disabled all PUB/SUB related tests on CI for now, until we
understand why they fail at random.
Backported from main branch (ref. 5133706, f723f67, eb8a89e)
Apparently something changed since last time in cURL internals and now
an IPv4 wrapped by square brackets (which are used for IPv6 addresses)
returns a malformed request error.
m
After this change there was no check to prevent the user from using SSL
with PhpiredisStreamConnection (it does not work due to the fact that
internally is uses stream_socket_recvfrom()).
Pretty obvious phpiredis was not installed on my system when I made that
change or the test suite would have caught the wrong behaviour (like it
just happened now that I build it from scratch).
Apparently something changed since last time in cURL internals and now
an IPv4 wrapped by square brackets (which are used for IPv6 addresses)
returns a malformed request error.
After this change there was no check to prevent the user from using SSL
with PhpiredisStreamConnection (it does not work due to the fact that
internally is uses stream_socket_recvfrom()).
Pretty obvious phpiredis was not installed on my system when I made that
change or the test suite would have caught the wrong behaviour (like it
just happened now that I build it from scratch).
See commit dd5d665 for the above mentioned changes.
Tests for Option\Aggregate, Option\Replication and Option\Cluster should
be refactored because code is a bit too repetitive and a bunch of tests
are shared among them (replication and cluster options extend aggregate
and share the same logic after all).
This is a complete overhaul of how aggregate connections are created and
initialized, now everything is self-contained in our usual 3 supported
client options: "aggregate", "cluster" and "replication".
The usage of callables acting as connection initializerss is now more
consistent through the various options. When the callable is invoked it
receives 3 arguments (the original set of connection parameters passed
by reference, the options container, the current option) and must return
an instance of Predis\Connection\AggregateConnectionInterface otherwise
an InvalidArgumentException is thrown.
When using "cluster" and "replication" the returned aggregate connection
is automatically populated by adding the list of nodes in $parameters,
on the other hand "aggregate" skips this automatism so it is up to the
user. In any case the user-supplied callable receives $parameters as a
reference, setting $parameters to NULL inside the body of the callable
makes the client skip automatic aggregation regardless of the option in
use.
In addition to this the actual procedure of adding nodes to an aggregate
connection has been moved directly into the respective options instead
of being spread between the client (which instead should just pass a set
of parameters and get back a fully-configured aggregate connection) and
the connection factory (and the scope of a connection factory is only to
create new connetion instances to single Redis servers).
Leaving assertParameters() for now but in general we should review how
connection backends are initialized to simplify things and remove some
protected methods, at least from base classes.
The behaviour of a raw command should not ever be overridden by users,
so this change is actually to enforce consistency through the library.
For the most part, raw commands are used internally by Predis to handle
management commands such as CLUSTER SLOTS, ROLE, etc...
The "at" matcher will be removed in PHPUnit 10 but it is not a bad thing
after all because it was cumbersome and error-prone.
Took the opportunity to improve some tests while converting them.
- Make use of more typehints for function parameters
- Make use of typehints for function return values
- Use @var where needed to give proper hints to IDEs and avoid warnings
- Replace MockObject::setMethods() with addMethods() and onlyMethods()
- Rewording of some phpdocs
The username is now correctly retrieved from the userinfo fragment of
the URI when using the "redis" scheme and a "username:password" pair is
present. Values retrieved from the userinfo fragment always override the
ones specified in `username` and `password` if those fields are present
in the query string.
The option handler has been extended to accept descriptive string values
in order to create and configure an appropriate command factory.
Accepted string values are:
- "predis" creates the usual command factory
- "raw" creates a raw command factory
- "default" is simply an alias of "predis"
Any command ID will produce a command instance even for unknown commands
not implemented by Redis (the server will return a -ERR error response).
When using this factory the client does not process arguments before
sending commands to Redis and server responses are not further processed
before being returned to the caller.
When passing both "username" and "password" to connection parameters the
client now uses the extended AUTH command to support ACL authentication
with Redis 6.0.
The plain old authentication method is still supported like usual simply
by passing only "password" to connection parameters.
NULL or zero-length string values passed to "password" and "database" in
the connection parameters list do not trigger spurious AUTH and SELECT
commands anymore when connecting to Redis.
Fixes#436.
We have renamed most methods to drop the "command" suffix as it is quite
redundant. Due to this change and thanks to variadic methods introduced
with PHP 5.6 we took the opportunity to replace both "supportsCommand()"
and "supportsCommands()" with a single new method "supports()".
Added more stringent typehints for method arguments and typehints for
return values now that we do not need to support anything below PHP 7.2.
Also moved from using array() to [] in source code of class involved.
Changes backported from the main branch (ref. 04d5c10, 5afadb5).
This is just a temporary solution, we will revert this change as soon as
the actual cause for the spurious failures is identified.
Using "default" returns a connection factory instance with the default
configuration. Basically it is no different than omitting "connections"
in the client options array but it can be useful when applications want
to automatically configure Predis to use phpiredis when it is loaded:
$client = new Predis\Client('tcp://127.0.0.1', [
'connections' =>
extension_loaded('phpiredis')
? 'phpiredis'
: 'default'
]);
This is in response to ISSUE #397.
The "connections" client option now accepts certain string values that
are mapped to specific and most used configurations for the connection
factory. This is used to make it easier to configure Predis to load our
phpiredis-based connection backends without having to manually pass a
map of URI schemes and fully-qualified class names.
- "phpiredis-stream" maps `tcp` and `unix` to the connection backend
based on PHP streams (Predis\Connection\PhpiredisStreamConnection).
- "phpiredis-socket" maps `tcp` and `unix` to the connection backend
based on ext-socket (Predis\Connection\PhpiredisStreamConnection).
- `phpiredis` is simply an alias of `phpiredis-stream`.
An InvalidArgumentException is thrown on unsupported string values.
NOTE: this specific test fails at random without any apparent reason
when executed on our CI environments and these failures are not tied
to a particular version of PHP or Redis. It is most likely some weird
timing issue on busy systems as it is really rare to get it triggered
locally. The chances it is a bug in the library are pretty low so for
now we just mark this test skipped on our CI environments (but still
enabled for local test runs) and "debug" this issue using a separate
branch to avoid having spurious failures on main development branches
which is utterly annoying.
We will restore this test on CI environments as soon as we understand
what is the reason behind its random failures.
These failures are random and rarely reproducible on a local development
environment but sometimes they affect the success of a test run and it's
annoying. I think it is just a weird timing issue on busy hosts so let's
try with a couple of simple usleep() after SUBSCRIBE and PUBLISH and see
if anything changes in the next few test runs.
We have removed the utility script for building phar archives out of the
repository, but if you still need to build a phar archive for Predis you
should still be able to use "./bin/create-phar" from the v1.1 branch.
Having NULL values or zero-length strings for connection parameters does
not make much sense and actually it proved to be an issue with certain
parameters like "password" where an empty string would trigger an AUTH
command with an empty password (and obviously Redis was not happy with
that). The main offenders were a few libraries and frameworks that kept
passing empty values for parameters such as "database" and "password"
even when users left them unconfigured. This fix should make things more
robust and avoid such occurrences in the future.
Related to PR #436 (rejected).
Remove .github from directories exported in our release packages and fix
to ignore .php_cs.dist (the actual file commited) instead of .php_cs.
[ci skip]
Remove .github from directories exported in our release packages and fix
to ignore .php_cs.dist (the actual file commited) instead of .php_cs.
This also reverts commit 7489690 (slipped into main after merging v1.1),
we do not need it since there is no patching going on for PHPUnit after
our target version was bumped to ^8.0 || ^9.0 on main.
Starting with Redis 6.0 and the introduction of Access Control Lists,
COMMAND INFO returns an additional array for each specified command in
the request with a list of the ACL categories associated to a command.
The previous implementation was not good because we were exposing in the
public API an internal implementation detail of the base factory class,
furthermore it made Predis\Command\Factory::define() confusing. Having a
separate method to undefine commands in the factory is self-explanatory.
We also changed `Predis\Configuration\Option\Commands` accordingly when
a dictionary of $commandID => $classCommand is passed to the "commands"
client option and $classCommand is NULL.
A few minor changes (mostly cosmetic or documentation) were applied too.
From feedback to PR #644.
There was no real meaning to have a callback here, for the most part it
was just a leftover of a previous approach implemented with ee7104d and
quickly superseded by the current approach that simply returns the new
client instance instead of using callbacks.
From feedback to #644.
This is meant as a basic layer of protection for those cases where the whole
Predis repository is cloned into a path which is publicly exposed by the web
server.
Closes#408
This change does not affect distribution but simply separates the two
concepts of connection ID (ip:port pair) and alias (value set via the
"alias" connection parameter), the method getConnectionByAlias() has
been added to reflect this change.
The method getConnectionBySlot() has also been added.
When using replication backends, now the role of a connection is not
defined by its alias but by the new connection parameter "role" that
can be set to "master", "slave" and (for redis-sentinel) "sentinel".
This also 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 still possible to get a single connection from the pool by
using its ID. It is also possible to retrive a connection by its role
using the method getConnectionByRole().
NOTE: the "role" parameter is an hint for the internals of aggregate
connection backends so it is still possible that the actual role of
a connection changes during the execution of a script (e.g. a slave
gets promoted to the role of master): in this case the parameters of
that connection will not be changed as they are immutable, but the
method getConnectionByRole() will return the appropriate connection.
Predis\Client::getClientBy() has been updated with the addition of
"role" and "alias" as supported selectors.
In addition to that, the methods askSlotsMap() and buildSlotsMap() of
the redis-cluster connection backend have been renamed respectively
to askSlotMap() and buildSlotMap().
By default Predis chooses automatically which hash generator to use:
if a suitable version of ext-phpiredis is loaded (that is, a version
that implements the phpiredis_utils_crc16() function) it uses the new
Predis\Cluster\Hash\PhpiredisCRC16 generator, otherwise it falls back
to the usual pure-PHP Predis\Cluster\Hash\CRC16 generator.
Generating the hash of a string of 10 bytes is 10 times faster than
the equivalent pure-PHP implementation of CRC16 (PHP 7.0.7).
This generator requires a version of phpiredis that implements the
function "phpiredis_utils_crc16()".
This method improves the previous one, which worked only by picking a
connection by ID, allowing users to specify a selector type among the
following ones: "id", "key", "slot", "command". These selectors are
expanded internally to methods, invoked against the connection object
in use by the client, that follow the getConnectionByXXX() convention
already in use through the library:
id => getConnectionById()
key => getConnectionByKey()
slot => getConnectionBySlot()
command => getConnectionByCommand()
The underlying connection does not necessarily need to implement the
aggregate connection interface as the client relies on a duck-typing
approach by using method_exists().
The following code, despite not being something you would do in real
world scenarios, eventually ended up generating an exception for "Too
many open files".
for ($i = 0; $i < 10000; $i++) {
$client = new Predis\Client();
$client->connect();
}
The reason was that the destructor for the connection was not invoked
by PHP as expected when the client instance went out of scope, so the
underlying stream resource were not being properly released.
Even without an actual "connect()" operation, the memory usage of PHP
kept growing until exhausting the configured value of max memory.
The source of the issue is related to the use of closures as handlers
for the phpiredis reader resource, to be more precise it seems to be
some kind of circular reference memory leak. Apparently PHP does not
like the fact that a closure, automatically bound to "$this" starting
with PHP 5.4, is stored in the reader resource which in turn is kept
referenced by "$this". This ends up the refcount not going down thus
the GC does not collect those connection objects going out of scope.
This is confirmed by the fact that this issue is not triggered when
using PHP 5.3 where the ZE does not automatically bind closures to
"$this", unless you capture "$this" with the "use()" directive (and
the usual "$that = $this" trick).
Using a static assignement instead of simply returning new closures
in "getStatusHandler()" and "getErrorHandler()" is kind of an hack
that seems to be working fine, the added value of this solution is
that we do not have to break the internal API of the three connection
backends based on ext-phpiredis.
This commit fixes#345.
The following code, despite not being something you would do in real
world scenarios, eventually ended up generating an exception for "Too
many open files".
for ($i = 0; $i < 10000; $i++) {
$client = new Predis\Client();
$client->connect();
}
The reason was that the destructor for the connection was not invoked
by PHP as expected when the client instance went out of scope, so the
underlying stream resource were not being properly released.
Even without an actual "connect()" operation, the memory usage of PHP
kept growing until exhausting the configured value of max memory.
The source of the issue is related to the use of closures as handlers
for the phpiredis reader resource, to be more precise it seems to be
some kind of circular reference memory leak. Apparently PHP does not
like the fact that a closure, automatically bound to "$this" starting
with PHP 5.4, is stored in the reader resource which in turn is kept
referenced by "$this". This ends up the refcount not going down thus
the GC does not collect those connection objects going out of scope.
This is confirmed by the fact that this issue is not triggered when
using PHP 5.3 where the ZE does not automatically bind closures to
"$this", unless you capture "$this" with the "use()" directive (and
the usual "$that = $this" trick).
Using a static assignement instead of simply returning new closures
in "getStatusHandler()" and "getErrorHandler()" is kind of an hack
that seems to be working fine, the added value of this solution is
that we do not have to break the internal API of the three connection
backends based on ext-phpiredis.
This commit fixes#345.
This should not break existing code but allows users to retrieve more
easily the current dispatcher loop instance without resorting to some
tricks (like relying on the "use()" directive with closures).
This new method acts exactly like "getClientFor()" by returning a new
client instance for the specified node unless a callback is passed as
the second argument, in this case the callback is invoked and the new
client instance is passed to it. The value returned by the callback
is used as the return value of the "on()" method.
Promoted the "switchToMaster()" and "switchToSlave()" methods to be
part of the replication connection interface and demoted the method
"switchTo($connection)".
All option classes have been moved in the Predis\Configuration\Option
namespace and some have been optimized to have less impact on client
initialization timings.
Furthermore the accepted values for some options have been changed,
this is the complete list of accepted values:
- _aggregate_: callable returning an aggregate connection.
- cluster: string value ("predis", "redis"), callable returning an
aggregate connection.
- replication: string value ("predis", "sentinel"), callable returning
an aggregate connection.
- commands: command factory, named array mapping command IDs to PHP
classes, callable returning a command factory or a named array.
- connections: connection factory, callable returning a connection
factory, named array mapping connection schemes to PHP classes.
- _prefix_: string value, command processor, callable.
- _exceptions_: boolean value.
Note that the cluster and replication options now return a closure
acting as initializer instead of an aggregate connection.
By default Predis now uses a convention-over-configuration approach
by looking for a command class in the Predis\Command\Redis namespace
if it is not already defined in the commands class map.
This change allow us to decrease the time needed to load Predis on
each request since we removed 99% of the mappings in the commands
class map. Classes defined in the internal class map still take the
precedence over this mechanism, so users can still define their own
command classes to handle each command.
This change reduces some unnecessary complexity in the library, Redis
commands do not change much after all. Developers can still implement
their own commands factory, inject new commands or override existing
ones. The "profile" client options has been renamed to "commands" and
it accepts instances of Predis\Command\FactoryInterface.
The test suite checks at runtime the version of the running instance
of Redis for integration tests to adapt itself automatically.
We check if the string value is different than the casted int value,
if so it means that the integer is beyond PHP_INT_MAX or PHP_INT_MIN
and we simply return the string value. This is also useful on Windows
builds of PHP since the maximum integer size (prior to PHP 7.0) is 32
bits even for 64 bit builds.
Iterating over Predis\Connection\Aggregate\RedisCluster returns all
the connections currently mapped in the slots map instead of just the
ones initialized in the pool.
When the slots map is retrieved from Redis (which by default is done
automatically) this allows to iterate over all of the current master
nodes of the cluster. When the underlying use of "CLUSTER SLOTS" is
disabled the iteration returns only connections with a slots range
associated in their parameters or initialized by `-MOVED` responses
to make the behaviour of the iteration consistent between the two
modes of operation.
When various nodes in the configuration are unreachable while trying
to send a command, we should attempt to contact a reachable node to
fetch an updated slots map up to $retryLimit times or until there are
no more servers in the pool before giving up.
It is possible that the slots map fetched from Redis contains stale
data and points to a dead server, this happens when the nodes still
have to agree that a master server is down before promoting a slave
to the role of master. In this case no further attempts to execute
the command are performed and an exception is thrown.
This still needs some more testing and will delay v1.0.4 a few days
past its scheduled release.
This prevents an early failure of the command execution on the client
when one slave gets back online but is still loading the dataset from
disk (when this happens, Redis returns the -LOADING error response).
This commit fixes#280.
When the connection to a node in the cluster fails in the attempt to
execute a command, Predis now removes the failed connection from the
cluster pool and contacts a random node to ask for a fresh slots map
and tries to execute the command once again.
When the cluster is configured to have each master replicated to one
or more slaves, one the slaves is automatically promoted to the role
of master by redis-cluster with this change being reflected in the
output of CLUSTER SLOTS, so the next execution should run just fine.
Our current approach is relatively naive as CLUSTER SLOTS is executed
against a random master node, meaning that the client must open a new
connection and execute one more roundtrip only to fetch the new slots
map. For now it is enough, it is still better than having the client
fail when you actually have somes slaves in your redis-cluster setup,
but one improvement could consist in caching the list of slaves for
each master returned in the response of CLUSTER SLOTS so that when a
connection fails the client can try to guess which connection should
use for the next attempt.
This commit closes#173, closes#215, and closes#314.
ROLE was not being sent to master when still disconnected and with an
empty slaves pool preventing the client from checking the actual role
of the server upon connect().
When using redis-cluster it is now also possible to pass one slot or
non-contiguous ranges of slots via connection parameters in order to
improve the ability to pre-configure the slots map on the client.
Here is an example:
$parameters = [
'tcp://10.0.0.1:6379?slots=0-5460,5500-5600,11000',
'tcp://10.0.0.2:6379?slots=5461-5499,5600-10921',
'tcp://10.0.0.3:6379?slots=10922-10999,11001-16383',
];
This commit fixes#312 (props to @kenotr0n for the original PR).
Common failures are the use of SELECT with a database index outside
the bound of the configured number of databases in redis.conf or the
use of a wrong password for authentication with AUTH.
This resolves#322.
Now the client can discover the whole replication configuration by
asking to one of the servers (master has the precedence) using the
INFO REPLICATION command. This is obviously a best-effort fallback
and there is no strong guarantee about reliability and efficiency.
By enabling auto-discovery, the client automates this process when
the execution of a command fails because one of the target servers
is unreachable. The replication connection requires an instance of
connection factory associated to it in order to be able to create
new connections on the fly.
It is possible to enable the auto-discovery procedure easily via
client options:
$client = new Predis\Client($servers, [
'replication' => true,
'autodiscovery' => true,
]);
Internally the replication class uses this order to pick which server
it should connect to: current connection, one of the slaves, master.
If there is at least 1 slave, connect() will not fail even if master
is undefined. If there are no slaves, connect() will pick master. If
there are no connections registered for replication, connect() will
fail immediatly.
Now we do not extend Predis\Connection\Aggregate\MasterSlaveReplication
anymore in order to obtain a more coherent implementation with the logic
of redis-sentinel and apply more optimizations by avoiding useless round
trips with sentinel servers.
This is mostly useful when configuring the client to use redis-cluster
or redis-sentinel in order to set a common password for authentication
or database. In these kind of configurations it is impossible to pass
them via connection parameters as connections are created dinamically
by the client depending on the server response.
Once the client discovers the address of the master or a slave instance,
it must connect to that node and issue a ROLE command to verify that its
role still matches what the client got from the sentinel server.
I think it is better to have a default limit to the number of attempts
when trying to send a command after a connection failure, I am just not
sure if 20 is a good value but we can adjust it later.
Predis\Client now requires a list of connection parameters pointing to
sentinel instances and mandatory options "replication" and "service" set
respectively to "sentinel" and the chosen name for the master instance.
$sentinels = ['tcp://127.0.0.1:5381', 'tcp://127.0.0.1:5382'];
$options = ['replication' => 'sentinel', 'service' => 'mymaster'];
$client = new Predis\Client($sentinels, $options);
Despite being nice and clean on the outside I am not really fond of the
code being used internally to make this kind of configuration possible.
Improvements in this respect would require a few breaking changes (not
even an option for a minor release) so things will change for the good
with Predis 2.0.
This could be triggered when passing an array with "holes" to variadic commands.
Connection classes based on the protocol serialized exposed by phpiredis were
not affected by this bug.
Fixes#316.
When iterating a hash containing integer fields our iterator abstraction
based on HSCAN was always returning "0" as a field name after the first
$field => $value pair due to a wrong assumption on how the PHP function
array_shift() (which is used internally to advance to the next pair in
our buffered response to HSCAN) works.
The ZSCAN-based iterator had this very same bug which was already fixed
in 24e19a9 so I am not sure how this one went unnoticed until now.
This can be optionally done automatically but is disabled by default, just use
SentinelReplication::setUpdateSentinels() accordingly to enable the automatic
fetching of an updated list of sentinels.
By default, when the current server dies while executing a command Predis asks
for a new configuration to one of the sentinels and re-issues the same command.
This behavior can be disabled calling SentinelReplication::setAutomaticRetry().
This value should be reasonably low so that the client can fallback to the next
sentinel if the connect() operation is taking too much and slowing things down.
When the connection parameters of sentinels contain a "timeout" parameter, its
value takes the precedence over the default sentinels timeout.
This is a first implementation that is based on the work of @vmattila but some
more changes and missing bits are required in order to be considered complete.
To leverage redis-sentinel the client must be configured using the "aggregate"
option instead of the usual "replication" option, thought this may change for
the release of Predis v1.1.0 (it __will__ change for Predis v2.0.0 but this is
a whole different matter). This is a configuration example:
use Predis\Connection\Aggregate\SentinelReplication;
$sentinels = [
'tcp://127.0.0.1:5381',
'tcp://127.0.0.1:5382',
'tcp://127.0.0.1:5383',
];
$client = new Predis\Client($sentinels, [
'service' => 'nrk-master',
'aggregate' => function() {
return function ($sentinels, $options) {
$service = $option->service;
$connections = $options->connections;
return new SentinelReplication($sentinels, $service, $connections);
};
},
]);
The missing bits right now are:
- A more solid handling of failures when querying sentinels.
- When the connection fails while executing a command on one of the servers,
we should query again a sentinel and then re-issue the command accordingly.
Apparently HHVM is more strict than PHP in stream_socket_client() and does not
like at all IPv4 addresses and hostnames eclosed in square brackets. Note that
it is not that weird as square brackets are mandatory only when IPv6 addresses
are embedded in URI strings, so it is more like a weird incompatiblity of HHVM
with the behaviour of the standard PHP interpreter. The connect() attempt fails
but not due to the server being unavailable or some connectivity issue.
The important lesson is: never rely on undocumented behaviours especially when
targeting different runtimes, and do not forget to run the test suite on every
platform right before release like I unfortunately did.
Using IPv6 with Predis was basically impossible due to various inconsistencies
and bugs through the library, now it is supported by all the connection classes.
Following the standard for IPv6 literal addresses in URI strings, the IP literal
must be enclosed within square brackets when passing the parameters as a string:
$parameters = 'tcp://[2001:db8:0:f101::1]:6379';
See https://tools.ietf.org/html/rfc3986#section-3.2.2 for further details.
This commit also fixes#239 making redis-cluster usable with nodes using IPv6.
This trivial change makes it possible to use overridden static methods
when extending Predis\Command\Processor\KeyPrefixProcessor. PHP always
invokes the static methods of a parent class when using self:: even if
the extended classes override them.
Instead of using "unix://" you should just use "unix:":
$old = 'unix:///path/to/redis.sock';
$new = 'unix:/path/to/redis.sock';
The old format should be considered obsolete and will not be supported
starting from the next major release of Predis.
Meh
This is handy for accessing remote Redis instances over a secure SSL connection
which is currently a popular option or even requirement with many cloud hosting
environments.
In order to configure the client to use an SSL-encrypted connection the scheme
in the connection parameters must be either "tsl" or "rediss" and a set of SSL
options (see http://php.net/manual/en/context.ssl.php) must be provided via the
"ssl" parameter as a named array.
The following example (which does not necessarily represent an example of good
practices!) illustrates how to set the "ssl" parameter using a named array and
the equivalent URI string:
// Parameters as named array
$parameters = [
'scheme' => 'tls',
'host' => '127.0.0.1',
'ssl' => [
'cafile' => '/home/adaniele/redis.pem',
'verify_peer_name' => false,
],
];
// Parameters as URI string
$parameters = 'tls://127.0.0.1?ssl[cafile]=redis.pem&ssl[verify_peer_name]=1';
Support for SSL is currently limited to the Predis\Connection\StreamConnection
backend but we intend to investigate if it is possible to extend this feature
to Predis\Connection\PhpiredisStreamConnection in the future.
Be aware that using encrypted connections may lead to a performance degradation
especially in the connect() operation due to the overhead of the TLS handshake.
Unfortunately there is no real way to reuse SSL sessions from userland, aside
from enabling persistent connections, but this will work only on PHP >= 7.0.0
because previous versions of PHP do not provide enough info about a stream from
get_stream_meta_data().
NOTE: Redis does not have built-in support for SSL-encrypted connections, but if
you want to expose it to public networks you may want to rely on "stunnel".
The redis-cluster connection relies on a client-initialized connection factory,
so use Connection\Factory::setDefaultParameters() to set the default parameters
that must be applied to new nodes discovered through -MOVED or -ASK responses.
Falling back to a default timeout values should be done by the connection class
as it is an implementation detail that may vary depending on the backend.
These parameters augment the set of user-supplied parameters when creating a new
connection, but they do not override specific parameters when already defined.
An example of self-contained configuration using client options:
$client = new Predis\Client('tcp://127.0.0.1', [
'parameters' => [
'timeout' => 10,
],
'connections' => function ($options) {
$factory = $options->getDefault('connections');
$factory->setDefaultParameters($options->parameters);
return $factory;
},
]);
This change will be useful for both redis-cluster and redis-sentinel as it makes
it easy to apply shared parameters such as a common password for authentication
when the server returns one ore more new nodes from response (think of -MOVED).
stream_socket_client() has the undocumented ability to open different persistent
streams by providing a path in the $address string. Previously we supported this
behaviour with a combination of "persistent" and "path" (see #139) but this can
be confusing, especially now that we support the redis:// scheme which uses the
path part of an URI string to specify a database number.
After this change, instead of using an URI string such as:
$parameters = 'tcp://127.0.0.1/first?persistent=1&database=5';
You should use the following ones:
$parameters = 'tcp://127.0.0.1?persistent=first&database=5';
$parameters = 'redis://127.0.0.1/5?persistent=first';
Avoiding "path" makes even more sense when using array connection parameters:
$parameters = [
'host' => '127.0.0.1',
'database' => 5,
'persistent' => 'first',
]
This feature is not supported when using UNIX domain sockets because the path
trick of stream_socket_client() does not play well with the actual path of the
socket file. The client will throw an InvalidArgumentException exception to
notify the user.
NOTE: unfortunately we have to disable the tests for persistent connections when
running under HHVM due to a bug in their implementation of get_resource_type()
preventing us to recognize a persistent stream from userland code.
Each connection class should implement its own checks for connection parameters,
even at the cost of some code duplication (inheritance is not just about code
reuse after all).
Starting with Redis 3.0.3 the EXISTS command is variadic so that it is
possible to check for the existence of multiple keys in one request,
with the server returning the number of keys found.
This change could break codebases relying on strict comparison (===)
against a boolean value, but just doing $redis->exists('key') == TRUE
is totally fine.
The current code is checking for a failure (return false) or an empty buffer string, however
neither of these will be the case if the connection has been reset or has errored. According
to the docs for socket_recv, $buffer will be set to null if the connection is reset or their
is no data. As currently null is not allowed for, we enter an infinite loop, to prevent this
I've added null to the things we check before we emit a socket error. This prevents the
infinite loop and correctly results in an Exception if the connection is lost/reset.
Conflicts:
src/Connection/PhpiredisSocketConnection.php
This change is needed due to some internal changes in one of the
latest minor releases of PHPUnit 4.x that essentially broke how we
were checking for the required Redis version from method annotations.
Serializing an instance of a redis-cluster connection with its nodes
can take more than 1 seconds on extremely slow hardware.
See issue #220 for reference.
Serializing an instance of a redis-cluster connection with its nodes
can take more than 1 seconds on extremely slow hardware.
See issue #220 for reference.
When iterating a sorted set containing integer members, our iterator
abstraction based on ZSCAN was always returning "0" as a member value
after the first $member => $score pair because of a wrong assumption
on how the PHP function array_shift() (used internally to advance to
the next pair in our buffered response to ZSCAN) works.
Fixes#216.
When iterating a sorted set containing integer members, our iterator
abstraction based on ZSCAN was always returning "0" as a member value
after the first $member => $score pair because of a wrong assumption
on how the PHP function array_shift() (used internally to advance to
the next pair in our buffered response to ZSCAN) works.
Fixes#216.
Previously there was a bug in Redis that returned the wrong command
in -ERR messages when passing an invalid expire time with SETEX and
PSETEX. Now that the bug has been fixed and that travis-ci uses a an
updated version of Redis, our test suite turned red.
Previously there was a bug in Redis that returned the wrong command
in -ERR messages when passing an invalid expire time with SETEX and
PSETEX. Now that the bug has been fixed and that travis-ci uses a an
updated version of Redis, our test suite turned red.
This change is needed to address a bug in older versions of PHP 5.3
affecting inheritance (see https://bugs.php.net/bug.php?id=66818).
We will most likely resort to a workaround in v1.0 to stick with the
currently required minimum version of PHP (>= 5.3.2), but v1.1 will
surely require => 5.3.9.
[ci skip]
Even though tere are no changes between running the test suite against
3.0 and 2.8, we will stick with the current stable Redis version as
the default target and switch to 3.0 as soon as it will be released.
[ci skip]
Redis 3.0 does not really change much in terms of commands (most of
the ones implemented in the "unstable" branch have been backported to
the "2.8" branch after all) aside from a few cluster-related ones, so
we can bump the default version without worries.
Simply overriding one method of the standard connection factory class,
used internally by Predis, allows developers to use their own custom
connection parameters classes through the whole library.
For example, in order to support Heroku-style URIs one can do:
class HerokuParameters extends Predis\Connection\Parameters
{
protected function filter(array $parameters)
{
if (
isset($parameters['scheme']) &&
$parameters['scheme'] === 'redis'
) {
$parameters['scheme'] = 'tcp';
}
if (isset($parameters['pass'])) {
$parameters['password'] = $parameters['pass'];
}
unset($parameters['user'], $parameters['pass']);
return $parameters;
}
}
class ConnectionFactory extends Predis\Connection\Factory
{
protected function createParameters($parameters)
{
return HerokuParameters::create($parameters);
}
}
$client = new Predis\Client($_ENV['REDISCLOUD_URL'], [
'connections' => new ConnectionFactory()
]);
This idea comes in response to #196, but since we do not want to bake
support for SaaS-specific URIs in Predis this is the best compromise
we can offer leave developers free to implement their own logic for
handling these kind of URIs with the additional benefit of having it
available through the whole library (think of cluster or replication
where you have multiple nodes, thus multiple instances of connection
parameters to create).
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.
Casting values supplied by users should be done by the consumer since
we cannot cover any possible use case anyway. It is still possible to
subclass Predis\Connection\Parameters and override the filter() method
to convert certain values if deemed necessary.
This change actually had a positive impact on the design of the whole
internals for clustering which is now cleaner and easier to maintain.
It is still far from perfect, but we also have to keep performances in
consideration so we can say that we ended up with a good compromise.
Previously Predis assigned an hash to each command instance which was
computed from its key, now we changed approach and the library caches
the slot assigned to each command. This works for both our client-side
sharding cluster and the upcoming redis-cluster, but the former is the
one that needed most changes.
The PredisCluster aggregate connection now only takes an instance of
StrategyInterface, which in turn wraps the chosen distributor. After
all, in order to be able to calculate the assigned slot for a command
or key, the cluster strategy must have access to the distributor that
manages the distribution of the whole keyspace. Nothing really changes
in terms of configurability as it is still possible to decide which
distributor to use for client-side sharding, it is simply different:
$distributor = new Predis\Cluster\Distribution\KetamaRing();
$strategy = new Predis\Cluster\PredisStrategy($distributor);
$cluster = new Predis\Connection\Aggregate\PredisCluster($strategy);
As for the RedisCluster aggregate connection, the only change is that
the mathematical operation of calculating the assigned slot from a key
has been completely moved inside the cluster strategy instance.
The strategy for redis-cluster does not use external distributors so
trying to StrategyInterface::getDistributor() will throw an exception.
This may change in future releases, but this is not a priority since
redis-cluster relies on a fixed, well-defined distribution mechanism.
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.
This is a more sane default as it allows users to indicate only a few
servers of the whole cluster composition, while it previously required
a more complex configuration of the client using client options.
This feature can be disabled using the "enableAutoSlotsMap()" method.
It is better to wrap explicitly a multibulk response iterator into a
multibulk tuple response iterator instead of hard-coding that into a
specialized method.
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.
This change is possible because, after a few changes in redis-cluster,
our default cluster strategy used for client side sharding and the one
used for redis-cluster turned out to be exactly the same, except for
the hashing function used to calculate distribution.
Actually some checks used to enforce correctness are redundant in the
context of redis-cluster (e.g. the one used to make sure that keys in
requests performing cross-keys operations will hash to the same slot,
which is performed by the server) so we could also add a more dumb and
permissive cluster strategy that relies on checks performed by Redis.
Differently to v0.8, the strategy for client-side sharding now uses
the same rules for extracting hash tags from keys especially when
empty tags are found in the string.
The allocation stats section was most likely something available in
betas of some older release of Redis, it is not even reported on the
official documentation.
Actually we have just removed the server profile so nothing stops you
from reimplementing it, but Redis 2.0 has been released 4 years ago so
we are speaking of ancient releases that should probably not even be
used anymore.
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.
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.
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.
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.
This is more consistent with the actual purpose of this method and
more in-line with a possible future change in the underlying command
used to retrieve the slots map if redis-cluster will implement the
CLUSTER SLOTS command.
This change targets only method annotations and aims to make the body
of tests more readable using the @requiresRedisVersion annotation.
Tests using this annotation requires to be assigned the "connected"
group of tests because they create a connection to the Redis instance
specified in phpunit.xml to fetch its the version.
This is a quick example of how this annotation can be used:
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
*/
public function testExecutedOnlyWithMatchingRedisVersion()
{
}
Future improvements (currently not needed) include:
* Same annotation working on a class-level (but still applied only
to test methods with an explicitly assigned @group connected).
* Ability to specify a version range.
Meh
Meh
* Renamed SingleConnectionInterface to NodeConnectionInterface since
this name is better and makes even more sense in the context of
cluster and replication scenarios.
* Moved specialized aggregate connections (the ones implementing both
predis and redis cluster and master/slave replication) in a newly
created Predis\Connection\Aggregate sub-namespace.
* Removed the "Connection" part from names of aggregate connection
interfaces in the Predis\Connection\Aggregate sub-namespace.
* Changed "Composable" to "Composite" in the name of interfaces and
classes that can use pluggable protocol processors.
Let's allow people to install predis using "1.0.x@dev", "~1.0@dev", or any other valid version constraint rather than typing "dev-master" which is really horrible.
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.
Issue #151 pointed to a flaw in how command instances were converted
to strings: we were simply truncating their arguments when exceeding
a certain size as this was mostly intended for logging or debugging,
but this approach breaks strings containing multibyte characters so
we decided to drop this feature altogether for the sake of simplicity.
It is still possible to replicate the same (and eventually improved)
behavior externally by fetching ID and arguments of a command out of
a command instance using the public methods made available by the
Predis\Command\CommandInterface.
This commit represents a breaking change when the first occurrence of
"{}" is found in a key because it will produce a different hash than
previous versions of Predis, thus leading to a different partitioning.
If you really need to stick with the old behavior, you can subclass
Predis\Cluster\PredisStrategy and override extractKeyTag() using the
old implementation of this method and pass the strategy instance when
initializing the cluster connection via client options.
Multi-keys operations are not allowed even when keys generate the same
hash but this will probably be supported in later betas of Redis which
means we will basically end up reusing the whole strategy used for
client-side sharding.
Ported from the v0.8 branch.
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.
This actually makes sense since whatever the actual implementation is,
classes implementing these interfaces should provide at least those
properties that are actively used through the library.
Bug was not severe since +QUEUED status responses were returned anyway
but they were not cached as expected. We added a test in the suite.
This fixes#142.
The base abstract connection class now returns a bool to indicate when
the actual connect() operation has been performed on the underlying
resource. This return value is not part of the interface so extending
classes can decide to not return any value.
We allow failures on builds run against HHVM since it is still being
developed and things could break anytime, but at least this gives us
a decent indicator of the level of compatibility of Predis with this
runtime.
Achieving compatibility actually required a few marginal changes:
- HHVM still has some issues with re-entrant calls to __get(). The
applied change is an hack simply because it is ugly, but it is not
wrong and does not break the signature of the options interface.
- Since we cannot rely on the PHP version to detect the availability
of socket_import_stream(), we switched to function_exists(). As an
added bonus, using function_exists() is twice faster.
- In the test suite we removed an assertion for the message of an
E_WARNING simply because HHVM emits a different message. Checking
for the warning is actually enough in that context.
While the whole test suite passes on HHVM 2.3.0, please remember that
HHVM is still in development and things could break anytime especially
in some obscure corner cases.
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.
This is more consistent with Predis\Client::executeRaw() and its more
explicit since simply "raw" as a method name was a bit too vague even
despite being nicely short.
Using PHP's "parse_str()" to parse the query string is slightly more
efficient then our own code especially when the number of fields in
the query string grows, with the additional benefit of supporting
arrays for values when brackets are present in fieldnames.
So after this commit, providing this URI string:
$string = 'tcp://127.0.0.1?metavars[]=foo&metavars[]=hoge';
Is equivalent to providing the following named array:
$array = [
'scheme' => 'tcp',
'host' => '127.0.0.1',
'metavars' => ['foo', 'hoge'],
];
Aside from this improvement, the URI parsing behavior has not changed.
These parameters are applied only to connections being created on the
fly when not part of the current pool. This condition usually happens
upon -MOVE or -ASK responses returned by Redis to redirect client to
different nodes.
Only named arrays are explicitly accepted now, but the old behaviour
of creating a connection parameters instance out of an URI string or
a named array is still available using the "create()" static method.
$array = ['host' => '127.0.0.1', 'timeout' => 1];
$uri = 'tcp://127.0.0.1?timeout=1';
$parameters = new ConnectionParameters($array); // Arrays only
$parameters = ConnectionParameters::create($array); // Arrays OK
$parameters = ConnectionParameters::create($uri); // Strings OK
The purpose of the change is to have a more concise constructor with a
well defined signature.
List of changes:
- The cluster connection can be initialized with a partial list of
nodes, the full slots map will be fetched from Redis itself using
the CLUSTER NODES command.
- The slots map can be optionally retrieved from Redis if the server
returns a -MOVE response, otherwise only the interested slot will
be permanently reassigned to the new target node.
- $cluster->connect() connects to a random connection in the pool
instead of forcing the connect operation on all the connections.
Status response objects are needed mostly to make it possible from the
client perspective to differentiate a status response with the payload
"OK" from a normale bulk reply containing "OK".
The biggest change is for commands returning +OK responses: these were
previously translated to TRUE (bool value), but they are now returned
as instances of Predis\Response\Status. Just to illustrate an example
of the possibilities with this change we will use SET since it is the
most widely used command returning +OK:
$response = $client->set('foo', 'bar');
echo $response; // 'OK'
$response == 'OK'; // TRUE
isset($response->ok); // TRUE
$response == true; // TRUE
$response === true; // FALSE
$response instanceof Predis\Response\ObjectInterface; // TRUE
$response instanceof Predis\Response\Status; // TRUE
For those checking responses returned by commands such as SET or PONG,
the breaking change basically lies in the usage of strict comparison:
doing $response === true will now evaluate to FALSE instead of TRUE.
By default Predis caches common status responses such as OK or QUEUED
to lower the memory usage when using pipelines or transactions.
This optimization is mostly useful since connection parameters can be
instantiated more than one time in a script when using clustering or
replication.
A profile is not needed to create instances of basic initialization
commands such as AUTH (authentication) and SELECT (database selection)
since they do not need prefixing and other stuff.
If needed, developers can still extend the base connection factory to
make it use a server profile and configure the client to use it using
options.
When sending raw commands their arguments are not filtered, responses
are not parsed and key prefixes are not applied. The client also does
not throw any exception on Redis errors regardless of its settings.
The first parameter takes the raw arguments of the command (included
its identifier) as defined by the Redis documentation while the second
optional parameter is always populated by reference to indicate when
Redis actually returned an error response.
$client->raw(['PING']); // "PONG"
$client->raw(['SET','foo','bar']); // "OK"
$client->raw(['GET','foo'], $err); // "bar", $err=FALSE
$client->raw(['LPUSH','foo',1], $err); // "WRONGTYPE...", $err=TRUE
Internally, this method creates instances of Predis\Command\RawCommand
that get passed to the underlying connection instance for execution as
if they were usual commands defined by Predis.
Raw commands work in both cluster and replication scenarios since they
are recognized by their command ID, but key prefixing is not supported
since it is done by the profile instance when instantiating commands.
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.
We must always execute ASKING on the connection identified by the -ASK
response before executing the actual command because not doing so will
break the cluster specifications while redis-cluster is performing a
resharding operation.
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.
Remove recently added lines as they are not entirely true, the client
currently does not apply any key prefixing for any command not created
by Redis profiles even though it is possible now, contrary to v0.8.
[ci skip]
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.
We also changed our wording to indentify this kind of abstraction so
instead of using "scripted commands" (kind of broken English) we now
use "scriptable commands".
It was too broad in concept and not really that useful, so for now we
assume that only our base Predis\Profile\RedisProfile class can handle
command processing and we will decide later if we want to add a couple
of more methods to Predis\Profile\ProfileInterface.
While command classes define how the client should filter arguments or
parse responses, key prefixing depends on the actual command signature
as defined by Redis so it really is something that should be handled
separately as the norm.
Developers can define new handlers or override existing ones, but they
can still define the key prefixing logic inside their command classes
by implementing Predis\Command\PrefixableCommandInterface: the key
prefix processor will just use that by overriding any defined handler.
In this method we can put the logic needed to prepare the connection
right before sending the queued commands to the server (e.g. switching
to the master server when connected in replication mode).
Commands such as MULTI, EXEC, DISCARD, WATCH and UNWATCH disregard the
the "exceptions" client option and always result in an exception being
thrown on error responses even when the client is configured to return
those errors ("exceptions" set to FALSE).
We also changed some options for this class, the accepted ones are:
- "keys": string or array of strings for automatic WATCH.
- "cas": sets the check-and-set mode.
- "retry": number of attempts before giving up aborted transactions.
- "exceptions": sets whether exceptions should be thrown on error
responses (overrides the "exceptions" client option).
The "on_retry" option has been removed.
The Predis\Transaction\MultiExecState class is actually quite dummy as
it does not work as a finite-state machine, but is used to track state
using flags. It basically replicates the same behaviour of when it was
part of the Predis\Transaction\MultiExec code and it will do for now.
Only two options available for now, used to specify which kind of
pipeline object the client should use or return:
- "atomic": returns a pipeline wrapped in a MULTI / EXEC transaction
(class: Predis\Pipeline\Atomic).
- "fire-and-forget": returns a pipeline that does not read back
responses from the server (class: Predis\Pipeline\FireAndForget).
We might add more options in the future.
First of all we completely removed the concept of pipeline executors.
Now pipelines can be easily customized by extending our default class
Predis\Pipeline\Pipeline.
Tests coverage for the Predis\Pipeline namespace is decent but can be
definitely improved while test cases can be beautified.
This option must return a callable object that is used to override how
the client aggregates connections when passing an array of parameters
to its constructor.
When specified, this option overrides both "cluster" and "replication"
as it allows to make use of your own code to aggregate multiple nodes.
This is, for example, how you can mimic the standard initialization of
a cluster that relies on client-side sharding:
$parameters = ['tcp://127.0.0.1:6380', 'tcp://127.0.0.1:6381'];
$options = [
'aggregate' => function () {
return function ($parameters, $options) {
$connection = new Predis\Connection\PredisCluster();
$options->connections->aggregate($connection, $parameters);
return $connection;
};
},
];
$client = new Predis\Client($parameters, $options);
When invoked by the client, the specified callable must always return
a Predis\Connection\ConnectionInterface instance or the client will
throw an UnexpectedValueException.
Using list() with the warning suppressor is slower than using isset()
to check the presence of the first two elements of the array returned
by explode(). This also allow us to skip incomplete query string pairs
when parsing the URI string.
We have also changed the exception being thrown on invalid URIs to a
more appropriate one.
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.
The "profile" member is actually used for caching purposes as fetching
its value from the options instance would add noticeable overhead in a
part of the client where every bit of optimization matters, for this
we decided to keep it private.
Despite not being a globally supported feature of Predis anymore, they
are still optionally supported by our default text protocol processors
and they can be used to build custom stuff for specific needs.
Supporting this feature has been problematic and leaded to some ugly
code to make abstractions such as pipelines and transactions aware of
these kind of response objects. Furthermore, it was not possible to
add them to all the connection classes due to implementation limits.
For such reasons Predis do not support them globally anymore, but the
actual classes are still shipped within the library so that they can
be used to build custom stuff at a level lower than client (that is,
unless we decide to remove them for good before going stable).
This commit is a complete rewrite of the classes previously contained
in the Predis\Option namespace aimed at lowering the initialization
overhead while bringing in more consistency. The overall idea is still
the same with a mini DI container, Predis\Configuration\Options, which
carries options with values that can be initialized lazily.
The first difference with our previous implementation is that now even
user-defined options can be initialized lazily, everything needed is
an object responding to the __invoke() magic method such as a closure.
Other kind of callable arguments (strings, arrays) will be treated as
plain values. The only drawback is that we cannot pass any instance of
classes implementing __invoke() as an option value, but considered the
limited scope of our use case we can say it's more of an acceptable
compromise. Callbacks used for lazy initialization will receive two
arguments upon invokation:
- The current instance of Predis\Configuration\Option ($options)
- A string containing the name of the option ($option)
This is an example in actual code:
$options = new Predis\Configuration\Options([
'exceptions' => true,
'profile' => '2.8',
'distributor' => function () {
return new Predis\Cluster\Distribution\KetamaPureRing();
},
'cluster' => function ($options) {
$distr = $options->distributor;
$strategy = new Predis\Cluster\PredisClusterHashStrategy($distr);
$cluster = new Predis\Connection\PredisCluster();
return $cluster;
},
'connections' => function ($options, $option) {
$factory = $options->getDefault($option);
$factory->define('tcp', 'Predis\Connection\PhpiredisConnection');
return $factory;
},
]);
As you can see there's very little difference compared to before in
the actual usage as most changes are under the hood. Some options such
as "exceptions" and "replication" can now correctly parse bool values
from strings (so the string "false" is not evaluated as boolean true).
While options were initially conceived to configure the client and its
behavior, the concept has matured and it's perfectly fine to consider
the use of Predis\Configuration\Options to propagate configurations to
inner parts of the library.
This commit marks the start of works for the next major release of
Predis which will bring various breaking changes needed to polish the
internal design making the library even more flexible to use or extend
and, more importantly, almost stable in terms of API.
The Redis commands API exposed by Predis\Client is not going to change
much if not at all which is a good news. The most immediate changes
affecting developers will involve the renaming of a few namespaces and
classes, the removal of some previously deprecated classes and methods
and some tweaks to the current abstractions.
Right now the plan is to have a fast paced development to release this
version as soon as possible, ideally a few weeks later than Redis 2.8,
then wait to see the final definition of redis-cluster so that we can
tweak our code if needed and finally hit the v1.0.0 milestone with as
few changes as possible. Furthermore, v1.x will most likely be the
last version of Predis supporting PHP 5.3 as we will start migrating
to PHP 5.4 (or even 5.5) with v2.x, which is not going to happen soon
anyway.
This iterator tries to mimic the same behaviour of the cursor-based
iterators implemented upon the SCAN family of commands and offering
only limited guarantees on the returned elements, but uses LRANGE to
fetch items from a list incrementally.
Given the IP address we use for this test (the standard IP assigned by
operating systems when unable to get one lease from a DHCP server), on
certain kernels or network stacks we can get different messages such
as "No route to host". Since we just need to make sure that our test
does not take more than 1 seconds abiding by the sub-second timeout,
we can just live with that.
We are experimenting with a new approach at naming classes using less
redundant names by leveraging the containing namespace. The PHP "use"
directive is not limited to class names but can be used to import the
whole namespace, which means you can do something like this:
use Predis\Collection\Iterator;
// ...
foreach (new Iterator\Keyspace($client) as $key) {
// ...
}
Alternatively you can always rely on "use ... as ..." to import one of
the classes by giving it a more meaningful name in the context of the
root namespace:
use Predis\Collection\Iterator\Keyspace as KeyspaceIterator;
// ...
foreach (new KeyspaceIterator($client) as $key) {
// ...
}
In this specific case we chose to apply the -Key postfix to classes
iterating Redis keys to be more explicit about the fact that those
iterators does not work on local in-memory collections, but fetch
items from a key stored on a remote Redis server.
This iterator allows to perform full iterations over fields and values of a
hash by wrapping the incremental nature of HSCAN just like we did for SCAN:
$client = new Predis\Client('tcp://127.0.0.1', ['profile' => '2.8']);
$iterator = new Predis\Iterator\Scan\HashIterator($client, "hash_key");
foreach ($iterator as $field => $value) {
echo "$field => $value" . PHP_EOL;
}
Being HSCAN closely related to SCAN, it is subject to the same behaviour,
see http://redis.io/commands/scan for reference.
This iterator allows to perform full iterations over the members of a sorted
set by wrapping the incremental nature of ZSCAN just like we did for SCAN:
$client = new Predis\Client('tcp://127.0.0.1', ['profile' => '2.8']);
$iterator = new Predis\Iterator\Scan\SortedSetIterator($client, "zset_key");
foreach ($iterator as $member => $rank) {
echo "$rank => $member" . PHP_EOL;
}
Being ZSCAN closely related to SCAN, it is subject to the same behaviour,
see http://redis.io/commands/scan for reference.
This iterator implementation returns the member as key and the rank as value
since the rank is a float value which would be truncated when transforming
the iteration to an array (e.g. using iterator_to_array()). Luckily PHP
preserves the insertion order for named arrays members still result sorted.
This iterator allows to perform full iterations over the members of a set
by wrapping the incremental nature of SSCAN just like we did for SCAN:
$client = new Predis\Client('tcp://127.0.0.1', ['profile' => '2.8']);
$iterator = new Predis\Iterator\Scan\SetIterator($client, "set_key");
foreach ($iterator as $member) {
echo $member . PHP_EOL;
}
Being SSCAN closely related to SCAN, it is subject to the same behaviour,
see http://redis.io/commands/scan for reference.
Meh
The iterators based on SCAN, SSCAN, ZSCAN and HSCAN will extend this abstract
class to share most of the logic which is common for all of the Redis commands
in the SCAN family.
This iterator allows to perform full iterations over the keyspace of a Redis
instance by wrapping the incremental nature of SCAN using an abstraction that
fits perfectly in userland code:
$client = new Predis\Client('tcp://127.0.0.1', ['profile' => '2.8']);
$iterator = new Predis\Iterator\Scan\KeyspaceIterator($client);
foreach ($iterator as $key) {
echo $key . PHP_EOL;
}
Memory consumption during an iteration is limited because elements are fetched
incrementally, on the other hand SCAN gives limited guarantees about returned
elements since the underlying collection (the keyspace in this case) can change
during the whole iteration process. The most immediate drawback is that the same
element may be returned multiple times.
See http://redis.io/commands/scan to fully understand the inner workings,
and particularly the "Scan guarantees" paragraph.
Client::pubSub() still works like usual by returning a new pub/sub
context, but it is now considered an alias of Client::pubSubLoop().
This change is necessary in preparation for the next major version
of Predis where Client::pubSub() will be used for the new PUBSUB
command introduced in Redis 2.8.
One test is currently marked as skipped because it makes
Redis crash when the specified MATCH pattern returns one
or more elements.
See http://redis.io/commands/scan for reference.
One test is currently marked as skipped because it makes
Redis crash when the specified MATCH pattern returns one
or more elements.
See http://redis.io/commands/scan for reference.
We still need to populate this new server profile with the
recently added new commands for Redis 2.8.
The next development version has been set to Redis 3.0.
This commit addresses issues #124 and #126. LICENSE will be installed
with the "doc" role while phpunit.xml.dist will be installed with the
"test" role.
[ci skip]
Using gethostbyname, we will reuse the same (first) IP address for
each request. Here, we choose the IP we use randomly.
This is practical in a case where we have multiple redis read-only
slaves that can't invidually support the full application load, but are
accessible through a single hostname.
Providing a basic hint in the exception message about the server that caused
a connection exception could be useful especially with aggregated connections.
This is in response to issue #110.
A missing "use" directive was preventing the hash strategy to properly
use the specific methods of Predis\Command\ScriptedCommand, falling back
to analyzing the raw arguments array of the command.
Actually this was already the case for certain commands, but some of them
was left unguarded for such cases. This commit also fixes#109.
The behaviour of silently skipping key prefixing when a command has no
arguments may change in the future so we added explicit tests as guards
for future changes. Predis\Command\Processor\KeyPrefixProcessor will
continue to skip key prefixing on empty arguments, regardless.
This method should have been part of the interface since start since
it is used through the library. We also do not specify a default value
for the index argument since it does not make much sense.
We previously used FALSE for that but in the end it does not make much
sense. Luckily for us this does not represent a breaking change since
existing code will keep to work, so we can safely push this change into
the next patch release.
This cannot be implemented for previous versions of PHP because we
need socket_import_stream() to extract the underlying socket resource
from the stream in order to be able to set the TCP_NODELAY flag.
This commit reflects the recent change from the redis unstable branch
in which the number of hash slots was increased from 4096 to 16384.
See https://github.com/antirez/redis/commit/ebd666d for reference.
This class works just like Predis\Connection\PhpiredisConnection but
it does not require the socket extensions since it relies on PHP's
native streams thus allowing the use of persistent connections.
INFO does not have the concept of sections prior to 2.6.0 while our
test suite can be run against 2.4.0 (provided that phpunit.xml uses
the correct server profile), so we need to make sure that the check
does not break with prior version of Redis even if this is unlikely
to happen at this point.
Also changed a bit the naming of variables since things are already
pretty much explicit in this context.
It is not possible to get the default value of a client option using
either its name or instance:
$options = array(
'profile' => function ($options, $option) {
// instance of Predis\Option\OptionInterface
$profile = $options->getDefault($option);
// string representing an option handled by $options,
// returns NULL if the specified name is not handled.
$profile = $options->getDefault('profile');
return $profile;
},
);
This addition makes it less awkward to get the default value of an
option, especially when not in the context of a callable option
initializer.
This bug was actually introduced right before pushing the stable release
of v0.8.0 in which we moved the responsibility of parsing raw replies
with command parser to consumer classes.
This commit closes#101.
This is mainly in response to the longstanding issue #36 in which my
proposed solution was fine in terms of functionalities, but eventually
never made into the repository since it was far from being clean enough
for my taste.
Now developers can optionally pass a callable object when creating the
hashring instance to decide how the distributor should extract the hash
from a node (really a connection instance) to populate the ring:
use Predis\Cluster\Distribution\HashRing;
use Predis\Connection\PredisCluster;
$servers = array(
'tcp://10.0.0.1?alias=node01',
'tcp://10.0.0.2?alias=node02',
);
$options = array(
'nodehash' => function ($connection) {
return $connection->getParameters()->alias;
},
'cluster' => function ($options) {
$replicas = HashRing::DEFAULT_REPLICAS;
$hashring = new HashRing($replicas, $options->nodehash);
$cluster = new PredisCluster($hashring);
return $cluster;
},
);
$client = new Predis\Client($servers, $options);
Both HashRing and KetamaPureRing in the Predis\Cluster\Distribution
namespace support this new approach.
Ownership of the repository has been transferred. Thanks again to
@seppo0010, the original author of phpiredis, for his work on the
extension!
[ci skip]
Previously the getClientFor() method in a subclass of Predis\Client
returned an instance of Predis\Client instead of a new instance of
the subclass. The new behaviour is more correct.
This is just a convention implemented to client options supporting
callable initializers such as "profile", "cluster" and "replication".
This is useful to get a fully-initialized default value and perform
additional operations before returning it. An example with "profile":
$options = array(
'commands' => array(
'test1' => 'Predis\Command\ConnectionEcho',
'test2' => 'Predis\Command\ConnectionEcho',
),
'profile' => function ($options, $option) {
$profile = $option->getDefault($options);
if (is_array($options->commands)) {
foreach ($options->commands as $id => $cmd) {
$profile->defineCommand($id, $cmd);
}
}
return $profile;
},
);
Redis >= 2.8 returns -WRONGTYPE errors instead of -ERR when executing
operations on wrong key type (such as trying to LPUSH on a string key).
Luckily for us, phpunit's @expectedExceptionMessage annotation actually
does not perform an exact match but works on a substring so we just omit
the initial part of the exception message to make the test work.
When using UNSUBSCRIBE without a list of channels (which actually means
"unsubscribe from all the subscribed channels") Redis 2.6 does not
guarantee that channels are returned following the same order of
subscription.
We should actually make use of more protected methods in this
class instead of the private ones, this is most likely going
to be addressed in future commits.
See also issue #86 on GitHub for the reason behind this change.
Connection classes should just handle, convert and return simple Redis
types while parsing and transforming structured replies should be done
by consumers (see Predis\Client or Predis\Transaction\MultiExecContext).
This actually makes more sense considering that parsing a complex response
with the associated command parser may require different actions. As an
example, the result of EXEC is a multibulk that holds the actual responses,
so we really need to parse each one of its elements and we should also
make sure that iterable multibulks are consumed. We already did that
previously, but it was weird knowing that command parsers were applied
by the connection class.
This also moves some duplicated logic away from each connection class
implementation which is a nice bonus.
I think we can indeed start promoting Predis\Async now that it has
reached a fairly stable state (in terms of API and functionalites)
despite still being considered experimental.
[ci skip]
Multibulk response iterators will not be passed anymore to the response
parser method of the command that generated the response. Pipeline and
transaction abstractions still consume interators returned as response
items.
When no server profile is specified, the connection factory will not push
any initialization command to the newly created connection object.
This change is mainly useful when using redis-cluster and also makes it
possible to easily inject commands such as "SCRIPT LOAD" at initialization
time by grouping everything into one common place simply by extending the
connection factory class.
We extract the keys from commands using the second argument of EVAL /
EVALSHA which specifies the number of arguments that must be treated
as keys (used to populate the KEYS table in the Lua script) and then
we check if there is only one key since redis-cluster right now does
not support multi-keys requests.
Our scripted command abstraction is also supported.
We extract the keys from commands using the second argument of EVAL /
EVALSHA which specifies the number of arguments that must be treated
as keys (used to populate the KEYS table in the Lua script) and then
we check if all the keys generate the same hash using the usual method.
Our scripted command abstraction is also supported.
It comes without saying that accessing or setting keys from within the
Lua script is something that might not work as expected, so you should
be careful when using EVAL and EVALSHA in the context of client-side
sharding.
This should make things a tiny bit faster (we are speaking about micro
optimizations anyway) but more importantly it results in a better
encapsulation.
This makes it possible to easily assign a connection to a slot range
using the key "slots" in connection parameters like in the following
example:
$parameters = array(
'tcp://127.0.0.1:6379?slots=0-1364',
'tcp://127.0.0.1:6380?slots=1365-2729',
'tcp://127.0.0.1:6381?slots=2730-4095',
);
$options = array('cluster' => 'redis');
$client = new Predis\Client($parameters, $options);
It is possible to get the full list of slot ranges for each node of
the cluster using redis-cli connected to one of the nodes:
./redis-cli -h 127.0.0.1 -p 6379 CLUSTER NODES
The last column in the resulting output contains the slots assigned
to each instance participating to the redis cluster.
This is useful to avoid guessing the correct server instance at runtime
thus reducing the overhead of a guess-and-try approach since relying on
-ASK or -MOVED responses returned by a node is a costly operation.
For now, you can pre-associate slots using the "cluster" client option:
$parameters = array(
'tcp://127.0.0.1:6379',
'tcp://127.0.0.1:6380',
'tcp://127.0.0.1:6381',
);
$options = array(
'cluster' => function ($options) {
$cluster = new Predis\Connection\RedisCluster();
$cluster->setSlots(0, 1364, '127.0.0.1:6379');
$cluster->setSlots(1365, 2729, '127.0.0.1:6380');
$cluster->setSlots(2730, 4095, '127.0.0.1:6381');
return $cluster;
},
);
$client = new Predis\Client($parameters, $options);
In the future we will make the slots configuration easier by fetching
the configuration from a node using the command "CLUSTER NODES" or by
pre-computing the slices of slots associated to a node when adding it
to the cluster connection object.
This is needed because redis-cluster does not support the same commands
or operations that can be performed with our client-side managed predis
cluster.
For example redis-cluster does not support key tagging (that is, parts
of a key enclosed by {...} to hash only that specific part of a key)
and multiple-key operations suchs as MGET, MSET, SDIFF, SUNION or SINTER.
Some multiple-key operations can be performed anyway if the command has
only one key (e.g. "MGET foo" and "MSET foo bar" will not fail).
Do not ask me why I was using 0x (the prefix for hexadecimal notation)
when what we really need here is 0b (the prefix for binary notation).
Really, everything was working by sheer coincidence.
Since 0b has been added in PHP 5.4, we rely on plain decimal numbers
to define our constants for now.
This implementation is capable of handling "ASK" and "MOVED" replies
returned by Redis when one of the nodes asks the client for temporary
or permanent redirects of a slot to a different node.
Performances look almost on par with our client-side sharding solution
and everything looks already relatively stable.
Tests are still missing.
The reason for this change is to support the upcoming redis-cluster since
it has different behaviors compared to the client-side cluster implementation
provided by Predis. For example redis-cluster will not support key tags or
certain operations currently available with our client-side implementation.
The actual pipeline is wrapped between MULTI and EXEC to ensure that all the
commands are correctly sent and executed on the server. The whole pipeline is
discarded should it fail at a certain point during execution.
We do not use our Predis\Transaction\MultiExecContext abstraction internally
but rely on raw commands since this new executor does not really need full
support for Redis transactions.
We start using SplQueue to enqueue the commands executed inside a MULTI /
EXEC context so that we can dequeue them for parsing their replies later.
This change does not affect performances but makes the code more clean.
Using SplQueue instead of a plain PHP array to queue command instance in
the pipeline is faster and makes the underlying implementation definitely
more clean.
This change is not going to be backported to Predis v0.7 due to changes
in Predis\Pipeline\PipelineContextInterface.
Now Predis\Command\ScriptedCommand uses EVALSHA instead of EVAL internally
so that performances should be better since the client do not resend the
Lua script body on each call.
Plain EVALSHA commands are not affected and will return or throw the error.
The "throw_errors" connection parameter has been removed and replaced by the
new "exceptions" client option since exceptions on -ERR replies returned by
Redis are not generated by connection classes anymore but are thrown by the
client class and other abstractions such as pipeline contexts.
This change does not affect much people using the Predis\Client class (aside
from the different configuration) but gives much more flexibility to those
building their own pieces of code around the internal classes of Predis.
The actual code to switch to master has been implemented only in the
standard and the fire-and-forget executor classes since it does not
make sense to have it in special executors used exclusively when in
client-side clustering mode.
With a negative number Predis will count from the end of the arguments list
to calculate the actual number of keys that will be interpreted as elements
for `KEYS` by the underlying `EVAL` command.
Now developers can pass a client object or an executable context (see
Predis\Transaction\MultiExecContext or Predis\Pipeline\PipelineContext)
interchangeably as parameters to their methods using the new interface
Predis\BasicCliantInterface.
These new interfaces will also allow us to easily create new client
classes aside from the standard Predis\Client one.
Starting with 2.6, Redis uses a slightly different format for the payload
returned by MONITOR for each command. Predis can now adapt to this difference
and returns a new `client` field in the payload object. This new field is
always defined but is set to NULL when connected to Redis < 2.6.
Please note that Redis 2.6 now does not echo the `MONITOR` command as the
first payload when opening a new MONITOR context.
The reason for this change is that not every cluster implementation can support
this behaviour, think of Redis cluster for example. We moved the implementation
of this method in Predis\Connection\PredisCluster since it can still be useful.
Since PHP cannot serialize closures we switched to a private method to check
if a SORT command is a read-only operation, but closures are still supported
even if they will make the connection unserializable.
This is actually a temporary solution since we should be using a dedicated
pipeline executor to handle the master/slave replication connection.
Ideally we should stick with a slave if no write operations are present in
the buffer, but forcing a switch to the master connection is the easiest
solution for now and users can still create a new client instance from one
of the slaves when they are sure that their pipelines contain read-only
operations.
This can be useful to set up the profile with additional commands,
e.g. when injecting new "virtual" commands based upon the EVAL and
the Predis\Commands\ScriptedCommand class.
Some of these commands do not actually pose any threat, but their replies can
be misleading since users do not know on which server they are connected to.
We prefer to make things explicit so users can get a new client object out of
a specific connection in the replication pool to issue these commands.
Previously it was possible to create a new instance of Predis\Client using
the alias of a single connection in a cluster of connections. Now we added
the ability to do this also when using master/slave replication.
Since Pearhub is currently broken (unfortunately the project does not seem to
be actively mantained anymore) but we still want to have PEAR packages for our
library, we need to automate the creation of a package.xml definition file to
build the actual packages that will be uploaded on a self-hosted PEAR channel.
Onion, while still in its alpha stage right now, works perfectly fine for our
needs and its own package.ini file looks simple enough to edit and maintain.
See http://c9s.github.com/Onion for more details about Onion and thank @c9s
for making it available.
As suggested by @Seldaek in ISSUE #21, it would be desiderable to have the
ability to mark certain scripts for EVAL (and EVALSHA) as read-only to avoid
switching to the master server when this is not really needed.
When marking a script as read-only, only its SHA1 hash is saved to use less
memory and to make this work transparently for both EVAL and EVALSHA.
When the "replication" option is specified and evaluates to true, the array of
connection parameters passed to the client constructor os be used to initialize
a new instance of Predis\Network\IConnectionReplication. This option can also
accept a callable object used as an initializer or the fully-qualified name of
a class.
We used a new kind of virtual connection that, just like with cluster, groups
multiple connection objects and handles the logic needed to switch among them
depending on the kind of operation performed by commands..
Our default implementation starts by picking up a random slave and switches to
the master as soon as a command performing a write operation against a key is
detected. The master server will then be used for all the subsequent requests
unless a manual switch to a different connection object is performed.
Redis transactions are always performed on the master server, which means that
the switch to master is done as soon as the client issues a WATCH, MULTI or
any other command related to transactions.
See also https://github.com/nrk/predis/issues/21 for more details.
2011-12-16 16:28:36 +01:00
1402 changed files with 105612 additions and 31851 deletions
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.
- Added new hash-field expiration commands (#1520)
- Added missing `FT._LIST` and `BITFIELD_RO` commands (#1521)
### Changed
- Update `WATCH` command to accept `string|string[]` (#1476)
-Optimize cluster slotmap with compact slot range object (#1493)
### Fixed
-Fixed `EVAL_RO` cluster support (#1449)
- Fixed PHP 8.4 compatibility with `stream_context_set_option()` (#1503)
-Prevent named arguments runtime failure (#1509)
- Mark `GEOSEARCH` as read-only to ensure execution on replica (#1481)
- Fixed protocol loss during redis cluster `MOVED` / `ASK` (#1530)
### Maintenance
- Added CI testing with Redis 8.0 (#1510)
- Added test coverage for compatibility with Redis 8.0 (#1513)
- Use parallel on PHP-CS-Fixer (#1489)
## v2.3.0 (2024-11-21)
### Added
- Added `GeoShapeField` field (#1467)
- Added hash expiration commands (#1456)
- Added support for time series `IGNORE` filter (#1458)
-Added `XREAD` commands (#1459)
- Added `NOVALUES` argument support to `HSCAN` (#1459)
- Added support for search `INDEXMISSING` and `INDEXEMPTY` arguments (#1464)
### Changed
- Explicitly mark nullable parameters as nullable (#1448)
- Filter out available replicas based on link status flag (#1440)
-Respect `prefix` for `ZPOPMIN`, `ZPOPMAX`, `ZMSCORE`, `LMOVE`, `BLMOVE`, `SMISMEMBER` and `GEOSEARCH` (#1451, #1453, #1455, #1468)
- Updated test infrastructure (#1510)
### Fixed
- Fixed Relay support when using Redis Cluster (#1397)
-Fixed `cmsincrby()` type annotation (#1333)
- Fixed `set()` type annotation (#1394)
- Fixed operator precedence in connection factory (#1405)
- Fixed a bug with `null` arguments in `set()` method (#1470, #1471)
## v2.2.2 (2023-09-13)
### Added
- Added `client_info` client parameter
- Added support for `CLUSTER` container command
### Fixed
- Fixed `EXPIRETIME` not using `prefix`
- Disabled `CLIENT SETINFO` calls by default
## v2.2.1 (2023-08-15)
### 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)
### Changed
- Set client name and version when establishing a connection (#1347)
## v2.2.0 (2023-06-14)
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).
### 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)
### Changed
- Minor code style and type-hint changes (#1311)
### 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)
### 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)
### Bug Fixes
- Fixed deprecated function call syntax
### Deprecated
- Further deprecated phpiredis and webdis integration (#1179)
### Maintenance
- Applied coding standards
- Pass PHPStan level 2
## v2.1.1 (2023-01-17)
### Bug Fixes
- Fix `@template` in `Predis\Client` (#1017)
- Fix support options array in `ZINTERSTORE` and `ZUNIONSTORE` (#1018)
### Deprecated
- Deprecated phpiredis and webdis connections
## v2.1.0 (2023-01-16)
### 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)
### Bug Fixes
- Added annotations to suppress PHP 8.1 return type deprecation warning (#810)
### Maintenance
- Added mixin annotations for traits (#835)
## 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
## v2.0.2 (2022-09-06)
- Fixed PHP 8.2 deprecation notice: Use of "static" in callables
## v2.0.1 (2022-09-04)
- 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
## v2.0.0 (2022-06-08)
- Dropped support for PHP 7.1 and older
- Accepted values for some client options have changed, this is the new list of
accepted values:
-`aggregate`: callable returning an aggregate connection.
-`cluster`: string value (`predis`, `redis`), callable returning an aggregate
connection.
-`replication`: string value (`predis`, `sentinel`), callable returning an
aggregate connection.
-`commands`: command factory, named array mapping command IDs to PHP classes,
callable returning a command factory or a named array.
-`connections`: connection factory, callable object returning a connection
factory, named array mapping URI schemes to PHP classes, string identifying
a supported combination of configurations for the connection factory.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.