Passed
Push — master ( 524f76...237f5e )
by Paul
02:04
created

AbstractRequest::formatUri()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 4
nop 1
dl 0
loc 8
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
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
    public function __construct(GuzzleClient $client, Config $config, $serializer = null)
53
    {
54
        $this->client = $client;
55
        $this->serializer = $serializer;
56
        $this->config = $config;
57
58
        $this->setUp();
59
        $this->validateConfig();
60
    }
61
62
    /**
63
     * @return string|null
64
     */
65
    public function getUri()
66
    {
67
        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
    protected function requestGet($uri, QueryParams $queryParams = null)
77
    {
78
        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
    protected function execute($method, string $uri, $formData = [], QueryParams $queryParams = null)
137
    {
138
        $queryParams = $queryParams ?: new QueryParams();
139
        $options = $this->normalizeFormData($formData);
140
        $uri = $this->normalizeUri($uri, $queryParams);
141
142
        $options = array_merge($options, ['headers' => $this->getHeaders()->toArray()]);
143
144
        $response = $this->sendRequest($method, $uri, $options);
145
        $this->applyResponseTransformers($response);
146
147
        $this->config->set('serialization_context', []);
148
149
        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
    private function sendRequest($method, string $uri, $formData = [])
162
    {
163
        $responseRef = $this->createResponseReflectionInstance();
164
165
        $uri = $this->formatUri($uri);
166
167
        try {
168
            $psrResponse = $this->client->request($method, $uri, $formData);
169
170
            $response = $responseRef->newInstance(
171
                $psrResponse->getBody()->getContents(),
172
                $psrResponse->getStatusCode(),
173
                $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
        return $response;
190
    }
191
192
    /**
193
     * @param string $uri
194
     *
195
     * @return string
196
     */
197
    protected function formatUri(string $uri): string
198
    {
199
        $baseUri = $this->client->getConfig('base_uri');
200
201
        // todo: review
202
        return ($baseUri instanceof Uri && $baseUri->getPath())
203
            ? rtrim($baseUri->getPath(), '/') . '/' . ltrim($uri, '/')
204
            : $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
    protected function applyResponseTransformers(ResponseInterface $data)
235
    {
236
        foreach ($this->config->get(Config::RESPONSE_TRANSFORMERS, []) as $transformer) {
237
            if ($transformer instanceof TransformerInterface && $transformer->supports($data)) {
238
                $transformer->transform($data);
239
                continue;
240
            }
241
242
            if ($transformer instanceof \Closure) {
243
                $transformer($data);
244
            }
245
        }
246
    }
247
248
    /**
249
     * Tries to identify the data object sent, and convert them
250
     * into an array properly handled by the JMSSerializer
251
     * and for the acceptance of Kong API.
252
     *
253
     * @param array|object $formData
254
     *
255
     * @return array
256
     */
257
    private function normalizeFormData($formData = [])
258
    {
259
        if (empty($formData)) {
260
            return $formData;
261
        }
262
263
        $defaultFormNormalizer = $this->createDefaultFormNormalizer();
264
265
        $formNormalizer = $this->config->get(Config::FORM_NORMALIZER, $defaultFormNormalizer);
266
        if (!$formNormalizer instanceof FormNormalizerInterface) {
267
            return [];
268
        }
269
270
        $formParams = $formNormalizer->normalize($formData);
271
272
        if (!empty($formParams) && !isset($formParams['form_params'])) {
273
            $formParams = ['form_params' => $formParams];
274
        }
275
276
        return $formParams;
277
    }
278
279
    /**
280
     * Create default form normalizer
281
     *
282
     * @return FormNormalizerInterface
283
     */
284
    protected function createDefaultFormNormalizer(): FormNormalizerInterface
285
    {
286
        return new DefaultFormNormalizer(
287
            $this->serializer,
288
            $this->config->get('serialization_context')
289
        );
290
    }
291
292
    /**
293
     * Adds a query string params to the URI.
294
     *
295
     * @param string $uri
296
     * @param QueryParams $queryParams
297
     *
298
     * @return string
299
     */
300
    private function normalizeUri(string $uri, QueryParams $queryParams)
301
    {
302
        if (false !== strpos($uri, '?')) {
303
            throw new InvalidParameterException(sprintf(
304
                'It was not possible to normalize the URI as the current URI %s already 
305
                has the interrogation char in its string.' .
306
                $uri
307
            ));
308
        }
309
310
        return $uri . $queryParams->toString();
311
    }
312
313
    /**
314
     * Sets the Serialization context based in the groups the request should deal with.
315
     *
316
     * @param array $groups
317
     *
318
     * @return void
319
     */
320
    protected function setSerializationContextFor(array $groups = []): void
321
    {
322
323
        $serializationContext = Context::create()->setGroups($groups);
324
325
        $this->config->set('serialization_context', $serializationContext);
326
    }
327
328
    /**
329
     * Creates a Reflection Response class.
330
     *
331
     * @return \ReflectionClass
332
     */
333
    private function createResponseReflectionInstance(): \ReflectionClass
334
    {
335
        $responseClass = $this->config->get(Config::RESPONSE_CLASS, Response::class);
336
        $responseRef = new \ReflectionClass($responseClass);
337
338
        if (!$responseRef->implementsInterface(ResponseInterface::class)) {
339
            throw new InvalidParameterException(sprintf(
340
                'The response class must be an implementation of %s',
341
                ResponseInterface::class
342
            ));
343
        }
344
345
        return $responseRef;
346
    }
347
348
    protected function disableFormNormalizer()
349
    {
350
        $this->config->set(Config::FORM_NORMALIZER, null);
351
    }
352
353
    /**
354
     * Validates if the object has a valid id, otherwise throws an exception.
355
     *
356
     * @param object $object
357
     *
358
     * @return void
359
     */
360
    protected function validateObjectId($object)
361
    {
362
        if (!method_exists($object, 'getId') || null === $object->getId()) {
363
            throw new InvalidParameterException(sprintf(
364
                'The object "%s" must have an ID to continue the operation. "%s" given.',
365
                get_class($object),
366
                gettype($object->getId())
367
            ));
368
        }
369
    }
370
371
    /**
372
     * Validates the required parameters from Config file.
373
     *
374
     * @return void
375
     */
376
    private function validateConfig()
377
    {
378
        Assert::lazy()
379
            ->that($this->config->toArray(), Config::URI_PREFIX)->keyExists(Config::URI_PREFIX)
380
            ->verifyNow();
381
    }
382
383
    /**
384
     * Set headers for request
385
     *
386
     * @param RequestHeaders $headers
387
     */
388
    protected function setHeaders(RequestHeaders $headers)
389
    {
390
        $this->headers = $headers;
391
    }
392
393
    /**
394
     * Get headers for request
395
     *
396
     * @return RequestHeaders
397
     */
398
    protected function getHeaders(): RequestHeaders
399
    {
400
        return $this->headers;
401
    }
402
403
    /**
404
     * Initialization of the request.
405
     *
406
     * @return void
407
     */
408
    abstract protected function setUp();
409
}
410