Completed
Pull Request — master (#34)
by Christian
16:03 queued 12:30
created

Client::nextServer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
ccs 0
cts 4
cp 0
rs 9.4285
cc 1
eloc 4
nc 1
nop 0
crap 2
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
namespace Crate\PDO\Http;
24
25
use Crate\PDO\Exception\RuntimeException;
26
use Crate\PDO\Exception\UnsupportedException;
27
use Crate\Stdlib\Collection;
28
use GuzzleHttp\Exception\BadResponseException;
29
use GuzzleHttp\Exception\ConnectException;
30
use GuzzleHttp\Exception\ParseException;
31
32
33
class Client implements ClientInterface
34
{
35
36
    const DEFAULT_SERVER = "localhost:4200";
37
38
    /**
39
     * @var array
40
     */
41
    private $availableServers = [];
42
43
    /**
44
     * @var array
45
     */
46
    private $serverPool = [];
47
48
    /**
49
     * Client constructor.
50
     * @param array $servers
51
     * @param array $options
52
     */
53
    public function __construct(array $servers, array $options)
54
    {
55
        if ($servers == null || count($servers) == 0) {
56
            $this->serverPool[self::DEFAULT_SERVER] = new Server(self::DEFAULT_SERVER, $options);
57
            $this->availableServers[] = self::DEFAULT_SERVER;
58
        } else {
59
            foreach ($servers as &$server) {
60
                $this->serverPool[$server] = new Server($server, $options);
61
                $this->availableServers[] = $server;
62
            }
63
        }
64
    }
65
66
    /**
67
     * {@Inheritdoc}
68
     */
69
    public function execute($query, array $parameters)
70
    {
71
        $body = [
72
            'stmt' => $query,
73
            'args' => $parameters
74
        ];
75
76
        while (true) {
77
            $nextServer = $this->nextServer();
78
            /**
79
             * @var Server $s
80
             */
81
            $s = $this->serverPool[$nextServer];
82
83
            try {
84
85
                $response = $s->doRequest($body);
86
                $responseBody = $response->json();
87
88
                return new Collection(
89
                    $responseBody['rows'],
90
                    $responseBody['cols'],
91
                    $responseBody['duration'],
92
                    $responseBody['rowcount']
93
                );
94
95
            } catch (ConnectException $exception) {
96
                // drop the server from the list of available servers
97
                $this->dropServer($nextServer);
98
                // break the loop if no more servers are available
99
                $this->raiseIfNoMoreServers($exception);
100
            } catch (BadResponseException $exception) {
101
102
                try {
103
104
                    $json = $exception->getResponse()->json();
105
106
                    $errorCode    = $json['error']['code'];
107
                    $errorMessage = $json['error']['message'];
108
109
                    throw new RuntimeException($errorMessage, $errorCode, $exception);
110
111
                } catch (ParseException $e) {
112
                    throw new RuntimeException('Server returned non-JSON response.', 0, $exception);
113
                }
114
115
            }
116
        }
117
        return null;
118
    }
119
120
    /**
121
     * {@Inheritdoc}
122
     */
123
    public function getServerInfo()
124
    {
125
        throw new UnsupportedException('Not yet implemented');
126
    }
127
128
    /**
129
     * {@Inheritdoc}
130
     */
131
    public function getServerVersion()
132
    {
133
        throw new UnsupportedException('Not yet implemented');
134
    }
135
136
    /**
137
     * {@Inheritdoc}
138
     */
139
    public function setTimeout($timeout)
140
    {
141
        foreach ($this->serverPool as $k => &$s) {
142
            /**
143
             * @var $s Server
144
             */
145
            $s->setTimeout($timeout);
146
        }
147
    }
148
149
    /**
150
     * {@Inheritdoc}
151
     */
152
    public function setHttpBasicAuth($username, $passwd)
153
    {
154
        foreach ($this->serverPool as $k => &$s) {
155
            /**
156
             * @var $s Server
157
             */
158
            $s->setHttpBasicAuth($username, $passwd);
159
        }
160
    }
161
162
    /**
163
     * {@Inheritdoc}
164
     */
165
    public function setHttpHeader($name, $value)
166
    {
167
        foreach ($this->serverPool as $k => &$s) {
168
            /**
169
             * @var $s Server
170
             */
171
            $s->setHttpHeader($name, $value);
172
        }
173
    }
174
175
    /**
176
     * @return string The next available server instance
177
     */
178
    private function nextServer()
179
    {
180
        $server = $this->availableServers[0];
181
        $this->roundRobin();
182
        return $server;
183
    }
184
185
    /**
186
     * Very simple round-robin implementation
187
     * Pops the first item of the availableServers array and appends it at the end.
188
     *
189
     * @return void
190
     */
191
    private function roundRobin()
192
    {
193
        /**
194
         * Performing round robin on the array only makes sense
195
         * if there are more than 1 available servers.
196
         */
197
        if (count($this->availableServers) > 1) {
198
            $this->availableServers[] = array_shift($this->availableServers);
199
        }
200
    }
201
202
    /**
203
     * @param string           $server
204
     */
205
    private function dropServer($server)
206
    {
207
        if (($idx = array_search($server, $this->availableServers)) !== false) {
208
            unset($this->availableServers[$idx]);
209
        }
210
    }
211
212
    /**
213
     * @param ConnectException $exception
214
     */
215
    private function raiseIfNoMoreServers($exception)
216
    {
217
        if (count($this->availableServers) == 0) {
218
            throw new ConnectException("No more servers available, exception from last server: " . $exception->getMessage(),
219
                $exception->getRequest(), $exception->getResponse(), $exception);
220
        }
221
    }
222
223
}
224