ApiService::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

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

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