Completed
Pull Request — master (#13)
by
unknown
14:29
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
                    $path[$name] = $parameter->getSchema()->default;
292
                    break;
293
                case 'query':
294
                    $query[$name] = $parameter->getSchema()->default;
295
                    break;
296
                case 'header':
297
                    $headers[$name] = $parameter->getSchema()->default;
298
                    break;
299
                case 'formData':
300 6
                    $formData[$name] = sprintf('%s=%s', $name, $parameter->getSchema()->default);
301
                    break;
302 6
                case 'body':
303 6
                    $body = array_filter(array_map(function (array $params) {
304
                        return $params['default'] ?? null;
305 6
                    }, json_decode(json_encode($parameter->getSchema()->properties), true)));
306
                    break;
307
            }
308
        }
309
310
        return [$path, $query, $headers, $body, $formData];
311
    }
312
313
    private function buildRequestUri(string $pathTemplate, array $pathParameters, array $queryParameters): UriInterface
314 1
    {
315
        $path = $this->uriTemplate->expand($pathTemplate, $pathParameters);
316 1
        $query = http_build_query($queryParameters);
317 1
318 1
        return $this->baseUri->withPath($path)->withQuery($query);
319
    }
320
321
    private function serializeRequestBody(array $decodedBody, string $contentType): string
322
    {
323
        return $this->serializer->serialize(
324
            $decodedBody,
325
            DecoderUtils::extractFormatFromContentType($contentType)
326
        );
327
    }
328
329
    private function getDataFromResponse(ResponseInterface $response, ResponseDefinition $definition, RequestInterface $request)
330
    {
331
        if (true === $this->config['returnResponse']) {
332 4
            return $response;
333
        }
334
335
        // @todo Find a better way to handle responses with a body definition
336
        if (!$definition->hasBodySchema()) {
337 4
            return new Item([], $request->getHeaders(), []);
338
        }
339
        $statusCode = $response->getStatusCode();
340
341
        return $this->serializer->deserialize(
342 4
            (string) $response->getBody(),
343 1
            $statusCode >= 400 && $statusCode <= 599 ? ErrorInterface::class : ResourceInterface::class,
344
            DecoderUtils::extractFormatFromContentType($response->getHeaderLine('Content-Type')),
345
            [
346 3
                'response' => $response,
347 3
                'responseDefinition' => $definition,
348 3
                'request' => $request,
349 3
            ]
350
        );
351 3
    }
352 3
353 3
    private function validateRequest(RequestInterface $request, RequestDefinition $definition)
354
    {
355
        if (false === $this->config['validateRequest']) {
356
            return;
357
        }
358
359
        $this->messageValidator->validateRequest($request, $definition);
360
        if ($this->messageValidator->hasViolations()) {
361
            throw new RequestViolations(
362
                $this->messageValidator->getViolations()
363
            );
364
        }
365
    }
366
367 5
    private function validateResponse(ResponseInterface $response, RequestDefinition $definition)
368
    {
369 5
        if (false === $this->config['validateResponse']) {
370 1
            return;
371
        }
372
373 4
        $this->messageValidator->validateResponse($response, $definition);
374 4
        if ($this->messageValidator->hasViolations()) {
375 1
            throw new ResponseViolations(
376 1
                $this->messageValidator->getViolations()
377
            );
378
        }
379 3
    }
380
}
381