Failed Conditions
Push — 42-add-disconnect ( 3c6c37...b8a885 )
by Bas
02:15
created

ArangoClient::getConfig()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 6
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 Psr\Http\Message\ResponseInterface;
16
use Spatie\DataTransferObject\Exceptions\UnknownProperties;
17
use stdClass;
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 HandlesResponses;
29
    use HandlesJson;
30
    use HasManagers;
31
    use SupportsTransactions;
32
33
    protected GuzzleClient $httpClient;
34
35
    protected HttpClientConfig $config;
36
37
    /**
38
     * ArangoClient constructor.
39
     *
40
     * @param  array<string|numeric|null>  $config
41
     * @param  GuzzleClient|null  $httpClient
42
     *
43
     * @throws UnknownProperties
44
     */
45
    public function __construct(array $config = [], ?GuzzleClient $httpClient = null)
46
    {
47
        $this->connect($config, $httpClient);
48
    }
49
50
    /**
51
     * ArangoClient constructor.
52
     *
53
     * @param  array<string|numeric|null>  $config
54
     * @param  GuzzleClient|null  $httpClient
55
     *
56
     * @throws UnknownProperties
57
     */
58
    public function connect(array $config = [], ?GuzzleClient $httpClient = null): void
59
    {
60
        $config['endpoint'] = $this->generateEndpoint($config);
61
        $this->config = new HttpClientConfig($config);
62
63
        $this->httpClient = $httpClient ?? new GuzzleClient($this->config->mapGuzzleHttpClientConfig());
64
    }
65
66
    /**
67
     * We disconnect by creating a new guzzle client. The old client will remove the current connection upon destruction.
68
     *
69
     * @return bool
70
     */
71
    public function disconnect(): bool
72
    {
73
        $this->httpClient = new GuzzleClient($this->config->mapGuzzleHttpClientConfig());
74
75
        return true;
76
    }
77
78
79
    /**
80
     * @param  array<mixed>  $config
81
     */
82
    public function generateEndpoint(array $config): string
83
    {
84
        if (isset($config['endpoint'])) {
85
            return (string) $config['endpoint'];
86
        }
87
88
        $endpoint = 'http://localhost:8529';
89
        if (isset($config['host'])) {
90
            $endpoint = (string) $config['host'];
91
        }
92
93
        if (isset($config['port'])) {
94
            $endpoint .= ':' . (string) $config['port'];
95
        }
96
97
        return $endpoint;
98
    }
99
100
    /**
101
     * @param  array<mixed>|HttpRequestOptions  $options
102
     *
103
     * @throws ArangoException
104
     */
105
    public function request(string $method, string $uri, array|HttpRequestOptions $options = [], ?string $database = null): stdClass
106
    {
107
        $uri = $this->prependDatabaseToUri($uri, $database);
108
109
        if (is_array($options)) {
0 ignored issues
show
introduced by
The condition is_array($options) is always true.
Loading history...
110
            $options = $this->prepareRequestOptions($options);
111
        }
112
113
        $response = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
114
        try {
115
            $response = $this->httpClient->request($method, $uri, $options->all());
116
        } catch (Throwable $e) {
117
            $this->handleGuzzleException($e);
118
        }
119
120
        if ($response !== null) {
121
            return $this->cleanupResponse($response);
122
        }
123
124
        return new stdClass();
125
    }
126
127
    /**
128
     * @param  array<mixed>|HttpRequestOptions  $options
129
     *
130
     * @throws ArangoException
131
     */
132
    public function rawRequest(string $method, string $uri, array|HttpRequestOptions $options = []): ResponseInterface|null
133
    {
134
        if (is_array($options)) {
0 ignored issues
show
introduced by
The condition is_array($options) is always true.
Loading history...
135
            $options = $this->prepareRequestOptions($options);
136
        }
137
138
        $response = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
139
        try {
140
            $response = $this->httpClient->request($method, $uri, $options->all());
141
        } catch (Throwable $e) {
142
            $this->handleGuzzleException($e);
143
        }
144
145
        return $response;
146
    }
147
148
149
    /**
150
     * @param  array<mixed>  $options
151
     *
152
     * @throws ArangoException
153
     */
154
    protected function prepareRequestOptions(array $options): HttpRequestOptions
155
    {
156
        if (isset($options['body'])) {
157
            $options['body'] = $this->jsonEncode($options['body']);
158
        }
159
160
        return new HttpRequestOptions($options);
161
    }
162
163
    /**
164
     * Return the response with debug information (for internal testing purposes).
165
     *
166
     * @param  array<mixed>  $options
167
     *
168
     * @throws GuzzleException
169
     */
170
    public function debugRequest(
171
        string $method,
172
        string $uri,
173
        array $options = [],
174
        ?string $database = null,
175
    ): ResponseInterface {
176
        $uri = $this->prependDatabaseToUri($uri, $database);
177
        $options['debug'] = true;
178
179
        return $this->httpClient->request($method, $uri, $options);
180
    }
181
182
    protected function prependDatabaseToUri(string $uri, ?string $database = null): string
183
    {
184
        if (!isset($database)) {
185
            $database = $this->config->database;
186
        }
187
188
        return '/_db/' . urlencode($database) . $uri;
189
    }
190
191
    /**
192
     * @throws ArangoException
193
     */
194
    protected function handleGuzzleException(Throwable $e): void
195
    {
196
        $message = $e->getMessage();
197
        $code = $e->getCode();
198
199
        if ($e instanceof RequestException && $e->hasResponse()) {
200
            $response = $e->getResponse();
201
            if ($response !== null) {
202
                $decodedResponse = $this->decodeResponse($response);
203
            }
204
            if (isset($decodedResponse->errorMessage)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $decodedResponse does not seem to be defined for all execution paths leading up to this point.
Loading history...
205
                $message = (string) $decodedResponse->errorMessage;
206
            }
207
208
            if (isset($decodedResponse->code)) {
209
                $code = (int) $decodedResponse->code;
210
            }
211
        }
212
213
        throw(
214
            new ArangoException(
215
                $code . ' - ' . $message,
216
                $code,
217
            )
218
        );
219
    }
220
221
    /**
222
     * @SuppressWarnings(PHPMD.StaticAccess)
223
     */
224
    protected function cleanupResponse(ResponseInterface $response): stdClass
225
    {
226
        $response = $this->decodeResponse($response);
227
        unset($response->error);
228
        unset($response->code);
229
230
        return $response;
231
    }
232
233
    /**
234
     * @param  array<scalar>  $bindVars
235
     * @param  array<mixed>  $options
236
     * @return Statement
237
     */
238
    public function prepare(
239
        string $query,
240
        array $bindVars = [],
241
        array $options = [],
242
    ): Traversable {
243
        return new Statement($this, $query, $bindVars, $options);
244
    }
245
246
    /**
247
     * @return mixed
248
     */
249
    public function getConfig(?string $value = null): mixed
250
    {
251
        if ($value) {
252
            return $this->config->$value;
253
        }
254
        return $this->config->toArray();
255
    }
256
257
    public function setDatabase(string $name): void
258
    {
259
        $this->config->database = $name;
260
    }
261
262
    public function getDatabase(): string
263
    {
264
        return $this->config->database;
265
    }
266
267
    public function setHttpClient(GuzzleClient $httpClient): void
268
    {
269
        $this->httpClient = $httpClient;
270
    }
271
272
    public function getHttpClient(): GuzzleClient
273
    {
274
        return $this->httpClient;
275
    }
276
277
    public function getUser(): string
278
    {
279
        return (string) $this->config->username;
280
    }
281
}
282