Passed
Pull Request — main (#136)
by Andreas
11:46
created

ServerPool::execute()   B

Complexity

Conditions 6
Paths 11

Size

Total Lines 51
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 6.0009

Importance

Changes 0
Metric Value
cc 6
eloc 31
c 0
b 0
f 0
nc 11
nop 2
dl 0
loc 51
ccs 33
cts 34
cp 0.9706
crap 6.0009
rs 8.8017

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Licensed to CRATE Technology GmbH("Crate") under one or more contributor
4
 * license agreements.  See the NOTICE file distributed with this work for
5
 * additional information regarding copyright ownership.  Crate licenses
6
 * this file to you under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.  You may
8
 * obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
15
 * License for the specific language governing permissions and limitations
16
 * under the License.
17
 *
18
 * However, if you have executed another commercial license agreement
19
 * with Crate these terms will supersede the license and you may use the
20
 * software solely pursuant to the terms of the relevant commercial agreement.
21
 */
22
23
declare(strict_types=1);
24
25
namespace Crate\PDO\Http;
26
27
use Crate\PDO\Exception\RuntimeException;
28
use Crate\PDO\PDO;
29
use Crate\PDO\PDOInterface;
30
use Crate\Stdlib\Collection;
31
use Crate\Stdlib\CollectionInterface;
32
use GuzzleHttp\Client;
33
use GuzzleHttp\ClientInterface;
34
use GuzzleHttp\Exception\BadResponseException;
35
use GuzzleHttp\Exception\ConnectException;
36
use GuzzleHttp\RequestOptions;
37
38
/**
39
 * Class ServerPool
40
 *
41
 * Very basic round robin implementation
42
 */
43
final class ServerPool implements ServerInterface
44
{
45
    private const DEFAULT_SERVER = 'localhost:4200';
46
47
    /**
48
     * @var string
49
     */
50
    private $protocol = 'http';
51
52
    /**
53
     * @var array
54
     */
55
    private $httpOptions = [];
56
57
    /**
58
     * @var string[]
59
     */
60
    private $availableServers = [];
61
62
    /**
63
     * @var Client
64
     */
65
    private $httpClient;
66
67
    /**
68
     * Client constructor.
69
     *
70
     * @param array                $servers
71
     * @param ClientInterface|null $client
72
     */
73 26
    public function __construct(array $servers, ClientInterface $client = null)
74
    {
75 26
        if (\count($servers) === 0) {
76
            $servers = [self::DEFAULT_SERVER];
77
        }
78
79
        // micro optimization so we don't always hit the same server first
80 26
        shuffle($servers);
81
82 26
        foreach ($servers as $server) {
83 26
            $this->availableServers[] = $server;
84
        }
85
86 26
        $this->httpClient = $client ?: new Client();
87
    }
88
89
    /**
90
     * {@Inheritdoc}
91
     * @throws \GuzzleHttp\Exception\ConnectException
92
     */
93 26
    public function execute(string $query, array $parameters): CollectionInterface
94
    {
95 26
        $numServers = count($this->availableServers) - 1;
96
97 26
        for ($i = 0; $i <= $numServers; $i++) {
98
            // always get the first available server
99 26
            $server = $this->availableServers[0];
100
101
            // Move the selected server to the end of the stack
102 26
            $this->availableServers[] = array_shift($this->availableServers);
103
104 26
            $options = array_merge($this->httpOptions, [
105 26
                'base_uri' => sprintf('%s://%s', $this->protocol, $server),
106 26
                'json'     => [
107 26
                    'stmt' => $query,
108 26
                    'args' => $parameters,
109 26
                ],
110 26
            ]);
111
112
            try {
113 26
                $response     = $this->httpClient->request('POST', '/_sql', $options);
114 22
                $responseBody = json_decode((string)$response->getBody(), true);
115
116 22
                return new Collection(
117 22
                    $responseBody['rows'],
118 22
                    $responseBody['cols'],
119 22
                    $responseBody['duration'],
120 22
                    $responseBody['rowcount']
121 22
                );
122 6
            } catch (ConnectException $exception) {
123
                // Catch it before the BadResponseException but do nothing.
124 4
                continue;
125 2
            } catch (BadResponseException $exception) {
126 2
                $body = (string)$exception->getResponse()->getBody();
127 2
                $json = json_decode($body, true);
128
129 2
                if ($json === null && json_last_error() !== JSON_ERROR_NONE) {
130
                    throw new RuntimeException(sprintf('Server returned non-JSON response: %s', $body), 0, $exception);
131
                }
132
133 2
                $errorCode    = $json['error']['code'];
134 2
                $errorMessage = $json['error']['message'];
135
136 2
                throw new RuntimeException($errorMessage, $errorCode, $exception);
137
            }
138
        }
139
140 2
        throw new ConnectException(
141 2
            sprintf('No more servers available, exception from last server: %s', $exception->getMessage()),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $exception does not seem to be defined for all execution paths leading up to this point.
Loading history...
142 2
            $exception->getRequest(),
143 2
            $exception
144 2
        );
145
    }
146
147
    /**
148
     * {@Inheritdoc}
149
     */
150
    public function getServerInfo(): array
151
    {
152
        return [
153
            'serverVersion' => $this->getServerVersion(),
154
        ];
155
    }
156
157
    /**
158
     * {@Inheritdoc}
159
     */
160
    public function getServerVersion(): string
161
    {
162
        $result = $this->execute("select version['number'] from sys.nodes limit 1", []);
163
164
        if (count($result->getRows()) !== 1) {
165
            throw new RuntimeException('Failed to determine server version');
166
        }
167
168
        return $result->getRows()[0][0];
169
    }
170
171
    /**
172
     * Reconfigure the the server pool based on the attributes in PDO
173
     *
174
     * @param PDOInterface $pdo
175
     */
176 26
    public function configure(PDOInterface $pdo): void
177
    {
178 26
        $sslMode = $pdo->getAttribute(PDO::CRATE_ATTR_SSL_MODE);
179
180 26
        $protocol = $sslMode === PDO::CRATE_ATTR_SSL_MODE_DISABLED ? 'http' : 'https';
181
182 26
        $options = [
183 26
            RequestOptions::TIMEOUT         => $pdo->getAttribute(PDO::ATTR_TIMEOUT),
184 26
            RequestOptions::CONNECT_TIMEOUT => $pdo->getAttribute(PDO::ATTR_TIMEOUT),
185 26
            RequestOptions::AUTH            => $pdo->getAttribute(PDO::CRATE_ATTR_HTTP_BASIC_AUTH) ?: null,
186 26
            RequestOptions::HEADERS         => [
187 26
                'Default-Schema' => $pdo->getAttribute(PDO::CRATE_ATTR_DEFAULT_SCHEMA),
188 26
            ],
189 26
        ];
190
191 26
        if ($sslMode === PDO::CRATE_ATTR_SSL_MODE_ENABLED_BUT_WITHOUT_HOST_VERIFICATION) {
192 4
            $options['verify'] = false;
193
        }
194
195 26
        $ca         = $pdo->getAttribute(PDO::CRATE_ATTR_SSL_CA_PATH);
196 26
        $caPassword = $pdo->getAttribute(PDO::CRATE_ATTR_SSL_CA_PASSWORD);
197
198 26
        if ($ca) {
199 4
            if ($caPassword) {
200 2
                $options[RequestOptions::VERIFY] = [$ca, $caPassword];
201
            } else {
202 4
                $options[RequestOptions::VERIFY] = $ca;
203
            }
204
        }
205
206 26
        $cert         = $pdo->getAttribute(PDO::CRATE_ATTR_SSL_CERT_PATH);
207 26
        $certPassword = $pdo->getAttribute(PDO::CRATE_ATTR_SSL_CERT_PASSWORD);
208
209 26
        if ($cert) {
210 4
            if ($certPassword) {
211 2
                $options[RequestOptions::CERT] = [$cert, $certPassword];
212
            } else {
213 4
                $options[RequestOptions::CERT] = $cert;
214
            }
215
        }
216
217 26
        $key         = $pdo->getAttribute(PDO::CRATE_ATTR_SSL_KEY_PATH);
218 26
        $keyPassword = $pdo->getAttribute(PDO::CRATE_ATTR_SSL_KEY_PASSWORD);
219
220 26
        if ($key) {
221 4
            if ($keyPassword) {
222 2
                $options[RequestOptions::SSL_KEY] = [$key, $keyPassword];
223
            } else {
224 4
                $options[RequestOptions::SSL_KEY] = $key;
225
            }
226
        }
227
228 26
        $this->protocol    = $protocol;
229 26
        $this->httpOptions = $options;
230
    }
231
}
232