Connection::getRetryInterval()   A
last analyzed

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

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

748
        $written = @fwrite(/** @scrutinizer ignore-type */ $this->getSocket(), $command);
Loading history...
749
750 14
        if ($written === false) {
751
            throw new SocketException("Failed to write to socket.\nRedis command was: " . $command);
752
        }
753 14
        if ($written !== ($len = mb_strlen($command, '8bit'))) {
754
            throw new SocketException("Failed to write to socket. $written of $len bytes written.\nRedis command was: " . $command);
755
        }
756
757 14
        return $this->parseResponse($params, $command);
758
    }
759
760 14
    private function parseResponse(array $params, ?string $command = null)
761
    {
762 14
        $prettyCommand = implode(' ', $params);
763
764 14
        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

764
        if (($line = fgets(/** @scrutinizer ignore-type */ $this->getSocket())) === false) {
Loading history...
765 3
            throw new SocketException("Failed to read from socket.\nRedis command was: " . $prettyCommand);
766
        }
767
768 14
        $type = $line[0];
769 14
        $line = mb_substr($line, 1, -2, '8bit');
770
771
        switch ($type) {
772 14
            case '+': // Status reply
773 14
                if ($line === 'OK' || $line === 'PONG') {
774 14
                    return true;
775
                }
776
777 1
                return $line;
778 9
            case '-': // Error reply
779
                if ($this->isRedirect($line)) {
780
                    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

780
                    return $this->redirect($line, /** @scrutinizer ignore-type */ $command, $params);
Loading history...
781
                }
782
783
                throw new Exception("Redis error: " . $line . "\nRedis command was: " . $prettyCommand);
784 9
            case ':': // Integer reply
785
                // no cast to int as it is in the range of a signed 64 bit integer
786 1
                return $line;
787 9
            case '$': // Bulk replies
788 9
                if ($line === '-1') {
789 1
                    return null;
790
                }
791
792 9
                $length = (int)$line + 2;
793 9
                $data = '';
794
795 9
                while ($length > 0) {
796 9
                    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

796
                    if (($block = fread(/** @scrutinizer ignore-type */ $this->getSocket(), $length)) === false) {
Loading history...
797
                        throw new SocketException("Failed to read from socket.\nRedis command was: " . $prettyCommand);
798
                    }
799 9
                    $data .= $block;
800 9
                    $length -= mb_strlen($block, '8bit');
801
                }
802
803 9
                return mb_substr($data, 0, -2, '8bit');
804 2
            case '*': // Multi-bulk replies
805 2
                $count = (int) $line;
806 2
                $data = [];
807 2
                for ($i = 0; $i < $count; $i++) {
808 2
                    $data[] = $this->parseResponse($params);
809
                }
810
811 2
                return $data;
812
            default:
813
                throw new Exception(
814
                    'Received illegal data from redis: ' . $line . "\nRedis command was: " . $prettyCommand
815
                );
816
        }
817
    }
818
819
    private function isRedirect(string $line): bool
820
    {
821
        return is_string($line) && mb_strpos($line, 'MOVED') === 0;
822
    }
823
824
    private function redirect(string $redirect, string $command, array $params = [])
825
    {
826
        $responseParts = preg_split('/\s+/', $redirect);
827
828
        $this->redirectConnectionString = ArrayHelper::getValue($responseParts, 2);
829
830
        if ($this->redirectConnectionString) {
831
            $this->logger->log(LogLevel::INFO, 'Redirecting to ' . $this->getConnectionString() . ' ' . __METHOD__);
832
833
            $this->open();
834
835
            $response = $this->sendCommandInternal($command, $params);
836
837
            $this->redirectConnectionString = '';
838
839
            return $response;
840
        }
841
842
        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

842
        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...
843
    }
844
845
    /**
846
     * @return float timeout to use for connection to redis. If not set the timeout set in php.ini will be used:
847
     * `ini_get("default_socket_timeout")`.
848
     */
849
    public function getConnectionTimeout(): ?float
