Passed
Push — next ( d904ac...c0af08 )
by Bas
13:43
created

ArangoClient::prepare()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 3
dl 0
loc 6
ccs 2
cts 2
cp 1
crap 1
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ArangoClient;
6
7
use ArangoClient\Exceptions\ArangoException;
8
use ArangoClient\Http\HttpClientConfig;
9
use ArangoClient\Http\HttpRequestOptions;
10
use ArangoClient\Statement\Statement;
11
use ArangoClient\Transactions\SupportsTransactions;
12
use GuzzleHttp\Client as GuzzleClient;
13
use GuzzleHttp\Exception\GuzzleException;
14
use GuzzleHttp\Exception\RequestException;
15
use GuzzleHttp\Psr7\StreamWrapper;
16
use JsonMachine\JsonMachine;
17
use Psr\Http\Message\ResponseInterface;
18
use Throwable;
19
use Traversable;
20
21
/**
22
 * The arangoClient handles connections to ArangoDB's HTTP REST API.
23
 *
24
 * @see https://www.arangodb.com/docs/stable/http/
25
 */
26
class ArangoClient
27
{
28
    use HasManagers;
29
    use SupportsTransactions;
30
31
    protected GuzzleClient $httpClient;
32
33
    protected HttpClientConfig $config;
34
35
    /**
36
     * ArangoClient constructor.
37
     *
38
     * @param  array<string|numeric|null>  $config
39
     * @param  GuzzleClient|null  $httpClient
40
     */
41 91
    public function __construct(array $config = [], GuzzleClient $httpClient = null)
42
    {
43 91
        $config['endpoint'] = $this->generateEndpoint($config);
44 91
        $this->config = new HttpClientConfig($config);
45
46 91
        $this->httpClient = isset($httpClient)
47
            ? $httpClient
48 91
            : new GuzzleClient($this->config->mapGuzzleHttpClientConfig());
49 91
    }
50
51
    /**
52
     * @param  array<mixed>  $config
53
     * @return string
54
     */
55 91
    public function generateEndpoint(array $config): string
56
    {
57 91
        if (isset($config['endpoint'])) {
58
            return (string) $config['endpoint'];
59
        }
60 91
        $endpoint = 'http://localhost:8529';
61 91
        if (isset($config['host'])) {
62 2
            $endpoint = (string) $config['host'];
63
        }
64 91
        if (isset($config['port'])) {
65 2
            $endpoint .= ':' . (string) $config['port'];
66
        }
67
68 91
        return $endpoint;
69
    }
70
71
    /**
72
     * @psalm-suppress MixedReturnStatement
73
     *
74
     * @param  string  $method
75
     * @param  string  $uri
76
     * @param  array<mixed>|HttpRequestOptions  $options
77
     * @param  string|null  $database
78
     * @return array<mixed>
79
     * @throws ArangoException
80
     */
81 91
    public function request(string $method, string $uri, $options = [], ?string $database = null): array
82
    {
83 91
        $uri = $this->prependDatabaseToUri($uri, $database);
84
85 91
        if (is_array($options)) {
86 91
            $options = $this->prepareRequestOptions($options);
87
        }
88
89 91
        $response = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
90
        try {
91 91
            $response = $this->httpClient->request($method, $uri, $options->all());
92 1
        } catch (Throwable $e) {
93 1
            $this->handleGuzzleException($e);
94
        }
95
96 91
        return $this->cleanupResponse($response);
97
    }
98
99
    /**
100
     * @param array<mixed> $options
101
     * @return HttpRequestOptions
102
     * @throws ArangoException
103
     */
104 91
    protected function prepareRequestOptions(array $options): HttpRequestOptions
105
    {
106 91
        if (isset($options['body'])) {
107 55
            $options['body'] = $this->jsonEncode($options['body']);
108
        }
109
110 91
        return new HttpRequestOptions($options);
111
    }
112
113
    /**
114
     * Return the response with debug information (for internal testing purposes).
115
     *
116
     * @param  string  $method
117
     * @param  string  $uri
118
     * @param  array<mixed>  $options
119
     * @param  string|null  $database
120
     * @return ResponseInterface
121
     * @throws GuzzleException
122
     */
123 2
    public function debugRequest(
124
        string $method,
125
        string $uri,
126
        array $options = [],
127
        ?string $database = null
128
    ): ResponseInterface {
129 2
        $uri = $this->prependDatabaseToUri($uri, $database);
130 2
        $options['debug'] = true;
131
132 2
        return $this->httpClient->request($method, $uri, $options);
133
    }
134
135 91
    protected function prependDatabaseToUri(string $uri, ?string $database = null): string
136
    {
137 91
        if (! isset($database)) {
138 91
            $database = $this->config->database;
139
        }
140 91
        return '/_db/' . urlencode($database) . $uri;
141
    }
142
143
    /**
144
     * @param  Throwable  $e
145
     * @throws ArangoException
146
     */
147 1
    protected function handleGuzzleException(Throwable $e): void
148
    {
149 1
        $message = $e->getMessage();
150 1
        $code = $e->getCode();
151
152 1
        if ($e instanceof RequestException && $e->hasResponse()) {
153 1
            $decodedResponse = $this->decodeResponse($e->getResponse());
154 1
            $message = (string) $decodedResponse['errorMessage'];
155 1
            $code = (int) $decodedResponse['code'];
156
        }
157
158
        throw(
159 1
        new ArangoException(
160 1
            $message,
161 1
            (int) $code
162
        )
163
        );
164
    }
165
166
    /**
167
     * @psalm-suppress MixedAssignment, MixedArrayOffset
168
     * @SuppressWarnings(PHPMD.StaticAccess)
169
     *
170
     * @param  ResponseInterface|null  $response
171
     * @return array<mixed>
172
     */
173 91
    protected function cleanupResponse(?ResponseInterface $response): array
174
    {
175 91
        $response =  $this->decodeResponse($response);
176 91
        unset($response['error']);
177 91
        unset($response['code']);
178
179 91
        return $response;
180
    }
181
182
    /**
183
     * @psalm-suppress MixedAssignment, MixedArrayOffset
184
     * @SuppressWarnings(PHPMD.StaticAccess)
185
     *
186
     * @param  ResponseInterface|null  $response
187
     * @return array<mixed>
188
     */
189 91
    protected function decodeResponse(?ResponseInterface $response): array
190
    {
191 91
        if (! isset($response)) {
192
            return [];
193
        }
194
195 91
        $decodedResponse = [];
196
197 91
        $phpStream = StreamWrapper::getResource($response->getBody());
198 91
        $decodedStream = JsonMachine::fromStream($phpStream);
199
200 91
        foreach ($decodedStream as $key => $value) {
201 91
            $decodedResponse[$key] = $value;
202
        }
203
204 91
        return $decodedResponse;
205
    }
206
207
    /**
208
     * @param  mixed  $data
209
     * @return string
210
     * @throws ArangoException
211
     */
212 59
    public function jsonEncode($data): string
213
    {
214 59
        $options = 0;
215 59
        if (empty($data)) {
216 3
            $options = JSON_FORCE_OBJECT;
217
        }
218
219 59
        $response = json_encode($data, $options);
220
221 59
        if ($response === false) {
222 1
            throw new ArangoException('JSON encoding failed with error: ' . json_last_error_msg(), json_last_error());
223
        }
224
225 58
        return $response;
226
    }
227
228
    /**
229
     * @param  string  $query
230
     * @param  array<scalar>  $bindVars
231
     * @param  array<mixed>  $options
232
     * @return Traversable<mixed>
233
     */
234 16
    public function prepare(
235
        string $query,
236
        array $bindVars = [],
237
        array $options = []
238
    ): Traversable {
239 16
        return new Statement($this, $query, $bindVars, $options);
240
    }
241
242
    /**
243
     * @return array<array-key, mixed>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
244
     */
245 4
    public function getConfig(): array
246
    {
247 4
        return $this->config->toArray();
248
    }
249
250
    /**
251
     * @param string $name
252
     * @return void
253
     */
254 91
    public function setDatabase(string $name): void
255
    {
256 91
        $this->config->database = $name;
257 91
    }
258
259
    /**
260
     * @return string
261
     */
262 1
    public function getDatabase(): string
263
    {
264 1
        return $this->config->database;
265
    }
266
267 1
    public function setHttpClient(GuzzleClient $httpClient): void
268
    {
269 1
        $this->httpClient = $httpClient;
270 1
    }
271
272 1
    public function getHttpClient(): GuzzleClient
273
    {
274 1
        return $this->httpClient;
275
    }
276
277 91
    public function getUser(): string
278
    {
279 91
        return (string) $this->config->username;
280
    }
281
}
282