Passed
Pull Request — master (#94)
by Julien
03:25 queued 51s
created

RestClient::responseOrJsonNullable()   A

Complexity

Conditions 6
Paths 9

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 16
c 1
b 0
f 0
nc 9
nop 1
dl 0
loc 27
ccs 13
cts 13
cp 1
crap 6
rs 9.1111
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Mapado\RestClientSdk;
6
7
use GuzzleHttp\Psr7;
8
use Mapado\RestClientSdk\Exception\RestClientException;
9
use Mapado\RestClientSdk\Exception\RestException;
10
use Psr\Http\Client\ClientExceptionInterface;
11
use Psr\Http\Client\ClientInterface;
12
use Psr\Http\Message\ResponseInterface;
13
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
14
15
/**
16
 * Class RestClient
17
 *
18
 * @author Julien Deniau <[email protected]>
19
 */
20
class RestClient
21
{
22
    /**
23
     * @var ClientInterface
24
     */
25
    private $httpClient;
26
27
    /**
28
     * @var ?string
29
     */
30
    private $baseUrl;
31
32
    /**
33
     * @var bool
34
     */
35
    private $logHistory;
36
37
    /**
38
     * @var array
39
     */
40
    private $requestHistory;
41
42
    /**
43
     * @var ?SymfonyRequest
44
     */
45
    private $currentRequest;
46
47
    public function __construct(
48
        ClientInterface $httpClient,
49
        ?string $baseUrl = null
50
    ) {
51 1
        $this->httpClient = $httpClient;
52 1
        $this->baseUrl =
53 1
            null !== $baseUrl && '/' === mb_substr($baseUrl, -1)
54
                ? mb_substr($baseUrl, 0, -1)
55 1
                : $baseUrl;
56 1
        $this->logHistory = false;
57 1
        $this->requestHistory = [];
58 1
    }
59
60
    public function isHistoryLogged(): bool
61
    {
62 1
        return $this->logHistory;
63
    }
64
65
    public function setCurrentRequest(SymfonyRequest $currentRequest): self
66
    {
67
        $this->currentRequest = $currentRequest;
68
69
        return $this;
70
    }
71
72
    public function setLogHistory(bool $logHistory): self
73
    {
74 1
        $this->logHistory = $logHistory;
75
76 1
        return $this;
77
    }
78
79
    public function getRequestHistory(): array
80
    {
81 1
        return $this->requestHistory;
82
    }
83
84
    /**
85
     * get a path
86
     *
87
     * @return array|ResponseInterface|null
88
     *
89
     * @throws RestException
90
     */
91
    public function get(string $path, array $parameters = [])
92
    {
93 1
        $requestUrl = $this->baseUrl . $path;
94
        try {
95 1
            $response = $this->executeRequest('GET', $requestUrl, $parameters);
96
97 1
            $this->throwClientException(
98 1
                $response,
99
                $path,
100 1
                'Error while getting resource',
101 1
                7
102
            );
103 1
            $this->throwServerException(
104 1
                $response,
105
                $path,
106 1
                'Error while getting resource',
107 1
                1
108
            );
109
110 1
            return $this->responseOrJsonNullable($response);
111 1
        } catch (ClientExceptionInterface $e) {
112
            throw new RestException(
113
                'Error while getting resource',
114
                $path,
115
                [],
116
                1,
117
                $e
118
            );
119
        }
120
    }
121
122
    /**
123
     * @throws RestException
124
     */
125
    public function delete(string $path): void
126
    {
127
        try {
128 1
            $response = $this->executeRequest('DELETE', $this->baseUrl . $path);
129
130 1
            $this->throwServerException(
131 1
                $response,
132
                $path,
133 1
                'Error while deleting resource',
134 1
                2
135
            );
136 1
        } catch (ClientExceptionInterface $e) {
137
            throw new RestException(
138
                'Error while deleting resource',
139
                $path,
140
                [],
141
                2,
142
                $e
143
            );
144
        }
145 1
    }
146
147
    /**
148
     * @return array|ResponseInterface
149
     *
150
     * @throws RestClientException
151
     * @throws RestException
152
     */
153
    public function post(string $path, array $data, array $parameters = [])
154
    {
155 1
        $parameters['json'] = $data;
156
        try {
157 1
            $response = $this->executeRequest(
158 1
                'POST',
159 1
                $this->baseUrl . $path,
160
                $parameters
161
            );
162
163 1
            $this->throwClientException(
164 1
                $response,
165
                $path,
166 1
                'Cannot create resource',
167 1
                3
168
            );
169 1
            $this->throwClientNotFoundException(
170 1
                $response,
171
                $path,
172 1
                'Cannot create resource',
173 1
                3
174
            );
175 1
            $this->throwServerException(
176 1
                $response,
177
                $path,
178 1
                'Error while posting resource',
179 1
                4
180
            );
181
182 1
            return $this->responseOrJson($response);
183 1
        } catch (ClientExceptionInterface $e) {
184
            throw new RestException(
185
                'Error while posting resource',
186
                $path,
187
                [],
188
                4,
189
                $e
190
            );
191
        }
192
    }
193
194
    /**
195
     * @return array|ResponseInterface
196
     *
197
     * @throws RestClientException
198
     * @throws RestException
199
     */
200
    public function put(string $path, array $data, array $parameters = [])
201
    {
202 1
        $parameters['json'] = $data;
203
204
        try {
205 1
            $response = $this->executeRequest(
206 1
                'PUT',
207 1
                $this->baseUrl . $path,
208
                $parameters
209
            );
210
211 1
            $this->throwClientNotFoundException(
212 1
                $response,
213
                $path,
214 1
                'Cannot update resource',
215 1
                5
216
            );
217 1
            $this->throwClientException(
218 1
                $response,
219
                $path,
220 1
                'Cannot update resource',
221 1
                5
222
            );
223 1
            $this->throwServerException(
224 1
                $response,
225
                $path,
226 1
                'Error while puting resource',
227 1
                6
228
            );
229
230 1
            return $this->responseOrJson($response);
231 1
        } catch (ClientExceptionInterface $e) {
232
            throw new RestException(
233
                'Error while puting resource',
234
                $path,
235
                [],
236
                6,
237
                $e
238
            );
239
        }
240
    }
241
242
    /**
243
     * Merge default parameters.
244
     */
245
    protected function mergeDefaultParameters(array $parameters): array
246
    {
247 1
        $request = $this->getCurrentRequest();
248
249 1
        $defaultParameters = ['version' => '1.0'];
250 1
        if (null !== $request) {
251
            $defaultParameters['headers'] = ['Referer' => $request->getUri()];
252
        }
253
254 1
        $out = array_replace_recursive($defaultParameters, $parameters);
255
256 1
        if (null === $out) {
257
            throw new \RuntimeException(
258
                sprintf(
259
                    'Error while calling array_replace_recursive in %s. This should not happen.',
260
                    __METHOD__
261
                )
262
            );
263
        }
264
265 1
        return $out;
266
    }
267
268
    protected function getCurrentRequest(): ?SymfonyRequest
269
    {
270 1
        if ('cli' === \PHP_SAPI) {
271
            // we are in cli mode, do not bother to get request
272 1
            return null;
273
        }
274
275
        if (!$this->currentRequest) {
276
            $this->currentRequest = SymfonyRequest::createFromGlobals();
277
        }
278
279
        return $this->currentRequest;
280
    }
281
282
    /**
283
     * @throws ClientExceptionInterface
284
     */
285
    private function executeRequest(
286
        string $method,
287
        string $url,
288
        array $parameters = []
289
    ): ResponseInterface {
290 1
        $parameters = $this->mergeDefaultParameters($parameters);
291
292 1
        $startTime = null;
293 1
        if ($this->isHistoryLogged()) {
294 1
            $startTime = microtime(true);
295
        }
296
297
        try {
298 1
            $request = new Psr7\Request($method, $url, $parameters);
299 1
            $response = $this->httpClient->sendRequest($request);
300 1
            $this->logRequest(
301 1
                $startTime,
302
                $method,
303
                $url,
304
                $parameters,
305
                $response
306
            );
307
        } catch (ClientExceptionInterface $e) {
308
            $this->logRequest($startTime, $method, $url, $parameters);
309
310
            throw $e;
311
        }
312
313 1
        return $response;
314
    }
315
316
    private function logRequest(
317
        ?float $startTime,
318
        string $method,
319
        string $url,
320
        array $parameters,
321
        ?ResponseInterface $response = null
322
    ): void {
323 1
        if ($this->isHistoryLogged()) {
324 1
            $queryTime = microtime(true) - $startTime;
325
326 1
            $this->requestHistory[] = [
327 1
                'method' => $method,
328 1
                'url' => $url,
329 1
                'parameters' => $parameters,
330 1
                'response' => $response,
331 1
                'responseBody' => $response
332 1
                    ? json_decode((string) $response->getBody(), true)
333
                    : null,
334 1
                'queryTime' => $queryTime,
335 1
                'backtrace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS),
336
            ];
337
        }
338 1
    }
339
340
    private function throwClientException(
341
        ResponseInterface $response,
342
        string $path,
343
        string $message,
344
        int $code
345
    ): void {
346 1
        $statusCode = $response->getStatusCode();
347
348 1
        if (404 !== $statusCode && $statusCode >= 400 && $statusCode < 500) {
349
            // 4xx except 404
350 1
            $exception = new RestClientException($message, $path, [], $code);
351
352 1
            $exception->setResponse($response);
353
354 1
            throw $exception;
355
        }
356 1
    }
357
358
    private function throwServerException(
359
        ResponseInterface $response,
360
        string $path,
361
        string $message,
362
        int $code
363
    ): void {
364 1
        $statusCode = $response->getStatusCode();
365
366 1
        if ($statusCode >= 500 && $statusCode < 600) {
367
            // 5xx errors
368 1
            $exception = new RestException($message, $path, [], $code);
369
370 1
            $exception->setResponse($response);
371
372 1
            throw $exception;
373
        }
374 1
    }
375
376
    private function throwClientNotFoundException(
377
        ResponseInterface $response,
378
        string $path,
379
        string $message,
380
        int $code
381
    ): void {
382 1
        $statusCode = $response->getStatusCode();
383
384 1
        if (404 === $statusCode) {
385 1
            $exception = new RestClientException($message, $path, [], $code);
386
387 1
            $exception->setResponse($response);
388
389 1
            throw $exception;
390
        }
391 1
    }
392
393
    /**
394
     * @return array|ResponseInterface|null
395
     */
396
    private function responseOrJsonNullable(ResponseInterface $response)
397
    {
398 1
        if (404 === $response->getStatusCode()) {
399 1
            return null;
400
        }
401
402 1
        $headers = $response->getHeaders();
403 1
        $jsonContentTypeList = ['application/ld+json', 'application/json'];
404
405 1
        $requestIsJson = false;
406
407 1
        if (isset($headers['Content-Type'])) {
408 1
            foreach ($jsonContentTypeList as $contentType) {
409
                if (
410
                    false !==
411 1
                    mb_stripos($headers['Content-Type'][0], $contentType)
412
                ) {
413 1
                    $requestIsJson = true;
414 1
                    break;
415
                }
416
            }
417
        }
418
419 1
        if ($requestIsJson) {
420 1
            return json_decode((string) $response->getBody(), true);
421
        } else {
422 1
            return $response;
423
        }
424
    }
425
426
    /**
427
     * @return array|ResponseInterface
428
     */
429
    private function responseOrJson(ResponseInterface $response)
430
    {
431 1
        $out = $this->responseOrJsonNullable($response);
432
433 1
        if (null === $out) {
434
            throw new \RuntimeException(
435
                'response should not be null. This should not happen.'
436
            );
437
        }
438
439 1
        return $out;
440
    }
441
}
442