Passed
Pull Request — master (#36)
by Wilmer
01:46
created

Connection::createCommand()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Redis;
6
7
use Exception;
8
use Psr\EventDispatcher\EventDispatcherInterface;
9
use Psr\Log\LoggerInterface;
10
use Psr\Log\LogLevel;
11
use Yiisoft\Arrays\ArrayHelper;
12
use Yiisoft\Db\Redis\Event\AfterOpen;
13
use Yiisoft\Strings\Inflector;
14
use Yiisoft\VarDumper\VarDumper;
15
16
use function array_keys;
17
use function array_merge;
18
use function count;
19
use function explode;
20
use function fclose;
21
use function fgets;
22
use function fread;
23
use function fwrite;
24
use function get_class;
25
use function get_object_vars;
26
use function implode;
27
use function in_array;
28
use function ini_get;
29
use function mb_strlen;
30
use function mb_substr;
31
use function preg_split;
32
use function stream_set_timeout;
33
use function stream_socket_enable_crypto;
34
use function strtoupper;
35
use function usleep;
36
use function version_compare;
37
38
/**
39
 * The redis connection class is used to establish a connection to a [redis](http://redis.io/) server.
40
 *
41
 * By default it assumes there is a redis server running on localhost at port 6379 and uses the database number 0.
42
 *
43
 * It is possible to connect to a redis server using [[hostname]] and [[port]] or using a [[unixSocket]].
44
 *
45
 * It also supports [the AUTH command](http://redis.io/commands/auth) of redis.
46
 * When the server needs authentication, you can set the [[password]] property to
47
 * authenticate with the server after connect.
48
 *
49
 * The execution of [redis commands](http://redis.io/commands) is possible with via [[executeCommand()]].
50
 *
51
 * @method mixed append($key, $value) Append a value to a key. <https://redis.io/commands/append>
52
 * @method mixed auth($password) Authenticate to the server. <https://redis.io/commands/auth>
53
 * @method mixed bgrewriteaof() Asynchronously rewrite the append-only file. <https://redis.io/commands/bgrewriteaof>
54
 * @method mixed bgsave() Asynchronously save the dataset to disk. <https://redis.io/commands/bgsave>
55
 * @method mixed bitcount($key, $start = null, $end = null) Count set bits in a string. <https://redis.io/commands/bitcount>
56
 * @method mixed bitfield($key, ...$operations) Perform arbitrary bitfield integer operations on strings. <https://redis.io/commands/bitfield>
57
 * @method mixed bitop($operation, $destkey, ...$keys) Perform bitwise operations between strings. <https://redis.io/commands/bitop>
58
 * @method mixed bitpos($key, $bit, $start = null, $end = null) Find first bit set or clear in a string. <https://redis.io/commands/bitpos>
59
 * @method mixed blpop(...$keys, $timeout) Remove and get the first element in a list, or block until one is available. <https://redis.io/commands/blpop>
60
 * @method mixed brpop(...$keys, $timeout) Remove and get the last element in a list, or block until one is available. <https://redis.io/commands/brpop>
61
 * @method mixed brpoplpush($source, $destination, $timeout) Pop a value from a list, push it to another list and return it; or block until one is available. <https://redis.io/commands/brpoplpush>
62
 * @method mixed clientKill(...$filters) Kill the connection of a client. <https://redis.io/commands/client-kill>
63
 * @method mixed clientList() Get the list of client connections. <https://redis.io/commands/client-list>
64
 * @method mixed clientGetname() Get the current connection name. <https://redis.io/commands/client-getname>
65
 * @method mixed clientPause($timeout) Stop processing commands from clients for some time. <https://redis.io/commands/client-pause>
66
 * @method mixed clientReply($option) Instruct the server whether to reply to commands. <https://redis.io/commands/client-reply>
67
 * @method mixed clientSetname($connectionName) Set the current connection name. <https://redis.io/commands/client-setname>
68
 * @method mixed clusterAddslots(...$slots) Assign new hash slots to receiving node. <https://redis.io/commands/cluster-addslots>
69
 * @method mixed clusterCountkeysinslot($slot) Return the number of local keys in the specified hash slot. <https://redis.io/commands/cluster-countkeysinslot>
70
 * @method mixed clusterDelslots(...$slots) Set hash slots as unbound in receiving node. <https://redis.io/commands/cluster-delslots>
71
 * @method mixed clusterFailover($option = null) Forces a slave to perform a manual failover of its master.. <https://redis.io/commands/cluster-failover>
72
 * @method mixed clusterForget($nodeId) Remove a node from the nodes table. <https://redis.io/commands/cluster-forget>
73
 * @method mixed clusterGetkeysinslot($slot, $count) Return local key names in the specified hash slot. <https://redis.io/commands/cluster-getkeysinslot>
74
 * @method mixed clusterInfo() Provides info about Redis Cluster node state. <https://redis.io/commands/cluster-info>
75
 * @method mixed clusterKeyslot($key) Returns the hash slot of the specified key. <https://redis.io/commands/cluster-keyslot>
76
 * @method mixed clusterMeet($ip, $port) Force a node cluster to handshake with another node. <https://redis.io/commands/cluster-meet>
77
 * @method mixed clusterNodes() Get Cluster config for the node. <https://redis.io/commands/cluster-nodes>
78
 * @method mixed clusterReplicate($nodeId) Reconfigure a node as a slave of the specified master node. <https://redis.io/commands/cluster-replicate>
79
 * @method mixed clusterReset($resetType = "SOFT") Reset a Redis Cluster node. <https://redis.io/commands/cluster-reset>
80
 * @method mixed clusterSaveconfig() Forces the node to save cluster state on disk. <https://redis.io/commands/cluster-saveconfig>
81
 * @method mixed clusterSetslot($slot, $type, $nodeid = null) Bind a hash slot to a specific node. <https://redis.io/commands/cluster-setslot>
82
 * @method mixed clusterSlaves($nodeId) List slave nodes of the specified master node. <https://redis.io/commands/cluster-slaves>
83
 * @method mixed clusterSlots() Get array of Cluster slot to node mappings. <https://redis.io/commands/cluster-slots>
84
 * @method mixed command() Get array of Redis command details. <https://redis.io/commands/command>
85
 * @method mixed commandCount() Get total number of Redis commands. <https://redis.io/commands/command-count>
86
 * @method mixed commandGetkeys() Extract keys given a full Redis command. <https://redis.io/commands/command-getkeys>
87
 * @method mixed commandInfo(...$commandNames) Get array of specific Redis command details. <https://redis.io/commands/command-info>
88
 * @method mixed configGet($parameter) Get the value of a configuration parameter. <https://redis.io/commands/config-get>
89
 * @method mixed configRewrite() Rewrite the configuration file with the in memory configuration. <https://redis.io/commands/config-rewrite>
90
 * @method mixed configSet($parameter, $value) Set a configuration parameter to the given value. <https://redis.io/commands/config-set>
91
 * @method mixed configResetstat() Reset the stats returned by INFO. <https://redis.io/commands/config-resetstat>
92
 * @method mixed dbsize() Return the number of keys in the selected database. <https://redis.io/commands/dbsize>
93
 * @method mixed debugObject($key) Get debugging information about a key. <https://redis.io/commands/debug-object>
94
 * @method mixed debugSegfault() Make the server crash. <https://redis.io/commands/debug-segfault>
95
 * @method mixed decr($key) Decrement the integer value of a key by one. <https://redis.io/commands/decr>
96
 * @method mixed decrby($key, $decrement) Decrement the integer value of a key by the given number. <https://redis.io/commands/decrby>
97
 * @method mixed del(...$keys) Delete a key. <https://redis.io/commands/del>
98
 * @method mixed discard() Discard all commands issued after MULTI. <https://redis.io/commands/discard>
99
 * @method mixed dump($key) Return a serialized version of the value stored at the specified key.. <https://redis.io/commands/dump>
100
 * @method mixed echo($message) Echo the given string. <https://redis.io/commands/echo>
101
 * @method mixed eval($script, $numkeys, ...$keys, ...$args) Execute a Lua script server side. <https://redis.io/commands/eval>
102
 * @method mixed evalsha($sha1, $numkeys, ...$keys, ...$args) Execute a Lua script server side. <https://redis.io/commands/evalsha>
103
 * @method mixed exec() Execute all commands issued after MULTI. <https://redis.io/commands/exec>
104
 * @method mixed exists(...$keys) Determine if a key exists. <https://redis.io/commands/exists>
105
 * @method mixed expire($key, $seconds) Set a key's time to live in seconds. <https://redis.io/commands/expire>
106
 * @method mixed expireat($key, $timestamp) Set the expiration for a key as a UNIX timestamp. <https://redis.io/commands/expireat>
107
 * @method mixed flushall($ASYNC = null) Remove all keys from all databases. <https://redis.io/commands/flushall>
108
 * @method mixed flushdb($ASYNC = null) Remove all keys from the current database. <https://redis.io/commands/flushdb>
109
 * @method mixed geoadd($key, $longitude, $latitude, $member, ...$more) Add one or more geospatial items in the geospatial index represented using a sorted set. <https://redis.io/commands/geoadd>
110
 * @method mixed geohash($key, ...$members) Returns members of a geospatial index as standard geohash strings. <https://redis.io/commands/geohash>
111
 * @method mixed geopos($key, ...$members) Returns longitude and latitude of members of a geospatial index. <https://redis.io/commands/geopos>
112
 * @method mixed geodist($key, $member1, $member2, $unit = null) Returns the distance between two members of a geospatial index. <https://redis.io/commands/geodist>
113
 * @method mixed georadius($key, $longitude, $latitude, $radius, $metric, ...$options) Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a point. <https://redis.io/commands/georadius>
114
 * @method mixed georadiusbymember($key, $member, $radius, $metric, ...$options) Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a member. <https://redis.io/commands/georadiusbymember>
115
 * @method mixed get($key) Get the value of a key. <https://redis.io/commands/get>
116
 * @method mixed getbit($key, $offset) Returns the bit value at offset in the string value stored at key. <https://redis.io/commands/getbit>
117
 * @method mixed getrange($key, $start, $end) Get a substring of the string stored at a key. <https://redis.io/commands/getrange>
118
 * @method mixed getset($key, $value) Set the string value of a key and return its old value. <https://redis.io/commands/getset>
119
 * @method mixed hdel($key, ...$fields) Delete one or more hash fields. <https://redis.io/commands/hdel>
120
 * @method mixed hexists($key, $field) Determine if a hash field exists. <https://redis.io/commands/hexists>
121
 * @method mixed hget($key, $field) Get the value of a hash field. <https://redis.io/commands/hget>
122
 * @method mixed hgetall($key) Get all the fields and values in a hash. <https://redis.io/commands/hgetall>
123
 * @method mixed hincrby($key, $field, $increment) Increment the integer value of a hash field by the given number. <https://redis.io/commands/hincrby>
124
 * @method mixed hincrbyfloat($key, $field, $increment) Increment the float value of a hash field by the given amount. <https://redis.io/commands/hincrbyfloat>
125
 * @method mixed hkeys($key) Get all the fields in a hash. <https://redis.io/commands/hkeys>
126
 * @method mixed hlen($key) Get the number of fields in a hash. <https://redis.io/commands/hlen>
127
 * @method mixed hmget($key, ...$fields) Get the values of all the given hash fields. <https://redis.io/commands/hmget>
128
 * @method mixed hmset($key, $field, $value, ...$more) Set multiple hash fields to multiple values. <https://redis.io/commands/hmset>
129
 * @method mixed hset($key, $field, $value) Set the string value of a hash field. <https://redis.io/commands/hset>
130
 * @method mixed hsetnx($key, $field, $value) Set the value of a hash field, only if the field does not exist. <https://redis.io/commands/hsetnx>
131
 * @method mixed hstrlen($key, $field) Get the length of the value of a hash field. <https://redis.io/commands/hstrlen>
132
 * @method mixed hvals($key) Get all the values in a hash. <https://redis.io/commands/hvals>
133
 * @method mixed incr($key) Increment the integer value of a key by one. <https://redis.io/commands/incr>
134
 * @method mixed incrby($key, $increment) Increment the integer value of a key by the given amount. <https://redis.io/commands/incrby>
135
 * @method mixed incrbyfloat($key, $increment) Increment the float value of a key by the given amount. <https://redis.io/commands/incrbyfloat>
136
 * @method mixed info($section = null) Get information and statistics about the server. <https://redis.io/commands/info>
137
 * @method mixed keys($pattern) Find all keys matching the given pattern. <https://redis.io/commands/keys>
138
 * @method mixed lastsave() Get the UNIX time stamp of the last successful save to disk. <https://redis.io/commands/lastsave>
139
 * @method mixed lindex($key, $index) Get an element from a list by its index. <https://redis.io/commands/lindex>
140
 * @method mixed linsert($key, $where, $pivot, $value) Insert an element before or after another element in a list. <https://redis.io/commands/linsert>
141
 * @method mixed llen($key) Get the length of a list. <https://redis.io/commands/llen>
142
 * @method mixed lpop($key) Remove and get the first element in a list. <https://redis.io/commands/lpop>
143
 * @method mixed lpush($key, ...$values) Prepend one or multiple values to a list. <https://redis.io/commands/lpush>
144
 * @method mixed lpushx($key, $value) Prepend a value to a list, only if the list exists. <https://redis.io/commands/lpushx>
145
 * @method mixed lrange($key, $start, $stop) Get a range of elements from a list. <https://redis.io/commands/lrange>
146
 * @method mixed lrem($key, $count, $value) Remove elements from a list. <https://redis.io/commands/lrem>
147
 * @method mixed lset($key, $index, $value) Set the value of an element in a list by its index. <https://redis.io/commands/lset>
148
 * @method mixed ltrim($key, $start, $stop) Trim a list to the specified range. <https://redis.io/commands/ltrim>
149
 * @method mixed mget(...$keys) Get the values of all the given keys. <https://redis.io/commands/mget>
150
 * @method mixed migrate($host, $port, $key, $destinationDb, $timeout, ...$options) Atomically transfer a key from a Redis instance to another one.. <https://redis.io/commands/migrate>
151
 * @method mixed monitor() Listen for all requests received by the server in real time. <https://redis.io/commands/monitor>
152
 * @method mixed move($key, $db) Move a key to another database. <https://redis.io/commands/move>
153
 * @method mixed mset(...$keyValuePairs) Set multiple keys to multiple values. <https://redis.io/commands/mset>
154
 * @method mixed msetnx(...$keyValuePairs) Set multiple keys to multiple values, only if none of the keys exist. <https://redis.io/commands/msetnx>
155
 * @method mixed multi() Mark the start of a transaction block. <https://redis.io/commands/multi>
156
 * @method mixed object($subcommand, ...$argumentss) Inspect the internals of Redis objects. <https://redis.io/commands/object>
157
 * @method mixed persist($key) Remove the expiration from a key. <https://redis.io/commands/persist>
158
 * @method mixed pexpire($key, $milliseconds) Set a key's time to live in milliseconds. <https://redis.io/commands/pexpire>
159
 * @method mixed pexpireat($key, $millisecondsTimestamp) Set the expiration for a key as a UNIX timestamp specified in milliseconds. <https://redis.io/commands/pexpireat>
160
 * @method mixed pfadd($key, ...$elements) Adds the specified elements to the specified HyperLogLog.. <https://redis.io/commands/pfadd>
161
 * @method mixed pfcount(...$keys) Return the approximated cardinality of the set(s) observed by the HyperLogLog at key(s).. <https://redis.io/commands/pfcount>
162
 * @method mixed pfmerge($destkey, ...$sourcekeys) Merge N different HyperLogLogs into a single one.. <https://redis.io/commands/pfmerge>
163
 * @method mixed ping($message = null) Ping the server. <https://redis.io/commands/ping>
164
 * @method mixed psetex($key, $milliseconds, $value) Set the value and expiration in milliseconds of a key. <https://redis.io/commands/psetex>
165
 * @method mixed psubscribe(...$patterns) Listen for messages published to channels matching the given patterns. <https://redis.io/commands/psubscribe>
166
 * @method mixed pubsub($subcommand, ...$arguments) Inspect the state of the Pub/Sub subsystem. <https://redis.io/commands/pubsub>
167
 * @method mixed pttl($key) Get the time to live for a key in milliseconds. <https://redis.io/commands/pttl>
168
 * @method mixed publish($channel, $message) Post a message to a channel. <https://redis.io/commands/publish>
169
 * @method mixed punsubscribe(...$patterns) Stop listening for messages posted to channels matching the given patterns. <https://redis.io/commands/punsubscribe>
170
 * @method mixed quit() Close the connection. <https://redis.io/commands/quit>
171
 * @method mixed randomkey() Return a random key from the keyspace. <https://redis.io/commands/randomkey>
172
 * @method mixed readonly() Enables read queries for a connection to a cluster slave node. <https://redis.io/commands/readonly>
173
 * @method mixed readwrite() Disables read queries for a connection to a cluster slave node. <https://redis.io/commands/readwrite>
174
 * @method mixed rename($key, $newkey) Rename a key. <https://redis.io/commands/rename>
175
 * @method mixed renamenx($key, $newkey) Rename a key, only if the new key does not exist. <https://redis.io/commands/renamenx>
176
 * @method mixed restore($key, $ttl, $serializedValue, $REPLACE = null) Create a key using the provided serialized value, previously obtained using DUMP.. <https://redis.io/commands/restore>
177
 * @method mixed role() Return the role of the instance in the context of replication. <https://redis.io/commands/role>
178
 * @method mixed rpop($key) Remove and get the last element in a list. <https://redis.io/commands/rpop>
179
 * @method mixed rpoplpush($source, $destination) Remove the last element in a list, prepend it to another list and return it. <https://redis.io/commands/rpoplpush>
180
 * @method mixed rpush($key, ...$values) Append one or multiple values to a list. <https://redis.io/commands/rpush>
181
 * @method mixed rpushx($key, $value) Append a value to a list, only if the list exists. <https://redis.io/commands/rpushx>
182
 * @method mixed sadd($key, ...$members) Add one or more members to a set. <https://redis.io/commands/sadd>
183
 * @method mixed save() Synchronously save the dataset to disk. <https://redis.io/commands/save>
184
 * @method mixed scard($key) Get the number of members in a set. <https://redis.io/commands/scard>
185
 * @method mixed scriptDebug($option) Set the debug mode for executed scripts.. <https://redis.io/commands/script-debug>
186
 * @method mixed scriptExists(...$sha1s) Check existence of scripts in the script cache.. <https://redis.io/commands/script-exists>
187
 * @method mixed scriptFlush() Remove all the scripts from the script cache.. <https://redis.io/commands/script-flush>
188
 * @method mixed scriptKill() Kill the script currently in execution.. <https://redis.io/commands/script-kill>
189
 * @method mixed scriptLoad($script) Load the specified Lua script into the script cache.. <https://redis.io/commands/script-load>
190
 * @method mixed sdiff(...$keys) Subtract multiple sets. <https://redis.io/commands/sdiff>
191
 * @method mixed sdiffstore($destination, ...$keys) Subtract multiple sets and store the resulting set in a key. <https://redis.io/commands/sdiffstore>
192
 * @method mixed select($index) Change the selected database for the current connection. <https://redis.io/commands/select>
193
 * @method mixed set($key, $value, ...$options) Set the string value of a key. <https://redis.io/commands/set>
194
 * @method mixed setbit($key, $offset, $value) Sets or clears the bit at offset in the string value stored at key. <https://redis.io/commands/setbit>
195
 * @method mixed setex($key, $seconds, $value) Set the value and expiration of a key. <https://redis.io/commands/setex>
196
 * @method mixed setnx($key, $value) Set the value of a key, only if the key does not exist. <https://redis.io/commands/setnx>
197
 * @method mixed setrange($key, $offset, $value) Overwrite part of a string at key starting at the specified offset. <https://redis.io/commands/setrange>
198
 * @method mixed shutdown($saveOption = null) Synchronously save the dataset to disk and then shut down the server. <https://redis.io/commands/shutdown>
199
 * @method mixed sinter(...$keys) Intersect multiple sets. <https://redis.io/commands/sinter>
200
 * @method mixed sinterstore($destination, ...$keys) Intersect multiple sets and store the resulting set in a key. <https://redis.io/commands/sinterstore>
201
 * @method mixed sismember($key, $member) Determine if a given value is a member of a set. <https://redis.io/commands/sismember>
202
 * @method mixed slaveof($host, $port) Make the server a slave of another instance, or promote it as master. <https://redis.io/commands/slaveof>
203
 * @method mixed slowlog($subcommand, $argument = null) Manages the Redis slow queries log. <https://redis.io/commands/slowlog>
204
 * @method mixed smembers($key) Get all the members in a set. <https://redis.io/commands/smembers>
205
 * @method mixed smove($source, $destination, $member) Move a member from one set to another. <https://redis.io/commands/smove>
206
 * @method mixed sort($key, ...$options) Sort the elements in a list, set or sorted set. <https://redis.io/commands/sort>
207
 * @method mixed spop($key, $count = null) Remove and return one or multiple random members from a set. <https://redis.io/commands/spop>
208
 * @method mixed srandmember($key, $count = null) Get one or multiple random members from a set. <https://redis.io/commands/srandmember>
209
 * @method mixed srem($key, ...$members) Remove one or more members from a set. <https://redis.io/commands/srem>
210
 * @method mixed strlen($key) Get the length of the value stored in a key. <https://redis.io/commands/strlen>
211
 * @method mixed subscribe(...$channels) Listen for messages published to the given channels. <https://redis.io/commands/subscribe>
212
 * @method mixed sunion(...$keys) Add multiple sets. <https://redis.io/commands/sunion>
213
 * @method mixed sunionstore($destination, ...$keys) Add multiple sets and store the resulting set in a key. <https://redis.io/commands/sunionstore>
214
 * @method mixed swapdb($index, $index) Swaps two Redis databases. <https://redis.io/commands/swapdb>
215
 * @method mixed sync() Internal command used for replication. <https://redis.io/commands/sync>
216
 * @method mixed time() Return the current server time. <https://redis.io/commands/time>
217
 * @method mixed touch(...$keys) Alters the last access time of a key(s). Returns the number of existing keys specified.. <https://redis.io/commands/touch>
218
 * @method mixed ttl($key) Get the time to live for a key. <https://redis.io/commands/ttl>
219
 * @method mixed type($key) Determine the type stored at key. <https://redis.io/commands/type>
220
 * @method mixed unsubscribe(...$channels) Stop listening for messages posted to the given channels. <https://redis.io/commands/unsubscribe>
221
 * @method mixed unlink(...$keys) Delete a key asynchronously in another thread. Otherwise it is just as DEL, but non blocking.. <https://redis.io/commands/unlink>
222
 * @method mixed unwatch() Forget about all watched keys. <https://redis.io/commands/unwatch>
223
 * @method mixed wait($numslaves, $timeout) Wait for the synchronous replication of all the write commands sent in the context of the current connection. <https://redis.io/commands/wait>
224
 * @method mixed watch(...$keys) Watch the given keys to determine execution of the MULTI/EXEC block. <https://redis.io/commands/watch>
225
 * @method mixed zadd($key, ...$options) Add one or more members to a sorted set, or update its score if it already exists. <https://redis.io/commands/zadd>
226
 * @method mixed zcard($key) Get the number of members in a sorted set. <https://redis.io/commands/zcard>
227
 * @method mixed zcount($key, $min, $max) Count the members in a sorted set with scores within the given values. <https://redis.io/commands/zcount>
228
 * @method mixed zincrby($key, $increment, $member) Increment the score of a member in a sorted set. <https://redis.io/commands/zincrby>
229
 * @method mixed zinterstore($destination, $numkeys, $key, ...$options) Intersect multiple sorted sets and store the resulting sorted set in a new key. <https://redis.io/commands/zinterstore>
230
 * @method mixed zlexcount($key, $min, $max) Count the number of members in a sorted set between a given lexicographical range. <https://redis.io/commands/zlexcount>
231
 * @method mixed zrange($key, $start, $stop, $WITHSCORES = null) Return a range of members in a sorted set, by index. <https://redis.io/commands/zrange>
232
 * @method mixed zrangebylex($key, $min, $max, $LIMIT = null, $offset = null, $count = null) Return a range of members in a sorted set, by lexicographical range. <https://redis.io/commands/zrangebylex>
233
 * @method mixed zrevrangebylex($key, $max, $min, $LIMIT = null, $offset = null, $count = null) Return a range of members in a sorted set, by lexicographical range, ordered from higher to lower strings.. <https://redis.io/commands/zrevrangebylex>
234
 * @method mixed zrangebyscore($key, $min, $max, $WITHSCORES = null, $LIMIT = null, $offset = null, $count = null) Return a range of members in a sorted set, by score. <https://redis.io/commands/zrangebyscore>
235
 * @method mixed zrank($key, $member) Determine the index of a member in a sorted set. <https://redis.io/commands/zrank>
236
 * @method mixed zrem($key, ...$members) Remove one or more members from a sorted set. <https://redis.io/commands/zrem>
237
 * @method mixed zremrangebylex($key, $min, $max) Remove all members in a sorted set between the given lexicographical range. <https://redis.io/commands/zremrangebylex>
238
 * @method mixed zremrangebyrank($key, $start, $stop) Remove all members in a sorted set within the given indexes. <https://redis.io/commands/zremrangebyrank>
239
 * @method mixed zremrangebyscore($key, $min, $max) Remove all members in a sorted set within the given scores. <https://redis.io/commands/zremrangebyscore>
240
 * @method mixed zrevrange($key, $start, $stop, $WITHSCORES = null) Return a range of members in a sorted set, by index, with scores ordered from high to low. <https://redis.io/commands/zrevrange>
241
 * @method mixed zrevrangebyscore($key, $max, $min, $WITHSCORES = null, $LIMIT = null, $offset = null, $count = null) Return a range of members in a sorted set, by score, with scores ordered from high to low. <https://redis.io/commands/zrevrangebyscore>
242
 * @method mixed zrevrank($key, $member) Determine the index of a member in a sorted set, with scores ordered from high to low. <https://redis.io/commands/zrevrank>
243
 * @method mixed zscore($key, $member) Get the score associated with the given member in a sorted set. <https://redis.io/commands/zscore>
244
 * @method mixed zunionstore($destination, $numkeys, $key, ...$options) Add multiple sorted sets and store the resulting sorted set in a new key. <https://redis.io/commands/zunionstore>
245
 * @method mixed scan($cursor, $MATCH = null, $pattern = null, $COUNT = null, $count = null) Incrementally iterate the keys space. <https://redis.io/commands/scan>
246
 * @method mixed sscan($key, $cursor, $MATCH = null, $pattern = null, $COUNT = null, $count = null) Incrementally iterate Set elements. <https://redis.io/commands/sscan>
247
 * @method mixed hscan($key, $cursor, $MATCH = null, $pattern = null, $COUNT = null, $count = null) Incrementally iterate hash fields and associated values. <https://redis.io/commands/hscan>
248
 * @method mixed zscan($key, $cursor, $MATCH = null, $pattern = null, $COUNT = null, $count = null) Incrementally iterate sorted sets elements and associated scores. <https://redis.io/commands/zscan>
249
 */
250
final class Connection
251
{
252
    private string $hostname = 'localhost';
253
    private string $redirectConnectionString = '';
254
    private int $port = 6379;
255
    private string $unixSocket = '';
256
    private ?string $password = null;
257
    private ?int $database = null;
258
    private ?float $connectionTimeout = null;
259
    private ?float $dataTimeout = null;
260
    private bool $useSSL = false;
261
    private int $socketClientFlags = STREAM_CLIENT_CONNECT;
262
    private int $retries = 0;
263
    private int $retryInterval = 0;
264
    private array $redisCommands = [
265
        'APPEND', // Append a value to a key
266
        'AUTH', // Authenticate to the server
267
        'BGREWRITEAOF', // Asynchronously rewrite the append-only file
268
        'BGSAVE', // Asynchronously save the dataset to disk
269
        'BITCOUNT', // Count set bits in a string
270
        'BITFIELD', // Perform arbitrary bitfield integer operations on strings
271
        'BITOP', // Perform bitwise operations between strings
272
        'BITPOS', // Find first bit set or clear in a string
273
        'BLPOP', // Remove and get the first element in a list, or block until one is available
274
        'BRPOP', // Remove and get the last element in a list, or block until one is available
275
        'BRPOPLPUSH', // Pop a value from a list, push it to another list and return it; or block until one is available
276
        'CLIENT KILL', // Kill the connection of a client
277
        'CLIENT LIST', // Get the list of client connections
278
        'CLIENT GETNAME', // Get the current connection name
279
        'CLIENT PAUSE', // Stop processing commands from clients for some time
280
        'CLIENT REPLY', // Instruct the server whether to reply to commands
281
        'CLIENT SETNAME', // Set the current connection name
282
        'CLUSTER ADDSLOTS', // Assign new hash slots to receiving node
283
        'CLUSTER COUNTKEYSINSLOT', // Return the number of local keys in the specified hash slot
284
        'CLUSTER DELSLOTS', // Set hash slots as unbound in receiving node
285
        'CLUSTER FAILOVER', // Forces a slave to perform a manual failover of its master.
286
        'CLUSTER FORGET', // Remove a node from the nodes table
287
        'CLUSTER GETKEYSINSLOT', // Return local key names in the specified hash slot
288
        'CLUSTER INFO', // Provides info about Redis Cluster node state
289
        'CLUSTER KEYSLOT', // Returns the hash slot of the specified key
290
        'CLUSTER MEET', // Force a node cluster to handshake with another node
291
        'CLUSTER NODES', // Get Cluster config for the node
292
        'CLUSTER REPLICATE', // Reconfigure a node as a slave of the specified master node
293
        'CLUSTER RESET', // Reset a Redis Cluster node
294
        'CLUSTER SAVECONFIG', // Forces the node to save cluster state on disk
295
        'CLUSTER SETSLOT', // Bind a hash slot to a specific node
296
        'CLUSTER SLAVES', // List slave nodes of the specified master node
297
        'CLUSTER SLOTS', // Get array of Cluster slot to node mappings
298
        'COMMAND', // Get array of Redis command details
299
        'COMMAND COUNT', // Get total number of Redis commands
300
        'COMMAND GETKEYS', // Extract keys given a full Redis command
301
        'COMMAND INFO', // Get array of specific Redis command details
302
        'CONFIG GET', // Get the value of a configuration parameter
303
        'CONFIG REWRITE', // Rewrite the configuration file with the in memory configuration
304
        'CONFIG SET', // Set a configuration parameter to the given value
305
        'CONFIG RESETSTAT', // Reset the stats returned by INFO
306
        'DBSIZE', // Return the number of keys in the selected database
307
        'DEBUG OBJECT', // Get debugging information about a key
308
        'DEBUG SEGFAULT', // Make the server crash
309
        'DECR', // Decrement the integer value of a key by one
310
        'DECRBY', // Decrement the integer value of a key by the given number
311
        'DEL', // Delete a key
312
        'DISCARD', // Discard all commands issued after MULTI
313
        'DUMP', // Return a serialized version of the value stored at the specified key.
314
        'ECHO', // Echo the given string
315
        'EVAL', // Execute a Lua script server side
316
        'EVALSHA', // Execute a Lua script server side
317
        'EXEC', // Execute all commands issued after MULTI
318
        'EXISTS', // Determine if a key exists
319
        'EXPIRE', // Set a key's time to live in seconds
320
        'EXPIREAT', // Set the expiration for a key as a UNIX timestamp
321
        'FLUSHALL', // Remove all keys from all databases
322
        'FLUSHDB', // Remove all keys from the current database
323
        'GEOADD', // Add one or more geospatial items in the geospatial index represented using a sorted set
324
        'GEOHASH', // Returns members of a geospatial index as standard geohash strings
325
        'GEOPOS', // Returns longitude and latitude of members of a geospatial index
326
        'GEODIST', // Returns the distance between two members of a geospatial index
327
        'GEORADIUS', // Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a point
328
        'GEORADIUSBYMEMBER', // Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a member
329
        'GET', // Get the value of a key
330
        'GETBIT', // Returns the bit value at offset in the string value stored at key
331
        'GETRANGE', // Get a substring of the string stored at a key
332
        'GETSET', // Set the string value of a key and return its old value
333
        'HDEL', // Delete one or more hash fields
334
        'HEXISTS', // Determine if a hash field exists
335
        'HGET', // Get the value of a hash field
336
        'HGETALL', // Get all the fields and values in a hash
337
        'HINCRBY', // Increment the integer value of a hash field by the given number
338
        'HINCRBYFLOAT', // Increment the float value of a hash field by the given amount
339
        'HKEYS', // Get all the fields in a hash
340
        'HLEN', // Get the number of fields in a hash
341
        'HMGET', // Get the values of all the given hash fields
342
        'HMSET', // Set multiple hash fields to multiple values
343
        'HSET', // Set the string value of a hash field
344
        'HSETNX', // Set the value of a hash field, only if the field does not exist
345
        'HSTRLEN', // Get the length of the value of a hash field
346
        'HVALS', // Get all the values in a hash
347
        'INCR', // Increment the integer value of a key by one
348
        'INCRBY', // Increment the integer value of a key by the given amount
349
        'INCRBYFLOAT', // Increment the float value of a key by the given amount
350
        'INFO', // Get information and statistics about the server
351
        'KEYS', // Find all keys matching the given pattern
352
        'LASTSAVE', // Get the UNIX time stamp of the last successful save to disk
353
        'LINDEX', // Get an element from a list by its index
354
        'LINSERT', // Insert an element before or after another element in a list
355
        'LLEN', // Get the length of a list
356
        'LPOP', // Remove and get the first element in a list
357
        'LPUSH', // Prepend one or multiple values to a list
358
        'LPUSHX', // Prepend a value to a list, only if the list exists
359
        'LRANGE', // Get a range of elements from a list
360
        'LREM', // Remove elements from a list
361
        'LSET', // Set the value of an element in a list by its index
362
        'LTRIM', // Trim a list to the specified range
363
        'MGET', // Get the values of all the given keys
364
        'MIGRATE', // Atomically transfer a key from a Redis instance to another one.
365
        'MONITOR', // Listen for all requests received by the server in real time
366
        'MOVE', // Move a key to another database
367
        'MSET', // Set multiple keys to multiple values
368
        'MSETNX', // Set multiple keys to multiple values, only if none of the keys exist
369
        'MULTI', // Mark the start of a transaction block
370
        'OBJECT', // Inspect the internals of Redis objects
371
        'PERSIST', // Remove the expiration from a key
372
        'PEXPIRE', // Set a key's time to live in milliseconds
373
        'PEXPIREAT', // Set the expiration for a key as a UNIX timestamp specified in milliseconds
374
        'PFADD', // Adds the specified elements to the specified HyperLogLog.
375
        'PFCOUNT', // Return the approximated cardinality of the set(s) observed by the HyperLogLog at key(s).
376
        'PFMERGE', // Merge N different HyperLogLogs into a single one.
377
        'PING', // Ping the server
378
        'PSETEX', // Set the value and expiration in milliseconds of a key
379
        'PSUBSCRIBE', // Listen for messages published to channels matching the given patterns
380
        'PUBSUB', // Inspect the state of the Pub/Sub subsystem
381
        'PTTL', // Get the time to live for a key in milliseconds
382
        'PUBLISH', // Post a message to a channel
383
        'PUNSUBSCRIBE', // Stop listening for messages posted to channels matching the given patterns
384
        'QUIT', // Close the connection
385
        'RANDOMKEY', // Return a random key from the keyspace
386
        'READONLY', // Enables read queries for a connection to a cluster slave node
387
        'READWRITE', // Disables read queries for a connection to a cluster slave node
388
        'RENAME', // Rename a key
389
        'RENAMENX', // Rename a key, only if the new key does not exist
390
        'RESTORE', // Create a key using the provided serialized value, previously obtained using DUMP.
391
        'ROLE', // Return the role of the instance in the context of replication
392
        'RPOP', // Remove and get the last element in a list
393
        'RPOPLPUSH', // Remove the last element in a list, prepend it to another list and return it
394
        'RPUSH', // Append one or multiple values to a list
395
        'RPUSHX', // Append a value to a list, only if the list exists
396
        'SADD', // Add one or more members to a set
397
        'SAVE', // Synchronously save the dataset to disk
398
        'SCARD', // Get the number of members in a set
399
        'SCRIPT DEBUG', // Set the debug mode for executed scripts.
400
        'SCRIPT EXISTS', // Check existence of scripts in the script cache.
401
        'SCRIPT FLUSH', // Remove all the scripts from the script cache.
402
        'SCRIPT KILL', // Kill the script currently in execution.
403
        'SCRIPT LOAD', // Load the specified Lua script into the script cache.
404
        'SDIFF', // Subtract multiple sets
405
        'SDIFFSTORE', // Subtract multiple sets and store the resulting set in a key
406
        'SELECT', // Change the selected database for the current connection
407
        'SET', // Set the string value of a key
408
        'SETBIT', // Sets or clears the bit at offset in the string value stored at key
409
        'SETEX', // Set the value and expiration of a key
410
        'SETNX', // Set the value of a key, only if the key does not exist
411
        'SETRANGE', // Overwrite part of a string at key starting at the specified offset
412
        'SHUTDOWN', // Synchronously save the dataset to disk and then shut down the server
413
        'SINTER', // Intersect multiple sets
414
        'SINTERSTORE', // Intersect multiple sets and store the resulting set in a key
415
        'SISMEMBER', // Determine if a given value is a member of a set
416
        'SLAVEOF', // Make the server a slave of another instance, or promote it as master
417
        'SLOWLOG', // Manages the Redis slow queries log
418
        'SMEMBERS', // Get all the members in a set
419
        'SMOVE', // Move a member from one set to another
420
        'SORT', // Sort the elements in a list, set or sorted set
421
        'SPOP', // Remove and return one or multiple random members from a set
422
        'SRANDMEMBER', // Get one or multiple random members from a set
423
        'SREM', // Remove one or more members from a set
424
        'STRLEN', // Get the length of the value stored in a key
425
        'SUBSCRIBE', // Listen for messages published to the given channels
426
        'SUNION', // Add multiple sets
427
        'SUNIONSTORE', // Add multiple sets and store the resulting set in a key
428
        'SWAPDB', // Swaps two Redis databases
429
        'SYNC', // Internal command used for replication
430
        'TIME', // Return the current server time
431
        'TOUCH', // Alters the last access time of a key(s). Returns the number of existing keys specified.
432
        'TTL', // Get the time to live for a key
433
        'TYPE', // Determine the type stored at key
434
        'UNSUBSCRIBE', // Stop listening for messages posted to the given channels
435
        'UNLINK', // Delete a key asynchronously in another thread. Otherwise it is just as DEL, but non blocking.
436
        'UNWATCH', // Forget about all watched keys
437
        'WAIT', // Wait for the synchronous replication of all the write commands sent in the context of the current connection
438
        'WATCH', // Watch the given keys to determine execution of the MULTI/EXEC block
439
        'XACK', // Removes one or multiple messages from the pending entries list (PEL) of a stream consumer group
440
        'XADD', // Appends the specified stream entry to the stream at the specified key
441
        'XCLAIM', // Changes the ownership of a pending message, so that the new owner is the consumer specified as the command argument
442
        'XDEL', // Removes the specified entries from a stream, and returns the number of entries deleted
443
        'XGROUP', // Manages the consumer groups associated with a stream data structure
444
        'XINFO', // Retrieves different information about the streams and associated consumer groups
445
        'XLEN', // Returns the number of entries inside a stream
446
        'XPENDING', // Fetching data from a stream via a consumer group, and not acknowledging such data, has the effect of creating pending entries
447
        'XRANGE', // Returns the stream entries matching a given range of IDs
448
        'XREAD', // Read data from one or multiple streams, only returning entries with an ID greater than the last received ID reported by the caller
449
        'XREADGROUP', // Special version of the XREAD command with support for consumer groups
450
        'XREVRANGE', // Exactly like XRANGE, but with the notable difference of returning the entries in reverse order, and also taking the start-end range in reverse order
451
        'XTRIM', // Trims the stream to a given number of items, evicting older items (items with lower IDs) if needed
452
        'ZADD', // Add one or more members to a sorted set, or update its score if it already exists
453
        'ZCARD', // Get the number of members in a sorted set
454
        'ZCOUNT', // Count the members in a sorted set with scores within the given values
455
        'ZINCRBY', // Increment the score of a member in a sorted set
456
        'ZINTERSTORE', // Intersect multiple sorted sets and store the resulting sorted set in a new key
457
        'ZLEXCOUNT', // Count the number of members in a sorted set between a given lexicographical range
458
        'ZRANGE', // Return a range of members in a sorted set, by index
459
        'ZRANGEBYLEX', // Return a range of members in a sorted set, by lexicographical range
460
        'ZREVRANGEBYLEX', // Return a range of members in a sorted set, by lexicographical range, ordered from higher to lower strings.
461
        'ZRANGEBYSCORE', // Return a range of members in a sorted set, by score
462
        'ZRANK', // Determine the index of a member in a sorted set
463
        'ZREM', // Remove one or more members from a sorted set
464
        'ZREMRANGEBYLEX', // Remove all members in a sorted set between the given lexicographical range
465
        'ZREMRANGEBYRANK', // Remove all members in a sorted set within the given indexes
466
        'ZREMRANGEBYSCORE', // Remove all members in a sorted set within the given scores
467
        'ZREVRANGE', // Return a range of members in a sorted set, by index, with scores ordered from high to low
468
        'ZREVRANGEBYSCORE', // Return a range of members in a sorted set, by score, with scores ordered from high to low
469
        'ZREVRANK', // Determine the index of a member in a sorted set, with scores ordered from high to low
470
        'ZSCORE', // Get the score associated with the given member in a sorted set
471
        'ZUNIONSTORE', // Add multiple sorted sets and store the resulting sorted set in a new key
472
        'SCAN', // Incrementally iterate the keys space
473
        'SSCAN', // Incrementally iterate Set elements
474
        'HSCAN', // Incrementally iterate hash fields and associated values
475
        'ZSCAN', // Incrementally iterate sorted sets elements and associated scores
476
    ];
477
    private array $pool = [];
478
    private bool $runEvent = false;
479
    private EventDispatcherInterface $dispatch;
480
    private LoggerInterface $logger;
481
    private string $dsn;
482
483
    public function __construct(EventDispatcherInterface $dispatch, LoggerInterface $logger)
484
    {
485
        $this->dispatch = $dispatch;
486
        $this->logger = $logger;
487
    }
488 14
489
    /**
490 14
     * Closes the connection when this component is being serialized.
491 14
     *
492 14
     * @return array
493
     */
494
    public function __sleep(): array
495
    {
496
        unset($this->dispatch);
497
498
        $this->close();
499 1
500
        return array_keys(get_object_vars($this));
501 1
    }
502
503 1
    /**
504
     * Return the connection string used to open a socket connection. During a redirect (cluster mode) this will be the
505 1
     * target of the redirect.
506
     *
507
     * @return string socket connection string
508
     */
509
    public function getConnectionString(): string
510
    {
511
        if ($this->unixSocket) {
512
            return 'unix://' . $this->unixSocket;
513
        }
514 14
515
        return 'tcp://' . ($this->redirectConnectionString ?: "$this->hostname:$this->port");
516 14
    }
517
518
    /**
519
     * Return the connection resource if a connection to the target has been established before, `false` otherwise.
520 14
     *
521
     * @return resource|false
522
     */
523
    public function getSocket()
524
    {
525
        return ArrayHelper::getValue($this->pool, $this->getConnectionString(), false);
526
    }
527
528 14
    /**
529
     * Returns a value indicating whether the DB connection is established.
530 14
     *
531
     * @return bool whether the DB connection is established
532
     */
533
    public function getIsActive(): bool
534
    {
535
        return ArrayHelper::getValue($this->pool, "$this->hostname:$this->port", false) !== false;
536
    }
537
538
    /**
539
     * Establishes a DB connection.
540
     *
541
     * It does nothing if a DB connection has already been established.
542
     *
543
     * @throws Exception if connection fails
544
     */
545
    public function open(): void
546
    {
547
        if ($this->getSocket() !== false) {
548
            return;
549
        }
550 14
551
        $this->dsn = $this->getConnectionString() . ', database=' . $this->database;
552 14
553 14
        $this->logger->log(LogLevel::INFO, 'Opening redis DB connection: ' . $this->dsn . ' ' . __METHOD__);
554
555
        $socket = @stream_socket_client(
556 14
            $this->getConnectionString(),
557
            $errorNumber,
558 14
            $errorDescription,
559
            $this->connectionTimeout ?? (float) ini_get('default_socket_timeout'),
560 14
            $this->socketClientFlags
561 14
        );
562
563
        if ($socket) {
0 ignored issues
show
introduced by
$socket is of type false|resource, thus it always evaluated to false.
Loading history...
564 14
            $this->pool[ $this->getConnectionString() ] = $socket;
565 14
566
            if ($this->dataTimeout !== null) {
567
                stream_set_timeout(
568 14
                    $socket,
569 14
                    $timeout = (int) $this->dataTimeout,
570
                    (int) (($this->dataTimeout - $timeout) * 1000000)
571 14
                );
572
            }
573
574
            if ($this->useSSL) {
575
                stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
576
            }
577
578
            if ($this->password !== null) {
579 14
                $this->executeCommand('AUTH', [$this->password]);
580
            }
581
582
            if ($this->database !== null) {
583 14
                $this->executeCommand('SELECT', [$this->database]);
584
            }
585
586
            if ($this->runEvent) {
587 14
                $this->dispatch->dispatch(new AfterOpen());
588 14
            }
589
        } else {
590
            $message = "Failed to open redis DB connection ($this->dsn): $errorNumber - $errorDescription " . __CLASS__;
591 14
592 14
            $this->logger->log(
593
                LogLevel::ERROR,
594
                $message
595
            );
596
597
            throw new Exception($message, $errorDescription, $errorNumber);
598
        }
599
    }
600
601
    /**
602
     * Closes the currently active DB connection.
603
     *
604 14
     * It does nothing if the connection is already closed.
605
     */
606
    public function close(): void
607
    {
608
        foreach ($this->pool as $socket) {
609
            $this->dsn = $this->getConnectionString() . ', database=' . $this->database;
610
611 5
            $this->logger->log(LogLevel::INFO, 'Closing DB connection: ' . $this->dsn . ' ' . __METHOD__);
612
613 5
            try {
614 5
                $this->executeCommand('QUIT');
615
            } catch (SocketException $e) {
616 5
                /** ignore errors when quitting a closed connection. */
617
            }
618
619 5
            fclose($socket);
620 2
        }
621
622
        $this->pool = [];
623
    }
624 5
625
    /**
626
     * Returns the name of the DB driver.
627 5
     *
628 5
     * @return string name of the DB driver.
629
     */
630
    public function getDriverName(): string
631
    {
632
        return 'redis';
633
    }
634
635
    /**
636
     * Allows issuing all supported commands via magic methods.
637
     *
638
     * ```php
639
     * $redis->hmset('test_collection', 'key1', 'val1', 'key2', 'val2')
640
     * ```
641
     *
642
     * @param string $name name of the missing method to execute.
643
     * @param array $params method call arguments.
644
     *
645
     * @throws Exception
646
     *
647
     * @return mixed
648
     */
649
    public function __call(string $name, array $params)
650
    {
651
        $redisCommand = strtoupper((new Inflector())->toWords($name, false));
0 ignored issues
show
Unused Code introduced by
The call to Yiisoft\Strings\Inflector::toWords() has too many arguments starting with false. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

651
        $redisCommand = strtoupper((new Inflector())->/** @scrutinizer ignore-call */ toWords($name, false));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
652
653
        if (in_array($redisCommand, $this->redisCommands, true)) {
654 14
            return $this->executeCommand($redisCommand, $params);
655
        }
656 14
    }
657
658 14
    /**
659 14
     * Executes a redis command.
660
     *
661
     * For a list of available commands and their parameters see https://redis.io/commands.
662
     *
663
     * The params array should contain the params separated by white space, e.g. to execute
664
     * `SET mykey somevalue NX` call the following:
665
     *
666
     * ```php
667
     * $redis->executeCommand('SET', ['mykey', 'somevalue', 'NX']);
668
     * ```
669
     *
670
     * @param string $name the name of the command.
671
     * @param array $params list of parameters for the command.
672
     *
673
     * @throws Exception for commands that return [error reply](https://redis.io/topics/protocol#error-reply).
674
     *
675
     * @return array|bool|null|string Dependent on the executed command this method will return different data types:
676
     *
677
     * - `true` for commands that return "status reply" with the message `'OK'` or `'PONG'`.
678
     * - `string` for commands that return "status reply" that does not have the message `OK`.
679
     * - `string` for commands that return "integer reply"
680
     *   as the value is in the range of a signed 64 bit integer.
681
     * - `string` or `null` for commands that return "bulk reply".
682
     * - `array` for commands that return "Multi-bulk replies".
683
     *
684
     * See [redis protocol description](https://redis.io/topics/protocol)
685
     *
686
     * for details on the mentioned reply types.
687
     */
688
    public function executeCommand($name, $params = [])
689
    {
690
        $this->open();
691
692
        $params = array_merge(explode(' ', $name), $params);
693 14
        $command = '*' . count($params) . "\r\n";
694
695 14
        foreach ($params as $arg) {
696
            $command .= '$' . mb_strlen((string) $arg, '8bit') . "\r\n" . $arg . "\r\n";
697 14
        }
698 14
699
        $this->logger->log(LogLevel::INFO, "Executing Redis Command: {$name} " . ' ' . __METHOD__);
700 14
701 14
        if ($this->retries > 0) {
702
            $tries = $this->retries;
703
            while ($tries-- > 0) {
704 14
                try {
705
                    return $this->sendCommandInternal($command, $params);
706 14
                } catch (SocketException $e) {
707 2
                    $this->logger->log(LogLevel::ERROR, $e . ' ' . __METHOD__);
708 2
709
                    /** backup retries, fail on commands that fail inside here. */
710 2
                    $retries = $this->retries;
711 2
712 2
                    $this->retries = 0;
713
714
                    $this->close();
715 2
716
                    if ($this->retryInterval > 0) {
717 2
                        usleep($this->retryInterval);
718
                    }
719 2
720
                    $this->open();
721 2
722
                    $this->retries = $retries;
723
                }
724
            }
725 2
        }
726
727 2
        return $this->sendCommandInternal($command, $params);
728
    }
729
730
    /**
731
     * Sends RAW command string to the server.
732 14
     *
733
     * @param string $command
734
     * @param array $params
735
     *
736
     * @throws Exception
737
     * @throws SocketException on connection error.
738
     *
739
     * @return array|bool|false|mixed|string|null
740
     */
741
    private function sendCommandInternal(string $command, array $params = [])
742
    {
743
        $written = @fwrite($this->getSocket(), $command);
0 ignored issues
show
Bug introduced by
It seems like $this->getSocket() can also be of type false; however, parameter $stream of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

743
        $written = @fwrite(/** @scrutinizer ignore-type */ $this->getSocket(), $command);
Loading history...
744
745
        if ($written === false) {
746 14
            throw new SocketException("Failed to write to socket.\nRedis command was: " . $command);
747
        }
748 14
        if ($written !== ($len = mb_strlen($command, '8bit'))) {
749
            throw new SocketException("Failed to write to socket. $written of $len bytes written.\nRedis command was: " . $command);
750 14
        }
751
752
        return $this->parseResponse($params, $command);
753 14
    }
754
755
    private function parseResponse(array $params, ?string $command = null)
756
    {
757 14
        $prettyCommand = implode(' ', $params);
758
759
        if (($line = fgets($this->getSocket())) === false) {
0 ignored issues
show
Bug introduced by
It seems like $this->getSocket() can also be of type false; however, parameter $stream of fgets() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

759
        if (($line = fgets(/** @scrutinizer ignore-type */ $this->getSocket())) === false) {
Loading history...
760 14
            throw new SocketException("Failed to read from socket.\nRedis command was: " . $prettyCommand);
761
        }
762 14
763
        $type = $line[0];
764 14
        $line = mb_substr($line, 1, -2, '8bit');
765 3
766
        switch ($type) {
767
            case '+': // Status reply
768 14
                if ($line === 'OK' || $line === 'PONG') {
769 14
                    return true;
770
                }
771
772 14
                return $line;
773 14
            case '-': // Error reply
774 14
                if ($this->isRedirect($line)) {
775
                    return $this->redirect($line, $command, $params);
0 ignored issues
show
Bug introduced by
It seems like $command can also be of type null; however, parameter $command of Yiisoft\Db\Redis\Connection::redirect() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

775
                    return $this->redirect($line, /** @scrutinizer ignore-type */ $command, $params);
Loading history...
776
                }
777 1
778 9
                throw new Exception("Redis error: " . $line . "\nRedis command was: " . $prettyCommand);
779
            case ':': // Integer reply
780
                // no cast to int as it is in the range of a signed 64 bit integer
781
                return $line;
782
            case '$': // Bulk replies
783
                if ($line === '-1') {
784 9
                    return null;
785
                }
786 1
787 9
                $length = (int)$line + 2;
788 9
                $data = '';
789 1
790
                while ($length > 0) {
791
                    if (($block = fread($this->getSocket(), $length)) === false) {
0 ignored issues
show
Bug introduced by
It seems like $this->getSocket() can also be of type false; however, parameter $stream of fread() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

791
                    if (($block = fread(/** @scrutinizer ignore-type */ $this->getSocket(), $length)) === false) {
Loading history...
792 9
                        throw new SocketException("Failed to read from socket.\nRedis command was: " . $prettyCommand);
793 9
                    }
794
                    $data .= $block;
795 9
                    $length -= mb_strlen($block, '8bit');
796 9
                }
797
798
                return mb_substr($data, 0, -2, '8bit');
799 9
            case '*': // Multi-bulk replies
800 9
                $count = (int) $line;
801
                $data = [];
802
                for ($i = 0; $i < $count; $i++) {
803 9
                    $data[] = $this->parseResponse($params);
804 2
                }
805 2
806 2
                return $data;
807 2
            default:
808 2
                throw new Exception(
809
                    'Received illegal data from redis: ' . $line . "\nRedis command was: " . $prettyCommand
810
                );
811 2
        }
812
    }
813
814
    private function isRedirect(string $line): bool
815
    {
816
        return is_string($line) && mb_strpos($line, 'MOVED') === 0;
817
    }
818
819
    private function redirect(string $redirect, string $command, array $params = [])
820
    {
821
        $responseParts = preg_split('/\s+/', $redirect);
822
823
        $this->redirectConnectionString = ArrayHelper::getValue($responseParts, 2);
824
825
        if ($this->redirectConnectionString) {
826
            $this->logger->log(LogLevel::INFO, 'Redirecting to ' . $this->getConnectionString() . ' ' . __METHOD__);
827
828
            $this->open();
829
830
            $response = $this->sendCommandInternal($command, $params);
831
832
            $this->redirectConnectionString = '';
833
834
            return $response;
835
        }
836
837
        throw new Exception('No hostname found in redis redirect (MOVED): ' . VarDumper::dumpAsString($redirect));
0 ignored issues
show
Bug introduced by
The method dumpAsString() does not exist on Yiisoft\VarDumper\VarDumper. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

837
        throw new Exception('No hostname found in redis redirect (MOVED): ' . VarDumper::/** @scrutinizer ignore-call */ dumpAsString($redirect));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
838
    }
839
840
    /**
841
     * @return float timeout to use for connection to redis. If not set the timeout set in php.ini will be used:
842
     * `ini_get("default_socket_timeout")`.
843
     */
844
    public function getConnectionTimeout(): ?float
845
    {
846
        return $this->connectionTimeout;
847
    }
848
849
    /**
850
     * @return int the redis database to use. This is an integer value starting from 0. Defaults to 0.
851
     *
852
     * You can disable the SELECT command sent after connection by setting this property to `null`.
853
     */
854
    public function getDatabase(): ?int
855
    {
856
        return $this->database;
857
    }
858
859 1
    /**
860
     * @return float timeout to use for redis socket when reading and writing data. If not set the php default value
861 1
     * will be used.
862
     */
863
    public function getDataTimeout(): ?float
864
    {
865
        return $this->dataTimeout;
866
    }
867
868
    /**
869
     * @return string the hostname or ip address to use for connecting to the redis server. Defaults to 'localhost'.
870
     *
871
     * If {@see unixSocket} is specified, hostname and {@see port} will be ignored.
872
     */
873
    public function getHostname(): string
874
    {
875
        return $this->hostname;
876
    }
877
878
    /**
879
     * @return string the password for establishing DB connection. Defaults to null meaning no AUTH command is sent.
880
     *
881
     * {@see https://redis.io/commands/auth}
882
     */
883
    public function getPassword(): string
884
    {
885
        return $this->password;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->password could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
886
    }
887
888
    /**
889
     * @return array redis redirect socket connection pool.
890
     */
891
    public function getPool(): array
892
    {
893
        return $this->pool;
894
    }
895
896
    /**
897
     * @return int the port to use for connecting to the redis server. Default port is 6379.
898
     *
899
     * If {@see unixSocket} is specified, {@see hostname} and port will be ignored.
900
     */
901
    public function getPort(): int
902
    {
903
        return $this->port;
904
    }
905
906
    /**
907
     * @return string if the query gets redirected, use this as the temporary new hostname.
908
     */
909
    public function getRedirectConnectionString(): string
910
    {
911
        return $this->redirectConnectionString;
912
    }
913
914
    /**
915
     * @return array List of available redis commands.
916
     *
917
     * {@see https://redis.io/commands}
918
     */
919
    public function getRedisCommands(): array
920
    {
921
        return $this->redisCommands;
922
    }
923
924
    /**
925
     * @return int The number of times a command execution should be retried when a connection failure occurs.
926
     *
927
     * This is used in {@see executeCommand()} when a {@see SocketException} is thrown.
928
     *
929
     * Defaults to 0 meaning no retries on failure.
930
     */
931
    public function getRetries(): int
932
    {
933
        return $this->retries;
934
    }
935
936
    /**
937
     * @return int The retry interval in microseconds to wait between retry.
938
     *
939
     * This is used in {@see executeCommand()} when a {@see SocketException} is thrown.
940
     *
941
     * Defaults to 0 meaning no wait.
942
     */
943
    public function getRetryInterval(): int
944
    {
945
        return $this->retryInterval;
946
    }
947
948
    public function getRunEvent(): bool
949
    {
950
        return $this->runEvent;
951
    }
952
953
    /**
954
     * @return integer Bitmask field which may be set to any combination of connection flags passed to
955
     * [stream_socket_client()](https://www.php.net/manual/en/function.stream-socket-client.php).
956
     *
957
     * Currently the select of connection flags is limited to `STREAM_CLIENT_CONNECT` (default),
958
     * `STREAM_CLIENT_ASYNC_CONNECT` and `STREAM_CLIENT_PERSISTENT`.
959
     *
960
     * > Warning: `STREAM_CLIENT_PERSISTENT` will make PHP reuse connections to the same server. If you are using
961
     * > multiple connection objects to refer to different redis {@see $database|databases} on the same {@see port},
962
     * > redis commands may get executed on the wrong database. `STREAM_CLIENT_PERSISTENT` is only safe to use if you
963
     * > use only one database.
964
     * >
965
     * > You may still use persistent connections in this case when disambiguating ports as described
966
     * > in [a comment on the PHP manual](https://www.php.net/manual/en/function.stream-socket-client.php#105393)
967
     * > e.g. on the connection used for session storage, specify the port as:
968
     * >
969
     * > ```php
970
     * > 'port' => '6379/session'
971
     * > ```
972
     *
973
     * {@see https://www.php.net/manual/en/function.stream-socket-client.php}
974
     */
975
    public function getSocketClientFlags(): int
976
    {
977
        return $this->socketClientFlags;
978
    }
979
980
    /**
981
     * @return string the unix socket path (e.g. `/var/run/redis/redis.sock`) to use for connecting to the redis server.
982
     * This can be used instead of {@see hostname} and {@see port} to connect to the server using a unix socket. If a
983
     * unix socket path is specified, {@see hostname} and {@see port} will be ignored.
984
     */
985
    public function getUnixSocket(): string
986
    {
987
        return $this->unixSocket;
988
    }
989
990
    /**
991
     * @return bool Send sockets over SSL protocol. Default state is false.
992
     */
993
    public function getUseSSL(): bool
994
    {
995
        return $this->useSSL;
996
    }
997
998
    public function connectionTimeout(float $value): void
999
    {
1000
        $this->connectionTimeout = $value;
1001
    }
1002
1003
    public function database(int $value): void
1004
    {
1005
        $this->database = $value;
1006
    }
1007
1008 14
    public function dataTimeout(float $value): void
1009
    {
1010 14
        $this->dataTimeout = $value;
1011 14
    }
1012
1013
    public function hostname(string $value): void
1014
    {
1015
        $this->hostname = $value;
1016
    }
1017
1018 14
    public function password(?string $value): void
1019
    {
1020 14
        $this->password = $value;
1021 14
    }
1022
1023 14
    public function port(int $value): void
1024
    {
1025 14
        $this->port = $value;
1026 14
    }
1027
1028 14
    public function pool(array $value): void
1029
    {
1030 14
        $this->pool = $value;
1031 14
    }
1032
1033
    public function redirectConnectionString(string $value): void
1034
    {
1035
        $this->redirectConnectionString = $value;
1036
    }
1037
1038
    public function redisCommands(array $value): void
1039
    {
1040
        $this->redisCommands = $value;
1041
    }
1042
1043
    public function retries(int $value): void
1044
    {
1045
        $this->retries = $value;
1046
    }
1047
1048 2
    public function retryInterval(int $value): void
1049
    {
1050 2
        $this->retryInterval = $value;
1051 2
    }
1052
1053
    public function runEvent(bool $value): void
1054
    {
1055
        $this->runEvent = $value;
1056
    }
1057
1058 1
    public function socketClientFlags(int $value): void
1059
    {
1060 1
        $this->socketClientFlags = $value;
1061 1
    }
1062
1063
    public function unixSocket(string $value): void
1064
    {
1065
        $this->unixSocket = $value;
1066
    }
1067
1068
    public function useSSL(bool $useSSL): void
1069
    {
1070
        $this->useSSL = $useSSL;
1071
    }
1072
1073
    /**
1074
     * @return string the Data Source Name, or DSN, contains the information required to connect to the database.
1075
     *
1076
     * Please refer to the [PHP manual](https://secure.php.net/manual/en/pdo.construct.php) on the format of the DSN
1077
     * string.
1078
     */
1079
    public function getDsn(): string
1080
    {
1081
        return $this->dsn;
1082
    }
1083
1084
    /**
1085
     * Returns a server version as a string comparable by {@see version_compare()}.
1086
     *
1087
     * @throws Exception
1088
     *
1089
     * @return string server version as a string.
1090
     */
1091
    public function getServerVersion(): string
1092
    {
1093
        $version = (explode("\r\n", $this->executeCommand('INFO', ['server'])));
1094
1095
        return $version[1];
1096
    }
1097
}
1098