Passed
Push — master ( 69aa8a...e95496 )
by Paul
03:24
created

AbstractRequest::normalizeFormData()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 5.1971

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 0
loc 14
ccs 3
cts 8
cp 0.375
crap 5.1971
rs 9.4285
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
        return $formNormalizer->normalize($formData);
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