Passed
Push — main ( f06863...394e0a )
by Daryl
02:56
created

Api::logger()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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