850
    {
851
        return $this->connectionTimeout;
852
    }
853
854
    /**
855
     * @return int the redis database to use. This is an integer value starting from 0. Defaults to 0.
856
     *
857
     * You can disable the SELECT command sent after connection by setting this property to `null`.
858
     */
859 1
    public function getDatabase(): ?int
860
    {
861 1
        return $this->database;
862
    }
863
864
    /**
865
     * @return float timeout to use for redis socket when reading and writing data. If not set the php default value
866
     * will be used.
867
     */
868
    public function getDataTimeout(): ?float
869
    {
870
        return $this->dataTimeout;
871
    }
872
873
    /**
874
     * @return string the hostname or ip address to use for connecting to the redis server. Defaults to 'localhost'.
875
     *
876
     * If {@see unixSocket} is specified, hostname and {@see port} will be ignored.
877
     */
878
    public function getHostname(): string
879
    {
880
        return $this->hostname;
881
    }
882
883
    /**
884
     * @return string the password for establishing DB connection. Defaults to null meaning no AUTH command is sent.
885
     *
886
     * {@see https://redis.io/commands/auth}
887
     */
888
    public function getPassword(): string
889
    {
890
        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...
891
    }
892
893
    /**
894
     * @return array redis redirect socket connection pool.
895
     */
896
    public function getPool(): array
897
    {
898
        return $this->pool;
899
    }
900
901
    /**
902
     * @return int the port to use for connecting to the redis server. Default port is 6379.
903
     *
904
     * If {@see unixSocket} is specified, {@see hostname} and port will be ignored.
905
     */
906
    public function getPort(): int
907
    {
908
        return $this->port;
909
    }
910
911
    /**
912
     * @return string if the query gets redirected, use this as the temporary new hostname.
913
     */
914
    public function getRedirectConnectionString(): string
915
    {
916
        return $this->redirectConnectionString;
917
    }
918
919
    /**
920
     * @return array List of available redis commands.
921
     *
922
     * {@see https://redis.io/commands}
923
     */
924
    public function getRedisCommands(): array
925
    {
926
        return $this->redisCommands;
927
    }
928
929
    /**
930
     * @return int The number of times a command execution should be retried when a connection failure occurs.
931
     *
932
     * This is used in {@see executeCommand()} when a {@see SocketException} is thrown.
933
     *
934
     * Defaults to 0 meaning no retries on failure.
935
     */
936
    public function getRetries(): int
937
    {
938
        return $this->retries;
939
    }
940
941
    /**
942
     * @return int The retry interval in microseconds to wait between retry.
943
     *
944
     * This is used in {@see executeCommand()} when a {@see SocketException} is thrown.
945
     *
946
     * Defaults to 0 meaning no wait.
947
     */
948
    public function getRetryInterval(): int
949
    {
950
        return $this->retryInterval;
951
    }
952
953
    public function getRunEvent(): bool
954
    {
955
        return $this->runEvent;
956
    }
957
958
    /**
959
     * @return integer Bitmask field which may be set to any combination of connection flags passed to
960
     * [stream_socket_client()](https://www.php.net/manual/en/function.stream-socket-client.php).
961
     *
962
     * Currently the select of connection flags is limited to `STREAM_CLIENT_CONNECT` (default),
963
     * `STREAM_CLIENT_ASYNC_CONNECT` and `STREAM_CLIENT_PERSISTENT`.
964
     *
965
     * > Warning: `STREAM_CLIENT_PERSISTENT` will make PHP reuse connections to the same server. If you are using
966
     * > multiple connection objects to refer to different redis {@see $database|databases} on the same {@see port},
967
     * > redis commands may get executed on the wrong database. `STREAM_CLIENT_PERSISTENT` is only safe to use if you
968
     * > use only one database.
969
     * >
970
     * > You may still use persistent connections in this case when disambiguating ports as described
971
     * > in [a comment on the PHP manual](https://www.php.net/manual/en/function.stream-socket-client.php#105393)
972
     * > e.g. on the connection used for session storage, specify the port as:
973
     * >
974
     * > ```php
975
     * > 'port' => '6379/session'
976
     * > ```
977
     *
978
     * {@see https://www.php.net/manual/en/function.stream-socket-client.php}
979
     */
