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

Api::getBaseRoute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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