Passed
Pull Request — master (#13)
by
unknown
02:44
created

ApiService::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 9
c 0
b 0
f 0
nc 1
nop 8
dl 0
loc 11
ccs 10
cts 10
cp 1
crap 1
rs 9.9666

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace ElevenLabs\Api\Service;
6
7
use Assert\Assertion;
8
use ElevenLabs\Api\Decoder\DecoderUtils;
9
use ElevenLabs\Api\Definition\Parameter;
10
use ElevenLabs\Api\Definition\Parameters;
11
use ElevenLabs\Api\Definition\RequestDefinition;
12
use ElevenLabs\Api\Definition\ResponseDefinition;
13
use ElevenLabs\Api\Schema;
14
use ElevenLabs\Api\Service\Exception\ConstraintViolations;
15
use ElevenLabs\Api\Service\Exception\RequestViolations;
16
use ElevenLabs\Api\Service\Exception\ResponseViolations;
17
use ElevenLabs\Api\Service\Resource\Item;
18
use ElevenLabs\Api\Service\Resource\ResourceInterface;
19
use ElevenLabs\Api\Validator\MessageValidator;
20
use Http\Client\HttpAsyncClient;
21
use Http\Client\HttpClient;
22
use Http\Message\MessageFactory;
23
use Http\Message\UriFactory;
24
use Http\Promise\Promise;
25
use Psr\Http\Message\RequestInterface;
26
use Psr\Http\Message\ResponseInterface;
27
use Psr\Http\Message\UriInterface;
28
use Rize\UriTemplate;
29
use Symfony\Component\Serializer\SerializerInterface;
30
31
/**
32
 * Class ApiService.
33
 */
34
class ApiService
35
{
36
    const DEFAULT_CONFIG = [
37
        // override the scheme and host provided in the API schema by a baseUri (ex:https://domain.com)
38
        'baseUri' => null,
39
        // validate request
40
        'validateRequest' => true,
41
        // validate response
42
        'validateResponse' => false,
43
        // return response instead of a denormalized object
44
        'returnResponse' => false,
45
    ];
46
47
    /**
48
     * @var UriInterface
49
     */
50
    private $baseUri;
51
52
    /**
53
     * @var Schema
54
     */
55
    private $schema;
56
57
    /**
58
     * @var MessageValidator
59
     */
60
    private $messageValidator;
61
62
    /**
63
     * @var HttpAsyncClient|HttpClient
64
     */
65
    private $client;
66
67
    /**
68
     * @var MessageFactory
69
     */
70
    private $messageFactory;
71
72
    /**
73
     * @var UriTemplate
74
     */
75
    private $uriTemplate;
76
77
    /**
78
     * @var UriFactory
79
     */
80
    private $uriFactory;
81
82
    /**
83
     * @var SerializerInterface
84
     */
85
    private $serializer;
86
87
    /**
88
     * @var array
89
     */
90
    private $config;
91
92
    /**
93
     * ApiService constructor.
94
     *
95
     * @param UriFactory          $uriFactory
96
     * @param UriTemplate         $uriTemplate
97
     * @param HttpClient          $client
98
     * @param MessageFactory      $messageFactory
99
     * @param Schema              $schema
100
     * @param MessageValidator    $messageValidator
101
     * @param SerializerInterface $serializer
102
     * @param array               $config
103
     *
104
     * @throws \Assert\AssertionFailedException
105
     */
106 34
    public function __construct(UriFactory $uriFactory, UriTemplate $uriTemplate, HttpClient $client, MessageFactory $messageFactory, Schema $schema, MessageValidator $messageValidator, SerializerInterface $serializer, array $config = [])
107
    {
108 34
        $this->uriFactory = $uriFactory;
109 34
        $this->uriTemplate = $uriTemplate;
110 34
        $this->schema = $schema;
111 34
        $this->messageValidator = $messageValidator;
112 34
        $this->client = $client;
113 34
        $this->messageFactory = $messageFactory;
114 34
        $this->serializer = $serializer;
115 34
        $this->config = $this->getConfig($config);
116 30
        $this->baseUri = $this->getBaseUri();
117 27
    }
118
119
    /**
120
     * @param string $operationId The name of your operation as described in the API Schema
121
     * @param array  $params      An array of request parameters
122
     *
123
     * @throws ConstraintViolations
124
     * @throws \Http\Client\Exception
125
     *
126
     * @return ResourceInterface|ResponseInterface|array|object
127
     */
128 21
    public function call(string $operationId, array $params = [])
129
    {
130 21
        $requestDefinition = $this->schema->getRequestDefinition($operationId);
131 21
        $request = $this->createRequestFromDefinition($requestDefinition, $params);
132 20
        $this->validateRequest($request, $requestDefinition);
133
134 18
        $response = $this->client->sendRequest($request);
135 18
        $this->validateResponse($response, $requestDefinition);
136
137 16
        return $this->getDataFromResponse(
138 16
            $response,
139 16
            $requestDefinition->getResponseDefinition(
140 16
                $response->getStatusCode()
141
            ),
142 16
            $request
143
        );
144
    }
145
146
    /**
147
     * @param string $operationId
148
     * @param array  $params
149
     *
150
     * @throws \Exception
151
     *
152
     * @return Promise
153
     *
154
     */
155 1
    public function callAsync(string $operationId, array $params = []): Promise
156
    {
157 1
        if (!$this->client instanceof HttpAsyncClient) {
158
            throw new \RuntimeException(
159
                sprintf(
160
                    '"%s" does not support async request',
161
                    get_class($this->client)
162
                )
163
            );
164
        }
165
166 1
        $requestDefinition = $this->schema->getRequestDefinition($operationId);
167 1
        $request = $this->createRequestFromDefinition($requestDefinition, $params);
168 1
        $promise = $this->client->sendAsyncRequest($request);
169
170 1
        return $promise->then(
171
            function (ResponseInterface $response) use ($request, $requestDefinition) {
172
173 1
                return $this->getDataFromResponse(
174 1
                    $response,
175 1
                    $requestDefinition->getResponseDefinition(
176 1
                        $response->getStatusCode()
177
                    ),
178 1
                    $request
179
                );
180 1
            }
181
        );
182
    }
183
184
    /**
185
     * @return UriInterface
186
     */
187 30
    private function getBaseUri(): UriInterface
188
    {
189
        // Create a base uri from the API Schema
190 30
        if (null === $this->config['baseUri']) {
191 11
            $schemes = $this->schema->getSchemes();
192 11
            if (empty($schemes)) {
193 1
                throw new \LogicException('You need to provide at least on scheme in your API Schema');
194
            }
195
196 10
            $scheme = null;
197 10
            foreach ($this->schema->getSchemes() as $candidate) {
198
                // Always prefer https
199 10
                if ('https' === $candidate) {
200 8
                    $scheme = 'https';
201
                }
202 10
                if (null === $scheme && 'http' === $candidate) {
203 2
                    $scheme = 'http';
204
                }
205
            }
206
207 10
            if (null === $scheme) {
208 1
                throw new \RuntimeException('Cannot choose a proper scheme from the API Schema. Supported: https, http');
209
            }
210
211 9
            $host = $this->schema->getHost();
212 9
            if ('' === $host) {
213 1
                throw new \LogicException('The host in the API Schema should not be null');
214
            }
215
216 8
            return $this->uriFactory->createUri($scheme.'://'.$host);
217
        } else {
218 19
            return $this->uriFactory->createUri($this->config['baseUri']);
219
        }
220
    }
221
222
    /**
223
     * @param array $config
224
     *
225
     * @throws \Assert\AssertionFailedException
226
     *
227
     * @return array
228
     */
229 34
    private function getConfig(array $config): array
230
    {
231 34
        $config = array_merge(self::DEFAULT_CONFIG, $config);
232 34
        Assertion::boolean($config['returnResponse']);
233 33
        Assertion::boolean($config['validateRequest']);
234 32
        Assertion::boolean($config['validateResponse']);
235 31
        Assertion::nullOrString($config['baseUri']);
236
237 30
        return array_intersect_key($config, self::DEFAULT_CONFIG);
238
    }
239
240
    /**
241
     * @param RequestDefinition $definition
242
     * @param array             $params
243
     *
244
     * @return RequestInterface
245
     */
246 22
    private function createRequestFromDefinition(RequestDefinition $definition, array $params): RequestInterface
247
    {
248 22
        $contentType = $definition->getContentTypes()[0];
249 22
        $requestParameters = $definition->getRequestParameters();
250 22
        list($path, $query, $headers, $body, $formData) = $this->getDefaultValues($contentType, $requestParameters);
251
252 22
        foreach ($params as $name => $value) {
253 11
            $requestParameter = $requestParameters->getByName($name);
254 11
            if (null === $requestParameter) {
255 1
                throw new \InvalidArgumentException(sprintf('%s is not a defined request parameter', $name));
256
            }
257
258 10
            switch ($requestParameter->getLocation()) {
259 10
                case 'path':
260 2
                    $path[$name] = $value;
261 2
                    break;
262 9
                case 'query':
263 6
                    $query[$name] = $value;
264 6
                    break;
265 4
                case 'header':
266 1
                    $headers[$name] = $value;
267 1
                    break;
268 3
                case 'body':
269 2
                    $body = $this->serializeRequestBody($value, $contentType);
270 2
                    break;
271 1
                case 'formData':
272 1
                    $formData[$name] = sprintf('%s=%s', $name, $value);
273 1
                    break;
274
            }
275
        }
276
277 21
        if (!empty($formData)) {
278 2
            $body = implode('&', $formData);
279
        }
280
281 21
        $request = $this->messageFactory->createRequest(
282 21
            $definition->getMethod(),
283 21
            $this->buildRequestUri($definition->getPathTemplate(), $path, $query),
284 21
            $headers,
285 21
            $body
286
        );
287
288 21
        return $request;
289
    }
290
291
    /**
292
     * @param string     $contentType
293
     * @param Parameters $requestParameters
294
     *
295
     * @return array
296
     */
297 22
    private function getDefaultValues(string $contentType, Parameters $requestParameters): array
298
    {
299 22
        $path = [];
300 22
        $query = [];
301 22
        $headers = ['Content-Type' => $contentType];
302 22
        $body = null;
303 22
        $formData = [];
304
305
        /** @var Parameter $parameter */
306 22
        foreach ($requestParameters->getIterator() as $name => $parameter) {
307 10
            if (isset($parameter->getSchema()->default)) {
308 10
                $value = $parameter->getSchema()->default;
309 10
                switch ($parameter->getLocation()) {
310 10
                    case 'path':
311 6
                        $path[$name] = $value;
312 6
                        break;
313 9
                    case 'query':
314 6
                        $query[$name] = $value;
315 6
                        break;
316 8
                    case 'header':
317 6
                        $headers[$name] = $value;
318 6
                        break;
319 2
                    case 'formData':
320 1
                        $formData[$name] = sprintf('%s=%s', $name, $value);
321 1
                        break;
322 1
                    case 'body':
323 1
                        $body = $this->serializeRequestBody($value, $contentType);
324 1
                        break;
325
                }
326
            }
327
        }
328
329 22
        return [$path, $query, $headers, $body, $formData];
330
    }
331
332
333
    /**
334
     * @param string $pathTemplate    A template path
335
     * @param array  $pathParameters  Path parameters
336
     * @param array  $queryParameters Query parameters
337
     *
338
     * @return UriInterface
339
     */
340 21
    private function buildRequestUri(string $pathTemplate, array $pathParameters, array $queryParameters): UriInterface
341
    {
342 21
        $path = $this->uriTemplate->expand($pathTemplate, $pathParameters);
343 21
        $query = http_build_query($queryParameters);
344
345 21
        return $this->baseUri->withPath($path)->withQuery($query);
346
    }
347
348
    /**
349
     * @param array  $decodedBody
350
     * @param string $contentType
351
     *
352
     * @return string
353
     */
354 3
    private function serializeRequestBody(array $decodedBody, string $contentType): string
355
    {
356 3
        return $this->serializer->serialize(
357 3
            $decodedBody,
358 3
            DecoderUtils::extractFormatFromContentType($contentType)
359
        );
360
    }
361
362
    /**
363
     * @param ResponseInterface  $response
364
     * @param ResponseDefinition $definition
365
     * @param RequestInterface   $request
366
     *
367
     * @return ResourceInterface|ResponseInterface|array|object
368
     */
369 17
    private function getDataFromResponse(ResponseInterface $response, ResponseDefinition $definition, RequestInterface $request)
370
    {
371 17
        if (true === $this->config['returnResponse']) {
372 1
            return $response;
373
        }
374
375
        // @todo Find a better way to handle responses with a body definition
376 16
        if (!$definition->hasBodySchema()) {
377 11
            return new Item([], $request->getHeaders(), []);
378
        }
379
380 5
        return $this->serializer->deserialize(
381 5
            (string) $response->getBody(),
382 5
            ResourceInterface::class,
383 5
            DecoderUtils::extractFormatFromContentType($response->getHeaderLine('Content-Type')),
384
            [
385 5
                'response' => $response,
386 5
                'responseDefinition' => $definition,
387 5
                'request' => $request,
388
            ]
389
        );
390
    }
391
392
    /**
393
     * @param RequestInterface  $request
394
     * @param RequestDefinition $definition
395
     *
396
     * @throws ConstraintViolations
397
     */
398 20
    private function validateRequest(RequestInterface $request, RequestDefinition $definition)
399
    {
400 20
        if (false === $this->config['validateRequest']) {
401 2
            return;
402
        }
403
404 18
        $this->messageValidator->validateRequest($request, $definition);
405 18
        if ($this->messageValidator->hasViolations()) {
406 2
            throw new RequestViolations(
407 2
                $this->messageValidator->getViolations()
408
            );
409
        }
410 16
    }
411
412
    /**
413
     * @param ResponseInterface $response
414
     * @param RequestDefinition $definition
415
     *
416
     * @throws ConstraintViolations
417
     */
418 18
    private function validateResponse(ResponseInterface $response, RequestDefinition $definition)
419
    {
420 18
        if (false === $this->config['validateResponse']) {
421 10
            return;
422
        }
423
424 8
        $this->messageValidator->validateResponse($response, $definition);
425 8
        if ($this->messageValidator->hasViolations()) {
426 2
            throw new ResponseViolations(
427 2
                $this->messageValidator->getViolations()
428
            );
429
        }
430 6
    }
431
}
432