980
    public function getSocketClientFlags(): int
981
    {
982
        return $this->socketClientFlags;
983
    }
984
985
    /**
986
     * @return string the unix socket path (e.g. `/var/run/redis/redis.sock`) to use for connecting to the redis server.
987
     * This can be used instead of {@see hostname} and {@see port} to connect to the server using a unix socket. If a
988
     * unix socket path is specified, {@see hostname} and {@see port} will be ignored.
989
     */
990
    public function getUnixSocket(): string
991
    {
992
        return $this->unixSocket;
993
    }
994
995
    /**
996
     * @return bool Send sockets over SSL protocol. Default state is false.
997
     */
998
    public function getUseSSL(): bool
999
    {
1000
        return $this->useSSL;
1001
    }
1002
1003
    public function connectionTimeout(float $value): void
1004
    {
1005
        $this->connectionTimeout = $value;
1006
    }
1007
1008 14
    public function database(int $value): void
1009
    {
1010 14
        $this->database = $value;
1011 14
    }
1012
1013
    public function dataTimeout(float $value): void
1014
    {
1015
        $this->dataTimeout = $value;
1016
    }
1017
1018 14
    public function hostname(string $value): void
1019
    {
1020 14
        $this->hostname = $value;
1021 14
    }
1022
1023 14
    public function password(?string $value): void
1024
    {
1025 14
        $this->password = $value;
1026 14
    }
1027
1028 14
    public function port(int $value): void
1029
    {
1030 14
        $this->port = $value;
1031 14
    }
1032
1033
    public function pool(array $value): void
1034
    {
1035
        $this->pool = $value;
1036
    }
1037
1038
    public function redirectConnectionString(string $value): void
1039
    {
1040
        $this->redirectConnectionString = $value;
1041
    }
1042
1043
    public function redisCommands(array $value): void
1044
    {
1045
        $this->redisCommands = $value;
1046
    }
1047
1048 2
    public function retries(int $value): void
1049
    {
1050 2
        $this->retries = $value;
1051 2
    }
1052
1053
    public function retryInterval(int $value): void
1054
    {
1055
        $this->retryInterval = $value;
1056
    }
1057
1058 1
    public function runEvent(bool $value): void
1059
    {
1060 1
        $this->runEvent = $value;
1061 1
    }
1062
1063
    public function socketClientFlags(int $value): void
1064
    {
1065
        $this->socketClientFlags = $value;
1066
    }
1067
1068
    public function unixSocket(string $value): void
1069
    {
1070
        $this->unixSocket = $value;
1071
    }
1072
1073
    public function useSSL(bool $useSSL): void
1074
    {
1075
        $this->useSSL = $useSSL;
1076
    }
1077
1078
    /**
1079
     * Creates a command for execution.
1080
     *
1081
     * @param string|null $sql the SQL statement to be executed
1082
     * @param array $params the parameters to be bound to the SQL statement
1083
     *
1084
     * @throws NotSupportedException
1085
     *
1086
     * @return Command the DB command
1087
     */
1088
    public function createCommand(?string $sql = null, array $params = []): Command
1089
    {
1090
        throw new NotSupportedException(get_class($this) . ' does not support Command::class.');
1091
    }
1092
1093
    /**
1094
     * @return string the Data Source Name, or DSN, contains the information required to connect to the database.
1095
     *
1096
     * Please refer to the [PHP manual](https://secure.php.net/manual/en/pdo.construct.php) on the format of the DSN
1097
     * string.
1098
     */
1099
    public function getDsn(): string
1100
    {
1101
        return $this->dsn;
1102
    }
1103
1104
    /**
1105
     * Returns the schema information for the database opened by this connection.
1106
     *
1107
     * @throws NotSupportedException
1108
     *
1109
     * @return Schema the schema information for the database opened by this connection.
1110
     */
