Passed
Push — main ( 19d97c...3a0d8d )
by Daryl
02:38
created

Api   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 426
Duplicated Lines 0 %

Test Coverage

Coverage 89.66%

Importance

Changes 10
Bugs 0 Features 0
Metric Value
eloc 120
c 10
b 0
f 0
dl 0
loc 426
ccs 130
cts 145
cp 0.8966
rs 8.96
wmc 43

29 Methods

Rating   Name   Duplication   Size   Complexity  
A setClient() 0 3 1
A logger() 0 3 1
A baseRoute() 0 3 1
A setLogger() 0 3 1
A setCache() 0 3 1
A setMachine() 0 3 1
A getClient() 0 3 1
A setVersion() 0 3 1
A getMachine() 0 3 1
A getPassword() 0 3 1
A setPassword() 0 3 1
A getLogger() 0 3 1
A getBaseRoute() 0 3 1
A setUsergroup() 0 3 1
A getCache() 0 3 1
A setBaseRoute() 0 3 1
A getUsergroup() 0 3 1
A setUsername() 0 3 1
A generateCacheKey() 0 15 1
A getUsername() 0 3 1
A logEvent() 0 11 2
A getRequestArgs() 0 28 3
A __construct() 0 33 5
A getVersion() 0 3 1
A get() 0 7 1
A getUri() 0 4 1
A post() 0 8 1
B makeRequest() 0 50 8
A getAuthorizationHeaderValue() 0 12 1

How to fix   Complexity   

Complex Class

Complex classes like Api often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Api, and based on these observations, apply Extract Interface, too.

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