Passed
Push — master ( 12d735...10f25e )
by Paul
02:49
created

AbstractRequest::applyResponseTransformers()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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