1111
    public function getSchema(): Schema
1112
    {
1113
        throw new NotSupportedException(get_class($this) . ' does not support Schema::class.');
1114
    }
1115
1116
    /**
1117
     * Returns a server version as a string comparable by {@see version_compare()}.
1118
     *
1119
     * @throws Exception
1120
     *
1121
     * @return string server version as a string.
1122
     */
1123
    public function getServerVersion(): string
1124
    {
1125
        $version = (explode("\r\n", $this->executeCommand('INFO', ['server'])));
1126
1127
        return $version[1];
1128
    }
1129
1130
    /**
1131
     * Obtains the schema information for the named table.
1132
     *
1133
     * @param string $name table name.
1134
     * @param bool $refresh whether to reload the table schema even if it is found in the cache.
1135
     *
1136
     * @throws NotSupportedException
1137
     *
1138
     * @return TableSchema|null
1139
     */
1140
    public function getTableSchema($name, $refresh = false): ?TableSchema
1141
    {
1142
        throw new NotSupportedException(get_class($this) . ' does not support TableShema::class.');
1143
    }
1144
1145
    /**
1146
     * @inheritDoc
1147
     */
1148
    public function isAutoSlaveForReadQueriesEnabled(): bool
1149
    {
1150
        throw new NotSupportedException(get_class($this) . ' does not support SetEnableSlaves() method.');
1151
    }
1152
1153
    /**
1154
     * Whether to enable read/write splitting by using {@see setSlaves()} to read data. Note that if {@see setSlaves()}
1155
     * is empty, read/write splitting will NOT be enabled no matter what value this property takes.
1156
     *
1157
     * @param bool $value
1158
     *
1159
     * @throws NotSupportedException
1160
     */
1161
    public function setEnableSlaves(bool $value): void
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

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

1161
    public function setEnableSlaves(/** @scrutinizer ignore-unused */ bool $value): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1162
    {
1163
        throw new NotSupportedException(get_class($this) . ' does not support SetEnableSlaves() method.');
1164
    }
1165
1166
    /**
1167
     * Whether to enable schema caching. Note that in order to enable truly schema caching, a valid cache component as
1168
     * specified must be enabled and {@see setEnable()} must be set true.
1169
     *
1170
     * @param bool $value
1171
     */
1172
    public function setSchemaCacheEnable(bool $value): void
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

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

1172
    public function setSchemaCacheEnable(/** @scrutinizer ignore-unused */ bool $value): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1173
    {
1174
        throw new NotSupportedException(get_class($this) . ' does not support setSchemaCacheEnable() method.');
1175
    }
1176
1177
    /**
1178
     * Whether to enable query caching. Note that in order to enable query caching, a valid cache component as specified
1179
     * must be enabled and {@see enabled} must be set true. Also, only the results of the queries enclosed within
1180
     * {@see cache()} will be cached.
1181
     */
1182
    public function setQueryCacheEnable(bool $value): void
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

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

1182
    public function setQueryCacheEnable(/** @scrutinizer ignore-unused */ bool $value): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1183
    {
1184
        throw new NotSupportedException(get_class($this) . ' does not support setQueryCacheEnable() method.');
1185
    }
1186
1187
    /**
1188
     * Quotes a column name for use in a query.
1189
     *
1190
     * If the column name contains prefix, the prefix will also be properly quoted.
1191
     * If the column name is already quoted or contains special characters including '(', '[[' and '{{', then this
1192
     * method will do nothing.
1193
     *
1194
     * @param string $name column name
1195
     *
1196
     * @return string the properly quoted column name
1197
     */
1198
    public function quoteColumnName(string $name): string
0 ignored issues
show
Unused Code introduced by
The parameter $name is not used and could be removed. ( Ignorable by Annotation )

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

1198
    public function quoteColumnName(/** @scrutinizer ignore-unused */ string $name): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1199
    {
1200
        throw new NotSupportedException(get_class($this) . ' does not support quoteColumnName() method.');
1201
    }
1202
}
1203