Transitioned from obsolete gateway object classes (`edge_gateway_shell_action_jobs_o`, `edge_gateway_shell_events_o`, `edge_gateway_shell_sessions_o`, `edge_gateway_update_jobs_o`) to the new agent implementation (`edge-gateway-agent/agent.php`).
202 lines
4.9 KiB
PHP
202 lines
4.9 KiB
PHP
<?php
|
|
|
|
namespace classes;
|
|
|
|
use Throwable;
|
|
|
|
class order_bookings_counts_cache
|
|
{
|
|
public const PREFIX = 'order_bookings:counts:v1:';
|
|
|
|
/**
|
|
* Optional runtime adapter for tests.
|
|
*/
|
|
private static ?object $adapter = null;
|
|
|
|
public static function setAdapterForTests(?object $adapter): void
|
|
{
|
|
self::$adapter = $adapter;
|
|
}
|
|
|
|
public static function getTtl(): int
|
|
{
|
|
$raw = getenv('ORDER_BOOKINGS_COUNTS_CACHE_TTL');
|
|
if ($raw === false || trim((string)$raw) === '') {
|
|
return 30;
|
|
}
|
|
|
|
return max(0, (int)$raw);
|
|
}
|
|
|
|
public static function buildKey(array $context): string
|
|
{
|
|
$normalized = self::normalizeValue($context);
|
|
$encoded = json_encode($normalized, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
if (!is_string($encoded)) {
|
|
$encoded = serialize($normalized);
|
|
}
|
|
|
|
return self::PREFIX . md5($encoded);
|
|
}
|
|
|
|
public static function getCounts(string $key): ?array
|
|
{
|
|
$raw = self::redisGet($key);
|
|
if ($raw === null || $raw === '') {
|
|
return null;
|
|
}
|
|
|
|
$decoded = json_decode($raw, true);
|
|
if (!is_array($decoded)) {
|
|
return null;
|
|
}
|
|
|
|
return self::normalizeCounts($decoded);
|
|
}
|
|
|
|
public static function storeCounts(string $key, array $counts, ?int $ttl = null): void
|
|
{
|
|
$cacheTtl = $ttl ?? self::getTtl();
|
|
if ($cacheTtl <= 0) {
|
|
return;
|
|
}
|
|
|
|
$normalized = self::normalizeCounts($counts);
|
|
if ($normalized === null) {
|
|
return;
|
|
}
|
|
|
|
$encoded = json_encode($normalized, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
if (!is_string($encoded)) {
|
|
return;
|
|
}
|
|
|
|
self::redisSetEx($key, $encoded, $cacheTtl);
|
|
}
|
|
|
|
public static function clearAll(): void
|
|
{
|
|
self::clearPattern(self::PREFIX . '*');
|
|
}
|
|
|
|
private static function normalizeCounts(array $counts): ?array
|
|
{
|
|
if (!array_key_exists('past', $counts) || !array_key_exists('current', $counts) || !array_key_exists('future', $counts)) {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'past' => max(0, (int)$counts['past']),
|
|
'current' => max(0, (int)$counts['current']),
|
|
'future' => max(0, (int)$counts['future']),
|
|
];
|
|
}
|
|
|
|
private static function normalizeValue(mixed $value): mixed
|
|
{
|
|
if (!is_array($value)) {
|
|
return self::normalizeScalar($value);
|
|
}
|
|
|
|
$normalized = array_map([self::class, 'normalizeValue'], $value);
|
|
|
|
if (array_is_list($normalized)) {
|
|
if (self::isScalarList($normalized)) {
|
|
sort($normalized);
|
|
}
|
|
return $normalized;
|
|
}
|
|
|
|
ksort($normalized);
|
|
return $normalized;
|
|
}
|
|
|
|
private static function normalizeScalar(mixed $value): mixed
|
|
{
|
|
if (!is_string($value)) {
|
|
return $value;
|
|
}
|
|
|
|
if (preg_match('/^-?\d+$/', $value) === 1) {
|
|
return (int)$value;
|
|
}
|
|
|
|
if (is_numeric($value)) {
|
|
return (float)$value;
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
private static function isScalarList(array $value): bool
|
|
{
|
|
foreach ($value as $item) {
|
|
if (is_array($item) || is_object($item)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static function clearPattern(string $pattern): void
|
|
{
|
|
try {
|
|
$client = self::redisClient();
|
|
if ($client === null) {
|
|
return;
|
|
}
|
|
$client->clear_keys($pattern);
|
|
} catch (Throwable) {
|
|
// Cache invalidation must never break request flow.
|
|
}
|
|
}
|
|
|
|
private static function redisSetEx(string $key, string $value, int $ttl): void
|
|
{
|
|
try {
|
|
$client = self::redisClient();
|
|
if ($client === null) {
|
|
return;
|
|
}
|
|
$client->setEx($key, $value, $ttl);
|
|
} catch (Throwable) {
|
|
// Best-effort cache write.
|
|
}
|
|
}
|
|
|
|
private static function redisGet(string $key): ?string
|
|
{
|
|
try {
|
|
$client = self::redisClient();
|
|
if ($client === null) {
|
|
return null;
|
|
}
|
|
$value = $client->get($key);
|
|
return is_string($value) ? $value : null;
|
|
} catch (Throwable) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static function redisClient(): ?object
|
|
{
|
|
if (self::$adapter !== null) {
|
|
return self::$adapter;
|
|
}
|
|
|
|
try {
|
|
if (defined('redis')) {
|
|
$instance = constant('redis');
|
|
if (is_object($instance)) {
|
|
return $instance;
|
|
}
|
|
}
|
|
|
|
return (new redis())->connect();
|
|
} catch (Throwable) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|