Completed
Pull Request — 2.x (#36)
by
unknown
05:57
created

PhpRedisConnection::transaction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Monospice\LaravelRedisSentinel\Connections;
4
5
use Closure;
6
use Illuminate\Redis\Connections\PhpRedisConnection as LaravelPhpRedisConnection;
7
use Redis;
8
use RedisException;
9
10
/**
11
 * Executes Redis commands using the PhpRedis client.
12
 *
13
 * This package extends Laravel's PhpRedisConnection class to wrap all command
14
 * methods with a retryOnFailure method.
15
 *
16
 * @category Package
17
 * @package  Monospice\LaravelRedisSentinel
18
 * @author   Jeffrey Zant <[email protected]>
19
 * @license  See LICENSE file
20
 * @link     https://github.com/monospice/laravel-redis-sentinel-drivers
21
 */
22
class PhpRedisConnection extends LaravelPhpRedisConnection
23
{
24
    /**
25
     * The number of times the client attempts to retry a command when it fails
26
     * to connect to a Redis instance behind Sentinel.
27
     *
28
     * @var int
29
     */
30
    protected $retryLimit = 20;
31
32
    /**
33
     * The time in milliseconds to wait before the client retries a failed
34
     * command.
35
     *
36
     * @var int
37
     */
38
    protected $retryWait = 1000;
39
40
    /**
41
     * Create a new PhpRedis connection.
42
     *
43
     * @param  \Redis  $client
44
     * @param  callable|null  $connector
45
     * @param  array  $sentinelOptions
46
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
47
     */
48
    public function __construct($client, callable $connector = null, array $sentinelOptions = [])
49
    {
50
        parent::__construct($client, $connector);
51
52
        $this->retryLimit = (int) ($sentinelOptions['retry_limit'] ?? 20);
53
        $this->retryWait = (int) ($sentinelOptions['retry_wait'] ?? 1000);
54
    }
55
56
    /**
57
     * {@inheritdoc} in addition retry on client failure.
58
     *
59
     * @param  mixed  $cursor
60
     * @param  array  $options
61
     * @return mixed
62
     */
63
    public function scan($cursor, $options = [])
64
    {
65
        return $this->retryOnFailure(function () use ($cursor, $options) {
66
            return parent::scan($cursor, $options);
67
        });
68
    }
69
70
    /**
71
     * {@inheritdoc} in addition retry on client failure.
72
     *
73
     * @param  string  $key
74
     * @param  mixed  $cursor
75
     * @param  array  $options
76
     * @return mixed
77
     */
78
    public function zscan($key, $cursor, $options = [])
79
    {
80
        return $this->retryOnFailure(function () use ($key, $cursor, $options) {
81
            parent::zscan($key, $cursor, $options);
82
        });
83
    }
84
85
    /**
86
     * {@inheritdoc} in addition retry on client failure.
87
     *
88
     * @param  string  $key
89
     * @param  mixed  $cursor
90
     * @param  array  $options
91
     * @return mixed
92
     */
93
    public function hscan($key, $cursor, $options = [])
94
    {
95
        return $this->retryOnFailure(function () use ($key, $cursor, $options) {
96
            parent::hscan($key, $cursor, $options);
97
        });
98
    }
99
100
    /**
101
     * {@inheritdoc} in addition retry on client failure.
102
     *
103
     * @param  string  $key
104
     * @param  mixed  $cursor
105
     * @param  array  $options
106
     * @return mixed
107
     */
108
    public function sscan($key, $cursor, $options = [])
109
    {
110
        return $this->retryOnFailure(function () use ($key, $cursor, $options) {
111
            parent::sscan($key, $cursor, $options);
112
        });
113
    }
114
115
    /**
116
     * {@inheritdoc} in addition retry on client failure.
117
     *
118
     * @param  callable|null  $callback
119
     * @return \Redis|array
120
     */
121
    public function pipeline(callable $callback = null)
122
    {
123
        return $this->retryOnFailure(function () use ($callback) {
124
            return parent::pipeline($callback);
125
        });
126
    }
127
128
    /**
129
     * {@inheritdoc} in addition retry on client failure.
130
     *
131
     * @param  callable|null  $callback
132
     * @return \Redis|array
133
     */
134
    public function transaction(callable $callback = null)
135
    {
136
        return $this->retryOnFailure(function () use ($callback) {
137
            return parent::transaction($callback);
138
        });
139
    }
140
141
    /**
142
     * {@inheritdoc} in addition retry on client failure.
143
     *
144
     * @param  array|string  $channels
145
     * @param  \Closure  $callback
146
     * @return void
147
     */
148
    public function subscribe($channels, Closure $callback)
149
    {
150
        return $this->retryOnFailure(function () use ($channels, $callback) {
151
            return parent::subscribe($channels, $callback);
152
        });
153
    }
154
155
    /**
156
     * {@inheritdoc} in addition retry on client failure.
157
     *
158
     * @param  array|string  $channels
159
     * @param  \Closure  $callback
160
     * @return void
161
     */
162
    public function psubscribe($channels, Closure $callback)
163
    {
164
        return $this->retryOnFailure(function () use ($channels, $callback) {
165
            return parent::psubscribe($channels, $callback);
166
        });
167
    }
168
169
    /**
170
     * {@inheritdoc} in addition retry on client failure.
171
     *
172
     * @param  string  $method
173
     * @param  array  $parameters
174
     * @return mixed
175
     */
176
    public function command($method, array $parameters = [])
177
    {
178
        return $this->retryOnFailure(function () use ($method, $parameters) {
179
            return parent::command($method, $parameters);
180
        });
181
    }
182
183
    /**
184
     * Attempt to retry the provided operation when the client fails to connect
185
     * to a Redis server.
186
     *
187
     * @param callable $callback The operation to execute.
188
     * @return mixed The result of the first successful attempt.
189
     *
190
     * @throws RedisException After exhausting the allowed number of
191
     * attempts to reconnect.
192
     */
193
    protected function retryOnFailure(callable $callback)
194
    {
195
        $attempts = 0;
196
197
        do {
198
            try {
199
                return $callback();
200
            } catch (RedisException $exception) {
201
                $this->client->close();
202
203
                usleep($this->retryWait * 1000);
204
205
                try {
206
                    $this->client = $this->connector();
0 ignored issues
show
Documentation Bug introduced by
The method connector does not exist on object<Monospice\Laravel...ons\PhpRedisConnection>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
207
                } catch (RedisException $e) {
208
                    // Ignore the the creation of a new client gets an exception.
209
                    // If this exception isn't caught the retry will stop.
210
                }
211
212
                $attempts++;
213
            }
214
        } while ($attempts <= $this->retryLimit);
215
216
        throw $exception;
217
    }
218
}
219