Completed
Pull Request — 2.x (#36)
by
unknown
01:44
created

PhpRedisConnector::connect()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 9.504
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
namespace Monospice\LaravelRedisSentinel\Connectors;
4
5
use Illuminate\Support\Arr;
6
use Illuminate\Redis\Connectors\PhpRedisConnector as LaravelPhpRedisConnector;
7
use Monospice\LaravelRedisSentinel\Connections\PhpRedisConnection;
8
use Redis;
9
use RedisSentinel;
10
use RedisException;
11
12
/**
13
 * Initializes PhpRedis Client instances for Redis Sentinel connections
14
 *
15
 * @category Package
16
 * @package  Monospice\LaravelRedisSentinel
17
 * @author   Jeffrey Zant <[email protected]>
18
 * @license  See LICENSE file
19
 * @link     http://github.com/monospice/laravel-redis-sentinel-drivers
20
 */
21
class PhpRedisConnector extends LaravelPhpRedisConnector
22
{
23
    /**
24
     * Holds the current sentinel servers.
25
     *
26
     * @var array
27
     */
28
    protected $servers;
29
30
    /**
31
     * Configuration options specific to Sentinel connection operation
32
     *
33
     * Some of the Sentinel configuration options can be entered in this class.
34
     * The retry_wait and retry_limit values are passed to the connection.
35
     *
36
     * @var array
37
     */
38
    protected $sentinelKeys = [
39
        'sentinel_timeout' => null,
40
        'retry_wait' => null,
41
        'retry_limit' => null,
42
        'update_sentinels' => null,
43
44
        'sentinel_persistent' => null,
45
        'sentinel_read_timeout' => null,
46
    ];
47
48
    /**
49
     * Create a new Redis Sentinel connection from the provided configuration
50
     * options
51
     *
52
     * @param array $server  The client configuration for the connection
0 ignored issues
show
Documentation introduced by
There is no parameter named $server. Did you maybe mean $servers?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
53
     * @param array $options The global client options shared by all Sentinel
54
     * connections
55
     *
56
     * @return PhpRedisConnection The Sentinel connection containing a configured
57
     * PhpRedis Client
58
     */
59
    public function connect(array $servers, array $options = [ ])
60
    {
61
        // Set the initial Sentinel servers.
62
        $this->servers = array_map(function ($server) {
63
            return $this->formatServer($server);
64
        }, $servers);
65
66
        // Merge the global options shared by all Sentinel connections with
67
        // connection-specific options
68
        $clientOpts = array_merge($options, Arr::pull($servers, 'options', [ ]));
69
70
        // Extract the array of Sentinel connection options from the rest of
71
        // the client options
72
        $sentinelOpts = array_intersect_key($clientOpts, $this->sentinelKeys);
73
74
        // Filter the Sentinel connection options elements from the client
75
        // options array
76
        $clientOpts = array_diff_key($clientOpts, $this->sentinelKeys);
0 ignored issues
show
Unused Code introduced by
$clientOpts is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
77
78
        // Create a client by calling the Sentinel servers
79
        $connector = function () use ($options) {
80
            return $this->createClientWithSentinel($options);
81
        };
82
83
        return new PhpRedisConnection($connector(), $connector, $sentinelOpts);
84
    }
85
86
    /**
87
     * Create the Redis client instance
88
     *
89
     * @param  array  $options
90
     * @return Redis
0 ignored issues
show
Documentation introduced by
Should the return type not be Redis|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
91
     */
92
    protected function createClientWithSentinel(array $options)
93
    {
94
        $servers = $this->servers;
95
        $timeout = isset($options['sentinel_timeout']) ? $options['sentinel_timeout'] : 0;
96
        $persistent = isset($options['sentinel_peristent']) ? $options['sentinel_peristent'] : null;
97
        $retryWait = isset($options['retry_wait']) ? $options['retry_wait'] : 0;
98
        $readTimeout = isset($options['sentinel_read_timeout']) ? $options['sentinel_read_timeout'] : 0;
99
        $parameters = isset($options['parameters']) ? $options['parameters'] : [];
100
101
        // Shuffle the servers to perform some loadbalancing.
102
        shuffle($servers);
103
104
        // Try to connect to any of the servers.
105
        foreach ($servers as $idx => $server) {
106
            $host = isset($server['host']) ? $server['host'] : 'localhost';
107
            $port = isset($server['port']) ? $server['port'] : 26739;
108
            $service = isset($options['service']) ? $options['service'] : 'mymaster';
109
110
            // Create a connection to the Sentinel instance.
111
            $sentinel = new RedisSentinel($host, $port, $timeout, $persistent, $retryWait, $readTimeout);
112
113
            try {
114
                // Check if the Sentinel server list needs to be updated.
115
                // Put the current server and the other sentinels in the server list.
116
                $updateSentinels = isset($options['update_sentinels']) ? $options['update_sentinels'] : false;
117
                if ($updateSentinels === true) {
118
                    $this->updateSentinels($sentinel, $host, $port, $service);
119
                }
120
121
                // Lookup the master node.
122
                $master = $sentinel->getMasterAddrByName($service);
123
                if (is_array($master) && ! count($master)) {
124
                    throw new RedisException(sprintf('No master found for service "%s".', $service));
125
                }
126
127
                // Create a PhpRedis client for the selected master node.
128
                return $this->createClient(
129
                    array_merge($parameters, $server, ['host' => $master[0], 'port' => $master[1]])
130
                );
131
            } catch (RedisException $e) {
132
                // Rethrow the expection when the last server is reached.
133
                if ($idx === count($servers) - 1) {
134
                    throw $e;
135
                }
136
            }
137
        }
138
    }
139
140
    /**
141
     * Update the list With sentinel servers.
142
     *
143
     * @param RedisSentinel $sentinel
144
     * @param string $currentHost
145
     * @param int $currentPort
146
     * @param string $service
147
     * @return void
148
     */
149
    private function updateSentinels(RedisSentinel $sentinel, string $currentHost, int $currentPort, string $service)
150
    {
151
        $this->servers = array_merge(
152
            [
153
                [
154
                    'host' => $currentHost,
155
                    'port' => $currentPort,
156
                ]
157
            ], array_map(function ($sentinel) {
158
                return [
159
                    'host' => $sentinel['ip'],
160
                    'port' => $sentinel['port'],
161
                ];
162
            }, $sentinel->sentinels($service))
163
        );
164
    }
165
166
    /**
167
     * Format a server.
168
     *
169
     * @param mixed $server
170
     * @return array
171
     *
172
     * @throws RedisException
173
     */
174
    private function formatServer($server)
175
    {
176
        if (is_string($server)) {
177
            list($host, $port) = explode(':', $server);
178
            if (! $host || ! $port) {
179
                throw new RedisException('Could not format the server definition.');
180
            }
181
182
            return ['host' => $host, 'port' => (int) $port];
183
        }
184
185
        if (! is_array($server)) {
186
            throw new RedisException('Could not format the server definition.');
187
        }
188
189
        return $server;
190
    }
191
}
192