Passed
Pull Request — main (#7)
by Daryl
02:27
created

Api::makeRequest()   B

Complexity

Conditions 8
Paths 13

Size

Total Lines 53
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 8.4551

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 8
eloc 25
c 3
b 0
f 0
nc 13
nop 2
dl 0
loc 53
ccs 21
cts 26
cp 0.8077
crap 8.4551
rs 8.4444

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Clubdeuce\Tessitura\Helpers;
4
5
use Clubdeuce\Tessitura\Base\Base;
6
use Clubdeuce\Tessitura\Interfaces\ApiInterface;
7
use Clubdeuce\Tessitura\Interfaces\CacheInterface;
8
use Clubdeuce\Tessitura\Interfaces\LoggerAwareInterface;
9
use Exception;
10
use GuzzleHttp\Client;
11
use GuzzleHttp\ClientInterface;
12
use GuzzleHttp\Exception\GuzzleException;
13
use GuzzleHttp\Psr7\Response;
14
use GuzzleHttp\Psr7\Stream;
15
use Psr\Log\LoggerInterface;
16
17
/**
18
 * Class API
19
 * @package Clubdeuce\Tessitura\Helpers
20
 */
21
class Api extends Base implements 
22
    ApiInterface,
23
    LoggerAwareInterface
24
{
25
26
    const CACHE_EXPIRATION_DEFAULT = 10 * 60; // 10 minutes
27
28
    /**
29
     * @var string The base path to the Tessitura API
30
     */
31
    protected string $_base_route = '';
32
33
    /**
34
     * @var string The machine name required for authentication.
35
     */
36
    protected string $_machine;
37
38
    /**
39
     * @var string The password required for authentication.
40
     */
41
    protected string $_password;
42
43
    /**
44
     * @var string The username required for authentication.
45
     */
46
    protected string $_username;
47
48
    /**
49
     * @var string The usergroup required for authentication
50
     */
51
    protected string $_usergroup;
52
53
    /**
54
     * @var string The Tessitura API version to use with this library
55
     */
56
    protected string $_version = '15';
57
58
    /**
59
     * @var ClientInterface GuzzleHttp client
60
     */
61
    protected ClientInterface $_client;
62
63
    /**
64
     * @var LoggerInterface|null
65
     */
66
    protected ?LoggerInterface $_logger = null;
67
68
    /**
69
     * @var CacheInterface|null
70
     */
71
    protected ?CacheInterface $_cache = null;
72
73
    /**
74
     * API constructor.
75
     *
76
     * @param mixed[] $args {
77
     * @type string   $base_route
78
     * @type string   $location
79
     * @type string   $password
80
     * @type string   $usergroup
81
     * @type string   $username
82
     * @type string   $version
83
     * }
84
     * @param ClientInterface|null $client The HTTP client to use for API requests
85
     * @param LoggerInterface|null $logger The logger to use for logging
86
     * @param CacheInterface|null $cache The cache implementation to use for caching API responses
87
     */
88 21
    public function __construct(
89
        array $args = [],
90
        ?ClientInterface $client = null,
91
        ?LoggerInterface $logger = null,
92
        ?CacheInterface $cache = null
93
    ) {
94 21
        $args = $this->parseArgs($args, array(
95 21
            'baseRoute' => '',
96 21
            'machine' => '',
97 21
            'password' => '',
98 21
            'usergroup' => '',
99 21
            'username' => '',
100 21
            'version' => '16',
101 21
        ));
102
103 21
        if ($logger) {
104
            $this->setLogger($logger);
105
        }
106
107 21
        if ($cache) {
108 5
            $this->setCache($cache);
109
        }
110
111 21
        if (!$client && !empty($args['baseRoute'])) {
112 3
            $client = new Client([
113 3
                'base_uri' => $args['baseRoute'],
114 3
                'timeout' => 10.0,
115 3
            ]);
116
        }
117
118 21
        $args['client'] = $client;
119
120 21
        parent::__construct($args);
121
122
    }
123
124
    /**
125
     * @param string $resource
126
     * @param mixed[] $args
127
     * @return mixed
128
     * @throws Exception
129
     */
130 6
    public function get(string $resource, array $args = array()): mixed
131
    {
132 6
        $args = array_merge($args, array(
133 6
            'method' => 'GET',
134 6
        ));
135
136 6
        return $this->makeRequest($resource, $args);
137
    }
138
139
    /**
140
     * @param string  $endpoint
141
     * @param mixed[] $args
142
     * @return mixed
143
     * @throws Exception|GuzzleException
144
     */
145 7
    protected function makeRequest(string $endpoint, array $args): array
146
    {
147 7
        $args = $this->parseArgs($args, [
148 7
            'method' => 'GET',
149 7
        ]);
150
151 7
        $method = $args['method'];
152
        
153
        // Only cache GET requests
154 7
        if ($method === 'GET' && $this->_cache) {
155 4
            $cacheKey = $this->generateCacheKey($endpoint, $args);
156 4
            $cachedResponse = $this->_cache->get($cacheKey);
157
            
158 4
            if ($cachedResponse !== null) {
159 2
                $this->logEvent('Cache hit for endpoint: ' . $endpoint);
160 2
                return $cachedResponse;
161
            }
162
        }
163
164
        /**
165
         * @var Response $response
166
         */
167
        // Use the appropriate HTTP method
168 6
        if ($method === 'POST') {
169 1
            $response = $this->_client->post($this->getUri($endpoint), $args);
170
        } else {
171 5
            $response = $this->_client->get($this->getUri($endpoint), $args);
172
        }
173
174 5
        if (200 === $response->getStatusCode()) {
175 5
            $data = json_decode($response->getBody(), true);
176
            
177
            // Cache successful GET responses
178 5
            if ($method === 'GET' && $this->_cache) {
179 3
                $cacheExpiration = $args['cache_expiration'] ?? self::CACHE_EXPIRATION_DEFAULT;
180 3
                $this->_cache->set($cacheKey, $data, $cacheExpiration);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cacheKey does not seem to be defined for all execution paths leading up to this point.
Loading history...
181 3
                $this->logEvent('Cached response for endpoint: ' . $endpoint);
182
            }
183
            
184 5
            return $data;
185
        }
186
187
        // We have successfully gotten a response from the API, but not a 200 status code.
188
        /**
189
         * @var Stream $body
190
         */
191
        $body = $response->getBody();
192
193
        $this->logEvent("Error response from endpoint: {$endpoint}. {$body->getContents()}");
194
195
        throw new Exception(
196
            $body->getContents(),
197
            $response->getStatusCode()
198
        );
199
    }
200
201 6
    public function getVersion(): string
202
    {
203 6
        return $this->_version;
204
    }
205
206
    /**
207
     * @param mixed[] $args
208
     *
209
     * @return mixed[] {
210
     * @type int $cache_expiration
211
     * @type int $timeout
212
     * @type array $headers
213
     * }
214
     */
215 1
    protected function getRequestArgs(array $args = []): array
216
    {
217
218 1
        $args = $this->parseArgs($args, array(
219 1
            'cache_expiration' => self::CACHE_EXPIRATION_DEFAULT,
220 1
            'headers'          => [],
221 1
            'body'             => '',
222 1
            'timeout'          => 10.0,
223 1
        ));
224
225 1
        if (is_array($args['body'])) {
226
            if (empty($args['body'])) {
227
                $args['body'] = null;
228
            } else {
229
                $args['body'] = json_encode($args['body']);
230
            }
231
        }
232
233 1
        $parsedUrl = parse_url($this->baseRoute());
234 1
        $args['headers'] = $this->parseArgs($args['headers'], array(
235 1
            'Authorization' => $this->getAuthorizationHeaderValue(),
236 1
            'Content-Type'   => 'application/json',
237 1
            'Content-Length' => strlen($args['body']),
238 1
            'Accept'         => 'application/json',
239 1
            'Host'           => $parsedUrl['host'] ?? $this->baseRoute(),
240 1
        ));
241
242 1
        return array_filter($args);
243
244
    }
245
246
    /**
247
     * @return string
248
     */
249 2
    protected function getAuthorizationHeaderValue(): string
250
    {
251
252 2
        $auth_key = sprintf('%1$s:%2$s:%3$s:%4$s', $this->getUsername(), $this->getUsergroup(), $this->getMachine(), $this->getPassword());
253
254 2
        return sprintf('Basic %1$s', base64_encode($auth_key));
255
256
    }
257
258
    /**
259
     * @param string $endpoint
260
     *
261
     * @return string
262
     */
263 7
    protected function getUri(string $endpoint): string
264
    {
265
266 7
        return "{$this->baseRoute()}/{$endpoint}";
267
268
    }
269
270
    /**
271
     * @param string $endpoint
272
     * @param mixed[] $args
273
     * @return Exception|mixed[]
274
     * @throws GuzzleException
275
     */
276 1
    public function post(string $endpoint, array $args = []): array|Exception
277
    {
278
279 1
        $args = array_merge($args, array(
280 1
            'method' => 'POST',
281 1
        ));
282
283 1
        return $this->makeRequest($endpoint, $args);
284
285
    }
286
287
    /**
288
     * @param string $message
289
     * @param mixed[] $args {
290
     * @type string $file
291
     * @type string $line
292
     * @type string $function
293
     * @type array $trace
294
     * @type mixed[] $extra
295
     * }
296
     */
297 4
    protected function logEvent(string $message, array $args = []): void
298
    {
299
300 4
        $args = $this->parseArgs($args, array(
301 4
            'log' => 'tessitura',
302 4
        ));
303
304 4
        $message = 'Tessitura API: ' . $message;
305
306 4
        if ($this->getLogger()) {
307
            $this->getLogger()->info($message, $args);
308
        }
309
310
    }
311
312
    /**
313
     * Sets a logger instance on the object.
314
     *
315
     * @param LoggerInterface $logger The logger instance to use.
316
     * @return void
317
     */
318 1
    public function setLogger(LoggerInterface $logger): void
319
    {
320 1
        $this->_logger = $logger;
321
    }
322
323
    /**
324
     * Gets the logger instance.
325
     *
326
     * @return LoggerInterface|null The logger instance or null if none is set.
327
     */
328 5
    public function getLogger(): ?LoggerInterface
329
    {
330 5
        return $this->_logger;
331
    }
332
333
    public function logger(): ?LoggerInterface
334
    {
335
        return $this->getLogger();
336
    }
337
338
    public function setClient(ClientInterface $client): void
339
    {
340
        $this->_client = $client;
341
    }
342
343 1
    public function getClient(): ClientInterface
344
    {
345 1
        return $this->_client;
346
    }
347
348 10
    public function baseRoute(): string
349
    {
350 10
        return $this->_base_route;
351
    }
352
353 1
    public function setBaseRoute(string $baseRoute): void
354
    {
355 1
        $this->_base_route = $baseRoute;
356
    }
357
358 1
    public function setMachine(string $machine): void
359
    {
360 1
        $this->_machine = $machine;
361
    }
362
363 1
    public function setPassword(string $password): void
364
    {
365 1
        $this->_password = $password;
366
    }
367
368 1
    public function setUsername(string $username): void
369
    {
370 1
        $this->_username = $username;
371
    }
372
373 1
    public function setUsergroup(string $usergroup): void
374
    {
375 1
        $this->_usergroup = $usergroup;
376
    }
377
378 1
    public function setVersion(string $version): void
379
    {
380 1
        $this->_version = $version;
381
    }
382
383 1
    public function getBaseRoute(): string
384
    {
385 1
        return $this->_base_route;
386
    }
387
388 3
    public function getMachine(): string
389
    {
390 3
        return $this->_machine;
391
    }
392
393 3
    public function getPassword(): string
394
    {
395 3
        return $this->_password;
396
    }
397
398 4
    public function getUsergroup(): string
399
    {
400 4
        return $this->_usergroup;
401
    }
402
403 3
    public function getUsername(): string
404
    {
405 3
        return $this->_username;
406
    }
407
408
    /**
409
     * Sets a cache instance on the object.
410
     *
411
     * @param CacheInterface $cache The cache instance to use.
412
     * @return void
413
     */
414 6
    public function setCache(CacheInterface $cache): void
415
    {
416 6
        $this->_cache = $cache;
417
    }
418
419
    /**
420
     * Gets the cache instance.
421
     *
422
     * @return CacheInterface|null The cache instance or null if none is set.
423
     */
424 1
    public function getCache(): ?CacheInterface
425
    {
426 1
        return $this->_cache;
427
    }
428
429
    /**
430
     * Generate a cache key for the given endpoint and arguments.
431
     *
432
     * @param string $endpoint The API endpoint
433
     * @param array $args The request arguments
434
     * @return string The generated cache key
435
     */
436 5
    protected function generateCacheKey(string $endpoint, array $args): string
437
    {
438
        // Remove method and cache-specific args from key generation
439 5
        $keyArgs = $args;
440 5
        unset($keyArgs['method'], $keyArgs['cache_expiration']);
441
        
442
        // Create a consistent key based on endpoint and args
443 5
        $keyData = [
444 5
            'endpoint' => $endpoint,
445 5
            'base_route' => $this->baseRoute(),
446 5
            'version' => $this->getVersion(),
447 5
            'args' => $keyArgs
448 5
        ];
449
        
450 5
        return 'tessitura:' . md5(json_encode($keyData));
451
    }
452
}
453