Passed
Push — master ( 0e88a8...69aa8a )
by Paul
02:34
created

AbstractRequest::sendRequest()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 7.456

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 4
nop 3
dl 0
loc 16
ccs 4
cts 10
cp 0.4
crap 7.456
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace CCT\Component\Rest\Http;
6
7
use Assert\Assert;
8
use CCT\Component\Rest\Config;
9
use CCT\Component\Rest\Exception\InvalidParameterException;
10
use CCT\Component\Rest\Exception\ServiceUnavailableException;
11
use CCT\Component\Rest\Form\Normalizer\DefaultFormNormalizer;
12
use CCT\Component\Rest\Form\Normalizer\FormNormalizerInterface;
13
use CCT\Component\Rest\Http\Definition\QueryParams;
14
use CCT\Component\Rest\Http\Definition\RequestHeaders;
15
use CCT\Component\Rest\Serializer\Context\Context;
16
use CCT\Component\Rest\Serializer\SerializerInterface;
17
use CCT\Component\Rest\Transformer\TransformerInterface;
18
use GuzzleHttp\Client as GuzzleClient;
19
use GuzzleHttp\Exception\ConnectException;
20
use GuzzleHttp\Exception\RequestException;
21
use GuzzleHttp\Psr7\Uri;
22
use Psr\Http\Message\ResponseInterface as BaseResponseInterface;
23
24
abstract class AbstractRequest implements RequestInterface
25
{
26
    /**
27
     * @var GuzzleClient
28
     */
29
    protected $client;
30
31
    /**
32
     * @var SerializerInterface
33
     */
34
    protected $serializer;
35
36
    /**
37
     * @var Config
38
     */
39
    protected $config;
40
41
    /**
42
     * Request headers
43
     *
44
     * @var RequestHeaders
45
     */
46
    protected $headers;
47
48
    /**
49
     * @param GuzzleClient $client
50
     * @param SerializerInterface $serializer
51
     * @param Config $config
52
     */
53 4
    public function __construct(GuzzleClient $client, Config $config, $serializer = null)
54
    {
55 4
        $this->client = $client;
56 4
        $this->serializer = $serializer;
57 4
        $this->config = $config;
58
59 4
        $this->setUp();
60 4
        $this->validateConfig();
61 4
    }
62
63
    /**
64
     * @return string|null
65
     */
66 3
    public function getUri()
67
    {
68 3
        return $this->config->get(Config::URI_PREFIX);
69
    }
70
71
    /**
72
     * @param string $uri
73
     * @param QueryParams|null $queryParams
74
     *
75
     * @return ResponseInterface|\Symfony\Component\HttpFoundation\Response
76
     */
77 3
    protected function requestGet($uri, QueryParams $queryParams = null)
78
    {
79 3
        return $this->execute(self::METHOD_GET, $uri, [], $queryParams);
80
    }
81
82
    /**
83
     * @param string $uri
84
     * @param QueryParams|null $queryParams
85
     *
86
     * @return ResponseInterface|\Symfony\Component\HttpFoundation\Response
87
     */
88
    protected function requestDelete($uri, QueryParams $queryParams = null)
89
    {
90
        return $this->execute(self::METHOD_DELETE, $uri, [], $queryParams);
91
    }
92
93
    /**
94
     * @param string $uri
95
     * @param array|object $formData
96
     * @param QueryParams|null $queryParams
97
     *
98
     * @return ResponseInterface|\Symfony\Component\HttpFoundation\Response
99
     */
100
    protected function requestPost($uri, $formData, QueryParams $queryParams = null)
101
    {
102
        return $this->execute(self::METHOD_POST, $uri, $formData, $queryParams);
103
    }
104
105
    /**
106
     * @param string $uri
107
     * @param array|object $formData
108
     * @param QueryParams|null $queryParams
109
     *
110
     * @return ResponseInterface|\Symfony\Component\HttpFoundation\Response
111
     */
112
    protected function requestPatch($uri, $formData, QueryParams $queryParams = null)
113
    {
114
        return $this->execute(self::METHOD_PATCH, $uri, $formData, $queryParams);
115
    }
116
117
    /**
118
     * @param string $uri
119
     * @param array|object $formData
120
     * @param QueryParams|null $queryParams
121
     *
122
     * @return ResponseInterface|\Symfony\Component\HttpFoundation\Response
123
     */
124
    protected function requestPut($uri, $formData, QueryParams $queryParams = null)
125
    {
126
        return $this->execute(self::METHOD_PUT, $uri, $formData, $queryParams);
127
    }
128
129
    /**
130
     * @param string $method
131
     * @param string $uri
132
     * @param array|object $formData
133
     * @param QueryParams|null $queryParams
134
     *
135
     * @return ResponseInterface|\Symfony\Component\HttpFoundation\Response
136
     */
137 3
    protected function execute($method, string $uri, $formData = [], QueryParams $queryParams = null)
138
    {
139 3
        $queryParams = $queryParams ?: new QueryParams();
140 3
        $options = $this->normalizeFormData($formData);
141 3
        $uri = $this->normalizeUri($uri, $queryParams);
142
143 3
        $options = array_merge($options, ['headers' => $this->getHeaders()->toArray()]);
144
145 3
        $response = $this->sendRequest($method, $uri, $options);
146 3
        $this->applyResponseTransformers($response);
147
148 3
        $this->config->set('serialization_context', []);
149
150 3
        return $response;
151
    }
152
153
    /**
154
     * @param string $method
155
     * @param string $uri
156
     * @param array $formData
157
     *
158
     * @throws ServiceUnavailableException
159
     *
160
     * @return Response|object
161
     */
162 3
    private function sendRequest($method, string $uri, $formData = [])
163
    {
164 3
        $uri = $this->formatUri($uri);
165
166
        try {
167 3
            $response = $this->client->request($method, $uri, $formData);
168
        } catch (ConnectException $e) {
169
            throw new ServiceUnavailableException($e->getRequest(), $e->getMessage());
170
        } catch (RequestException $e) {
171
            if (null === $e->getResponse()->getBody()) {
172
                throw $e;
173
            }
174
            $response = $e->getResponse();
175
        }
176
177 3
        return  $this->createResponseRefFromResponse($response);
178
    }
179
180
    /**
181
     * Create Response reflection from a response
182
     *
183
     * @param BaseResponseInterface $response
184
     *
185
     * @return object
186
     */
187 3
    protected function createResponseRefFromResponse(BaseResponseInterface $response)
188
    {
189 3
        $responseRef = $this->createResponseReflectionInstance();
190
191 3
        return $responseRef->newInstance(
192 3
            $response->getBody()->getContents(),
193 3
            $response->getStatusCode(),
194 3
            $response->getHeaders()
195
        );
196
    }
197
198
    /**
199
     * @param string $uri
200
     *
201
     * @return string
202
     */
203 3
    protected function formatUri(string $uri): string
204
    {
205 3
        $baseUri = $this->client->getConfig('base_uri');
206
207
        // todo: review
208 3
        return ($baseUri instanceof Uri && $baseUri->getPath())
209
            ? rtrim($baseUri->getPath(), '/') . '/' . ltrim($uri, '/')
210 3
            : $uri;
211
    }
212
213
    /**
214
     * Appends new parameters to the URI.
215
     *
216
     * @param string $complement
217
     * @param string|null $uri
218
     *
219
     * @return string
220
     */
221
    protected function appendToUri(string $complement, ?string $uri = null)
222
    {
223
        $uri = $uri ?: $this->config->get(Config::URI_PREFIX);
224
225
        return sprintf(
226
            '%s/%s',
227
            rtrim($uri, '/'),
228
            ltrim($complement, '/')
229
        );
230
    }
231
232
    /**
233
     * It is possible to handle the Response data defining the Config key response_transformers
234
     * with an instance of Closure or an instance of TransformerInterface.
235
     *
236
     * @param ResponseInterface $data
237
     *
238
     * @return void
239
     */
240 3
    protected function applyResponseTransformers(ResponseInterface $data)
241
    {
242 3
        foreach ($this->config->get(Config::RESPONSE_TRANSFORMERS, []) as $transformer) {
243 2
            $this->applyResponseTransformer($transformer, $data);
244
        }
245 3
    }
246
247
    /**
248
     * Applied single response transformer
249
     *
250
     * @param TransformerInterface|\Closure $transformer
251
     * @param ResponseInterface $data
252
     */
253 2
    protected function applyResponseTransformer($transformer, ResponseInterface $data)
254
    {
255 2
        if ($transformer instanceof TransformerInterface && $transformer->supports($data)) {
256 2
            $transformer->transform($data);
257 2
            return;
258
        }
259
260 2
        if ($transformer instanceof \Closure) {
261
            $transformer($data);
262
        }
263 2
    }
264
265
    /**
266
     * Tries to identify the data object sent, and convert them
267
     * into an array properly handled by the JMSSerializer
268
     * and for the acceptance of Kong API.
269
     *
270
     * @param array|object $formData
271
     *
272
     * @return array
273
     */
274 3
    private function normalizeFormData($formData = [])
275
    {
276 3
        if (empty($formData)) {
277 3
            return $formData;
278
        }
279
280
        $defaultFormNormalizer = $this->createDefaultFormNormalizer();
281
282
        $formNormalizer = $this->config->get(Config::FORM_NORMALIZER, $defaultFormNormalizer);
283
        if (!$formNormalizer instanceof FormNormalizerInterface) {
284
            return [];
285
        }
286
287
        $formParams = $formNormalizer->normalize($formData);
288
289
        if (!empty($formParams) && !isset($formParams['form_params'])) {
290
            $formParams = ['form_params' => $formParams];
291
        }
292
293
        return $formParams;
294
    }
295
296
    /**
297
     * Create default form normalizer
298
     *
299
     * @return FormNormalizerInterface
300
     */
301
    protected function createDefaultFormNormalizer(): FormNormalizerInterface
302
    {
303
        return new DefaultFormNormalizer(
304
            $this->serializer,
305
            $this->config->get('serialization_context')
306
        );
307
    }
308
309
    /**
310
     * Adds a query string params to the URI.
311
     *
312
     * @param string $uri
313
     * @param QueryParams $queryParams
314
     *
315
     * @return string
316
     */
317 3
    private function normalizeUri(string $uri, QueryParams $queryParams)
318
    {
319 3
        if (false !== strpos($uri, '?')) {
320
            throw new InvalidParameterException(sprintf(
321
                'It was not possible to normalize the URI as the current URI %s already 
322
                has the interrogation char in its string.' .
323
                $uri
324
            ));
325
        }
326
327 3
        return $uri . $queryParams->toString();
328
    }
329
330
    /**
331
     * Sets the Serialization context based in the groups the request should deal with.
332
     *
333
     * @param array $groups
334
     *
335
     * @return void
336
     */
337
    protected function setSerializationContextFor(array $groups = []): void
338
    {
339
340
        $serializationContext = Context::create()->setGroups($groups);
341
342
        $this->config->set('serialization_context', $serializationContext);
343
    }
344
345
    /**
346
     * Creates a Reflection Response class.
347
     *
348
     * @return \ReflectionClass
349
     */
350 3
    private function createResponseReflectionInstance(): \ReflectionClass
351
    {
352 3
        $responseClass = $this->config->get(Config::RESPONSE_CLASS, Response::class);
353 3
        $responseRef = new \ReflectionClass($responseClass);
354
355 3
        if (!$responseRef->implementsInterface(ResponseInterface::class)) {
356
            throw new InvalidParameterException(sprintf(
357
                'The response class must be an implementation of %s',
358
                ResponseInterface::class
359
            ));
360
        }
361
362 3
        return $responseRef;
363
    }
364
365
    protected function disableFormNormalizer()
366
    {
367
        $this->config->set(Config::FORM_NORMALIZER, null);
368
    }
369
370
    /**
371
     * Validates if the object has a valid id, otherwise throws an exception.
372
     *
373
     * @param object $object
374
     *
375
     * @return void
376
     */
377
    protected function validateObjectId($object)
378
    {
379
        if (!method_exists($object, 'getId') || null === $object->getId()) {
380
            throw new InvalidParameterException(sprintf(
381
                'The object "%s" must have an ID to continue the operation. "%s" given.',
382
                get_class($object),
383
                gettype($object->getId())
384
            ));
385
        }
386
    }
387
388
    /**
389
     * Validates the required parameters from Config file.
390
     *
391
     * @return void
392
     */
393 4
    private function validateConfig()
394
    {
395 4
        Assert::lazy()
396 4
            ->that($this->config->toArray(), Config::URI_PREFIX)->keyExists(Config::URI_PREFIX)
397 4
            ->verifyNow();
398 4
    }
399
400
    /**
401
     * Set headers for request
402
     *
403
     * @param RequestHeaders $headers
404
     */
405 3
    protected function setHeaders(RequestHeaders $headers)
406
    {
407 3
        $this->headers = $headers;
408 3
    }
409
410
    /**
411
     * Get headers for request
412
     *
413
     * @return RequestHeaders
414
     */
415 3
    protected function getHeaders(): RequestHeaders
416
    {
417 3
        return $this->headers;
418
    }
419
420
    /**
421
     * Initialization of the request.
422
     *
423
     * @return void
424
     */
425
    abstract protected function setUp();
426
}
427