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