Completed
Pull Request — master (#13)
by
unknown
13:46
created

ApiService::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
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 19
ccs 8
cts 8
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
    public function __construct(
94
        UriFactory $uriFactory,
95 13
        UriTemplate $uriTemplate,
96
        HttpClient $client,
97
        MessageFactory $messageFactory,
98
        Schema $schema,
99
        MessageValidator $messageValidator,
100
        SerializerInterface $serializer,
101
        array $config = []
102
    ) {
103
        $this->uriFactory = $uriFactory;
104
        $this->uriTemplate = $uriTemplate;
105 13
        $this->schema = $schema;
106 13
        $this->messageValidator = $messageValidator;
107 13
        $this->client = $client;
108 13
        $this->messageFactory = $messageFactory;
109 13
        $this->serializer = $serializer;
110 13
        $this->config = $this->getConfig($config);
111 13
        $this->baseUri = $this->getBaseUri();
112 13
    }
113 13
114 10
    public function call(string $operationId, array $params = [])
115
    {
116
        $requestDefinition = $this->schema->getRequestDefinition($operationId);
117
        $request = $this->createRequestFromDefinition($requestDefinition, $params);
118
        $this->validateRequest($request, $requestDefinition);
119
120
        $response = $this->client->sendRequest($request);
121
        $this->validateResponse($response, $requestDefinition);
122
123
        return $this->getDataFromResponse(
124 5
            $response,
125
            $requestDefinition->getResponseDefinition(
126 5
                $response->getStatusCode()
127 5
            ),
128 5
            $request
129
        );
130 4
    }
131 4
132
    public function callAsync(string $operationId, array $params = []): Promise
133 3
    {
134 3
        if (!$this->client instanceof HttpAsyncClient) {
135 3
            throw new \RuntimeException(
136 3
                sprintf(
137
                    '"%s" does not support async request',
138 3
                    get_class($this->client)
139
                )
140
            );
141 3
        }
142
143
        $requestDefinition = $this->schema->getRequestDefinition($operationId);
144
        $request = $this->createRequestFromDefinition($requestDefinition, $params);
145
        $promise = $this->client->sendAsyncRequest($request);
146
147
        return $promise->then(
148
            function (ResponseInterface $response) use ($request, $requestDefinition) {
149
150
                return $this->getDataFromResponse(
151
                    $response,
152 1
                    $requestDefinition->getResponseDefinition(
153
                        $response->getStatusCode()
154 1
                    ),
155
                    $request
156
                );
157
            }
158
        );
159
    }
160
161
    public function getSchema(): Schema
162
    {
163 1
        return $this->schema;
164 1
    }
165 1
166
    public static function buildQuery(array $params): array
167 1
    {
168
        foreach ($params as $key => $param) {
169
            if (is_array($param)) {
170 1
                foreach ($param as $k => $v) {
171 1
                    $params[$key.'['.$k.']'] = $v;
172 1
                }
173 1
                unset($params[$key]);
174
            }
175 1
            if (false !== stripos($key, '_')) {
176
                $params[str_replace('_', '.', $key)] = $param;
177 1
                unset($params[$key]);
178
            }
179
        }
180
181
        return $params;
182
    }
183
184
    private function getBaseUri(): UriInterface
185
    {
186
        // Create a base uri from the API Schema
187 13
        if (null === $this->config['baseUri']) {
188
            $schemes = $this->schema->getSchemes();
189
            if (empty($schemes)) {
190 13
                throw new \LogicException('You need to provide at least on scheme in your API Schema');
191 10
            }
192 10
193 10
            $scheme = null;
194 1
            foreach ($this->schema->getSchemes() as $candidate) {
195
                // Always prefer https
196
                if ('https' === $candidate) {
197 9
                    $scheme = 'https';
198
                }
199 9
                if (null === $scheme && 'http' === $candidate) {
200 8
                    $scheme = 'http';
201
                }
202 9
            }
203 9
204
            if (null === $scheme) {
205
                throw new \RuntimeException('Cannot choose a proper scheme from the API Schema. Supported: https, http');
206 9
            }
207 1
208
            $host = $this->schema->getHost();
209
            if ('' === $host) {
210 8
                throw new \LogicException('The host in the API Schema should not be null');
211 8
            }
212 1
213
            return $this->uriFactory->createUri($scheme.'://'.$host);
214
        } else {
215 7
            return $this->uriFactory->createUri($this->config['baseUri']);
216
        }
217 3
    }
218
219
    private function getConfig(array $config): array
220
    {
221
        $config = array_merge(self::DEFAULT_CONFIG, $config);
222
        Assertion::boolean($config['returnResponse']);
223
        Assertion::boolean($config['validateRequest']);
224 13
        Assertion::boolean($config['validateResponse']);
225
        Assertion::nullOrString($config['baseUri']);
226 13
227 13
        return array_intersect_key($config, self::DEFAULT_CONFIG);
228 13
    }
229 13
230 13
    private function createRequestFromDefinition(RequestDefinition $definition, array $params): RequestInterface
231
    {
232 13
        $contentType = $definition->getContentTypes()[0] ?? 'application/json';
233
        $requestParameters = $definition->getRequestParameters();
234
        list($path, $query, $headers, $body, $formData) = $this->getDefaultValues($requestParameters);
235
        $headers = array_merge(
236
            $headers,
237
            ['Content-Type' => $contentType, 'Accept' => $definition->getAccepts()[0] ?? 'application/json']
0 ignored issues
show
Bug introduced by
The method getAccepts() does not exist on ElevenLabs\Api\Definition\RequestDefinition. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

237
            ['Content-Type' => $contentType, 'Accept' => $definition->/** @scrutinizer ignore-call */ getAccepts()[0] ?? 'application/json']

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
238
        );
239
240
        foreach ($params as $name => $value) {
241
            $requestParameter = $requestParameters->getByName($name);
242
            if (null === $requestParameter) {
243
                throw new \InvalidArgumentException(sprintf('%s is not a defined request parameter', $name));
244
            }
245 6
246
            switch ($requestParameter->getLocation()) {
247 6
                case 'path':
248 6
                    $path[$name] = $value;
249 6
                    break;
250 6
                case 'query':
251 6
                    $query[$name] = $value;
252 6
                    break;
253
                case 'header':
254 6
                    $headers[$name] = $value;
255 2
                    break;
256 2
                case 'body':
257
                    $body = $this->serializeRequestBody(array_merge($body ?? [], $value), $contentType);
258
                    break;
259
                case 'formData':
260 2
                    $formData[$name] = sprintf('%s=%s', $name, $value);
261 2
                    break;
262 1
            }
263 1
        }
264 2
265 2
        if (!empty($formData)) {
266 2
            $body = implode('&', $formData);
267 1
        }
268
269
        $request = $this->messageFactory->createRequest(
270 1
            $definition->getMethod(),
271 2
            $this->buildRequestUri($definition->getPathTemplate(), $path, $query),
272
            $headers,
273
            $body
274
        );
275 6
276 6
        return $request;
277 6
    }
278 6
279 6
    private function getDefaultValues(Parameters $requestParameters): array
280
    {
281
        $path = [];
282 6
        $query = [];
283
        $headers = [];
284
        $body = null;
285
        $formData = [];
286
287
        /** @var Parameter $parameter */
288
        foreach ($requestParameters->getIterator() as $name => $parameter) {
289
            switch ($parameter->getLocation()) {
290
                case 'path':
291
                    if (!empty($parameter->getSchema()->default)) {
292
                        $path[$name] = $parameter->getSchema()->default;
293
                    }
294
                    break;
295
                case 'query':
296
                    if (!empty($parameter->getSchema()->default)) {
297
                        $query[$name] = $parameter->getSchema()->default;
298
                    }
299
                    break;
300 6
                case 'header':
301
                    if (!empty($parameter->getSchema()->default)) {
302 6
                        $headers[$name] = $parameter->getSchema()->default;
303 6
                    }
304
                    break;
305 6
                case 'formData':
306
                    if (!empty($parameter->getSchema()->default)) {
307
                        $formData[$name] = sprintf('%s=%s', $name, $parameter->getSchema()->default);
308
                    }
309
                    break;
310
                case 'body':
311
                    if (!empty($parameter->getSchema()->properties)) {
312
                        $body = array_filter(array_map(function (array $params) {
313
                            return $params['default'] ?? null;
314 1
                        }, json_decode(json_encode($parameter->getSchema()->properties), true)));
315
                    }
316 1
                    break;
317 1
            }
318 1
        }
319
320
        return [$path, $query, $headers, $body, $formData];
321
    }
322
323
    private function buildRequestUri(string $pathTemplate, array $pathParameters, array $queryParameters): UriInterface
324
    {
325
        $path = $this->uriTemplate->expand($pathTemplate, $pathParameters);
326
        $query = http_build_query($queryParameters);
327
328
        return $this->baseUri->withPath($path)->withQuery($query);
329
    }
330
331
    private function serializeRequestBody(array $decodedBody, string $contentType): string
332 4
    {
333
        return $this->serializer->serialize(
334
            $decodedBody,
335
            DecoderUtils::extractFormatFromContentType($contentType)
336
        );
337 4
    }
338
339
    private function getDataFromResponse(ResponseInterface $response, ResponseDefinition $definition, RequestInterface $request)
340
    {
341
        if (true === $this->config['returnResponse']) {
342 4
            return $response;
343 1
        }
344
345
        // @todo Find a better way to handle responses with a body definition
346 3
        if (!$definition->hasBodySchema()) {
347 3
            return new Item([], $request->getHeaders(), []);
348 3
        }
349 3
        $statusCode = $response->getStatusCode();
350
351 3
        return $this->serializer->deserialize(
352 3
            (string) $response->getBody(),
353 3
            $statusCode >= 400 && $statusCode <= 599 ? ErrorInterface::class : ResourceInterface::class,
354
            DecoderUtils::extractFormatFromContentType($response->getHeaderLine('Content-Type')),
355
            [
356
                'response' => $response,
357
                'responseDefinition' => $definition,
358
                'request' => $request,
359
            ]
360
        );
361
    }
362
363
    private function validateRequest(RequestInterface $request, RequestDefinition $definition)
364
    {
365
        if (false === $this->config['validateRequest']) {
366
            return;
367 5
        }
368
369 5
        $this->messageValidator->validateRequest($request, $definition);
370 1
        if ($this->messageValidator->hasViolations()) {
371
            throw new RequestViolations(
372
                $this->messageValidator->getViolations()
373 4
            );
374 4
        }
375 1
    }
376 1
377
    private function validateResponse(ResponseInterface $response, RequestDefinition $definition)
378
    {
379 3
        if (false === $this->config['validateResponse']) {
380
            return;
381
        }
382
383
        $this->messageValidator->validateResponse($response, $definition);
384
        if ($this->messageValidator->hasViolations()) {
385
            throw new ResponseViolations(
386
                $this->messageValidator->getViolations()
387
            );
388
        }
389
    }
390
}
391