Passed
Pull Request — main (#7)
by
unknown
02:23
created

Api::getRequestArgs()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 3.0303

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 18
nc 3
nop 1
dl 0
loc 28
ccs 17
cts 20
cp 0.85
crap 3.0303
rs 9.6666
c 1
b 0
f 0
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
        $method = $args['method'] ?? 'GET';
148
        
149
        // Only cache GET requests
150 7
        if ($method === 'GET' && $this->_cache) {
151 4
            $cacheKey = $this->generateCacheKey($endpoint, $args);
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
        /**
161
         * @var Response $response
162
         */
163
        // Use the appropriate HTTP method
164 6
        if ($method === 'POST') {
165 1
            $response = $this->_client->post($this->getUri($endpoint), $args);
166
        } else {
167 5
            $response = $this->_client->get($this->getUri($endpoint), $args);
168
        }
169
170 5
        if (200 === $response->getStatusCode()) {
171 5
            $data = json_decode($response->getBody(), true);
172
            
173
            // Cache successful GET responses
174 5
            if ($method === 'GET' && $this->_cache) {
175 3
                $cacheExpiration = $args['cache_expiration'] ?? self::CACHE_EXPIRATION_DEFAULT;
176 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...
177 3
                $this->logEvent('Cached response for endpoint: ' . $endpoint);
178
            }
179
            
180 5
            return $data;
181
        }
182
183
        // We have successfully gotten a response from the API, but not a 200 status code.
184
        /**
185
         * @var Stream $body
186
         */
187
        $body = $response->getBody();
188
189
        throw new Exception(
190
            $body->getContents(),
191
            $response->getStatusCode()
192
        );
193
    }
194
195 6
    public function getVersion(): string
196
    {
197 6
        return $this->_version;
198
    }
199
200
    /**
201
     * @param mixed[] $args
202
     *
203
     * @return mixed[] {
204
     * @type int $cache_expiration
205
     * @type int $timeout
206
     * @type array $headers
207
     * }
208
     */
209 1
    protected function getRequestArgs(array $args = []): array
210
    {
211
212 1
        $args = $this->parseArgs($args, array(
213 1
            'cache_expiration' => self::CACHE_EXPIRATION_DEFAULT,
214 1
            'headers'          => [],
215 1
            'body'             => '',
216 1
            'timeout'          => 10.0,
217 1
        ));
218
219 1
        if (is_array($args['body'])) {
220
            if (empty($args['body'])) {
221
                $args['body'] = null;
222
            } else {
223
                $args['body'] = json_encode($args['body']);
224
            }
225
        }
226
227 1
        $parsedUrl = parse_url($this->baseRoute());
228 1
        $args['headers'] = $this->parseArgs($args['headers'], array(
229 1
            'Authorization' => $this->getAuthorizationHeaderValue(),
230 1
            'Content-Type'   => 'application/json',
231 1
            'Content-Length' => strlen($args['body']),
232 1
            'Accept'         => 'application/json',
233 1
            'Host'           => $parsedUrl['host'] ?? $this->baseRoute(),
234 1
        ));
235
236 1
        return array_filter($args);
237
238
    }
239
240
    /**
241
     * @return string
242
     */
243 2
    protected function getAuthorizationHeaderValue(): string
244
    {
245
246 2
        $auth_key = sprintf('%1$s:%2$s:%3$s:%4$s', $this->getUsername(), $this->getUsergroup(), $this->getMachine(), $this->getPassword());
247
248 2
        return sprintf('Basic %1$s', base64_encode($auth_key));
249
250
    }
251
252
    /**
253
     * @param string $endpoint
254
     *
255
     * @return string
256
     */
257 7
    protected function getUri(string $endpoint): string
258
    {
259
260 7
        return "{$this->baseRoute()}/{$endpoint}";
261
262
    }
263
264
    /**
265
     * @param string $endpoint
266
     * @param mixed[] $args
267
     * @return Exception|mixed[]
268
     * @throws GuzzleException
269
     */
270 1
    public function post(string $endpoint, array $args = []): array|Exception
271
    {
272
273 1
        $args = array_merge($args, array(
274 1
            'method' => 'POST',
275 1
        ));
276
277 1
        return $this->makeRequest($endpoint, $args);
278
279
    }
280
281
    /**
282
     * @param string $message
283
     * @param mixed[] $args {
284
     * @type string $file
285
     * @type string $line
286
     * @type string $function
287
     * @type array $trace
288
     * @type mixed[] $extra
289
     * }
290
     */
291 4
    protected function logEvent(string $message, array $args = []): void
292
    {
293
294 4
        $args = $this->parseArgs($args, array(
295 4
            'log' => 'tessitura',
296 4
        ));
297
298 4
        $message = 'Tessitura API: ' . $message;
299
300 4
        if ($this->getLogger()) {
301
            $this->getLogger()->info($message, $args);
302
        }
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(ClientInterface $client): void
333
    {
334
        $this->_client = $client;
335
    }
336
337 1
    public function getClient(): ClientInterface
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 array $args The request arguments
428
     * @return string The generated cache key
429
     */
430 5
    protected function generateCacheKey(string $endpoint, array $args): string
431
    {
432
        // Remove method and cache-specific args from key generation
433 5
        $keyArgs = $args;
434 5
        unset($keyArgs['method'], $keyArgs['cache_expiration']);
435
        
436
        // Create a consistent key based on endpoint and args
437 5
        $keyData = [
438 5
            'endpoint' => $endpoint,
439 5
            'base_route' => $this->baseRoute(),
440 5
            'version' => $this->getVersion(),
441 5
            'args' => $keyArgs
442 5
        ];
443
        
444 5
        return 'tessitura:' . md5(json_encode($keyData));
445
    }
446
}
447