Passed
Push — next ( 737617...5ddafc )
by Bas
13:34 queued 11:32
created

ArangoClient   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 204
Duplicated Lines 0 %

Test Coverage

Coverage 97.22%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 65
dl 0
loc 204
ccs 70
cts 72
cp 0.9722
rs 10
c 1
b 0
f 0
wmc 29

15 Methods

Rating   Name   Duplication   Size   Complexity  
A prepareRequestOptions() 0 7 2
A prepare() 0 6 1
A generateEndpoint() 0 14 4
A setDatabase() 0 3 1
A request() 0 20 4
A handleGuzzleException() 0 23 6
A cleanupResponse() 0 7 1
A getUser() 0 3 1
A setHttpClient() 0 3 1
A getDatabase() 0 3 1
A getHttpClient() 0 3 1
A debugRequest() 0 10 1
A prependDatabaseToUri() 0 7 2
A getConfig() 0 6 2
A __construct() 0 6 1
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 117
    public function __construct(array $config = [], GuzzleClient $httpClient = null)
46
    {
47 117
        $config['endpoint'] = $this->generateEndpoint($config);
48 117
        $this->config = new HttpClientConfig($config);
49
50 117
        $this->httpClient = $httpClient ?? new GuzzleClient($this->config->mapGuzzleHttpClientConfig());
51
    }
52
53
    /**
54
     * @param  array<mixed>  $config
55
     */
56 117
    public function generateEndpoint(array $config): string
57
    {
58 117
        if (isset($config['endpoint'])) {
59
            return (string) $config['endpoint'];
60
        }
61 117
        $endpoint = 'http://localhost:8529';
62 117
        if (isset($config['host'])) {
63 2
            $endpoint = (string) $config['host'];
64
        }
65 117
        if (isset($config['port'])) {
66 2
            $endpoint .= ':' . (string) $config['port'];
67
        }
68
69 117
        return $endpoint;
70
    }
71
72
    /**
73
     * @param  array<mixed>|HttpRequestOptions  $options
74
     *
75
     * @throws ArangoException
76
     */
77 117
    public function request(string $method, string $uri, array|HttpRequestOptions $options = [], ?string $database = null): stdClass
78
    {
79 117
        $uri = $this->prependDatabaseToUri($uri, $database);
80
81 117
        if (is_array($options)) {
0 ignored issues
show
introduced by
The condition is_array($options) is always true.
Loading history...
82 117
            $options = $this->prepareRequestOptions($options);
83
        }
84
85 117
        $response = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
86
        try {
87 117
            $response = $this->httpClient->request($method, $uri, $options->all());
88 2
        } catch (Throwable $e) {
89 2
            $this->handleGuzzleException($e);
90
        }
91
92 117
        if ($response !== null) {
93 117
            return $this->cleanupResponse($response);
94
        }
95
96
        return new stdClass();
97
    }
98
99
    /**
100
     * @param  array<mixed>  $options
101
     *
102
     * @throws ArangoException
103
     */
104 117
    protected function prepareRequestOptions(array $options): HttpRequestOptions
105
    {
106 117
        if (isset($options['body'])) {
107 75
            $options['body'] = $this->jsonEncode($options['body']);
108
        }
109
110 117
        return new HttpRequestOptions($options);
111
    }
112
113
    /**
114
     * Return the response with debug information (for internal testing purposes).
115
     *
116
     * @param  array<mixed>  $options
117
     *
118
     * @throws GuzzleException
119
     */
120 2
    public function debugRequest(
121
        string $method,
122
        string $uri,
123
        array $options = [],
124
        ?string $database = null,
125
    ): ResponseInterface {
126 2
        $uri = $this->prependDatabaseToUri($uri, $database);
127 2
        $options['debug'] = true;
128
129 2
        return $this->httpClient->request($method, $uri, $options);
130
    }
131
132 117
    protected function prependDatabaseToUri(string $uri, ?string $database = null): string
133
    {
134 117
        if (!isset($database)) {
135 117
            $database = $this->config->database;
136
        }
137
138 117
        return '/_db/' . urlencode($database) . $uri;
139
    }
140
141
    /**
142
     * @throws ArangoException
143
     */
144 2
    protected function handleGuzzleException(Throwable $e): void
145
    {
146 2
        $message = $e->getMessage();
147 2
        $code = $e->getCode();
148
149 2
        if ($e instanceof RequestException && $e->hasResponse()) {
150 2
            $response = $e->getResponse();
151 2
            if ($response !== null) {
152 2
                $decodedResponse = $this->decodeResponse($response);
153
            }
154 2
            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...
155 2
                $message = (string) $decodedResponse->errorMessage;
156
            }
157
158 2
            if (isset($decodedResponse->code)) {
159 2
                $code = (int) $decodedResponse->code;
160
            }
161
        }
162
163
        throw(
164 2
            new ArangoException(
165 2
                $code . ' - ' . $message,
166 2
                $code,
167 2
            )
168
        );
169
    }
170
171
    /**
172
     * @SuppressWarnings(PHPMD.StaticAccess)
173
     */
174 117
    protected function cleanupResponse(ResponseInterface $response): stdClass
175
    {
176 117
        $response = $this->decodeResponse($response);
177 117
        unset($response->error);
178 117
        unset($response->code);
179
180 117
        return $response;
181
    }
182
183
    /**
184
     * @param  array<scalar>  $bindVars
185
     * @param  array<mixed>  $options
186
     * @return Statement
187
     */
188 16
    public function prepare(
189
        string $query,
190
        array $bindVars = [],
191
        array $options = [],
192
    ): Traversable {
193 16
        return new Statement($this, $query, $bindVars, $options);
194
    }
195
196
    /**
197
     * @return mixed
198
     */
199 117
    public function getConfig(string $value = null): mixed
200
    {
201 117
        if ($value) {
202 117
            return $this->config->$value;
203
        }
204 4
        return $this->config->toArray();
205
    }
206
207 117
    public function setDatabase(string $name): void
208
    {
209 117
        $this->config->database = $name;
210
    }
211
212 8
    public function getDatabase(): string
213
    {
214 8
        return $this->config->database;
215
    }
216
217 1
    public function setHttpClient(GuzzleClient $httpClient): void
218
    {
219 1
        $this->httpClient = $httpClient;
220
    }
221
222 1
    public function getHttpClient(): GuzzleClient
223
    {
224 1
        return $this->httpClient;
225
    }
226
227 117
    public function getUser(): string
228
    {
229 117
        return (string) $this->config->username;
230
    }
231
}
232