incr($key); } $current = self::redisGet($key); $next = max(1, (int)$current + 1); self::redisSet($key, (string)$next); return $next; } catch (Throwable) { return 0; } } /** * @param array $tables */ public static function tableVersionFingerprint(array $tables): string { $versions = []; foreach ($tables as $table) { if (!is_string($table)) { continue; } $normalized = trim($table, " `\t\n\r\0\x0B"); if ($normalized === '') { continue; } $versions[$normalized] = (int)(self::redisGet(self::TABLE_VERSION_PREFIX . $normalized) ?? 0); } ksort($versions); return md5(json_encode($versions, JSON_UNESCAPED_UNICODE)); } public static function enqueueRebuild(string $scope = 'all', array $types = []): array { $payload = [ 'scope' => in_array($scope, ['all', 'types', 'dirty'], true) ? $scope : 'all', 'types' => array_values(array_unique(array_filter(array_map('strval', $types)))), 'requested_at' => time(), ]; self::redisSet(self::REBUILD_REQUEST_KEY, json_encode($payload, JSON_UNESCAPED_UNICODE)); self::redisExpire(self::REBUILD_REQUEST_KEY, 86400); return $payload; } public static function consumeRebuildRequest(): ?array { $raw = self::redisGet(self::REBUILD_REQUEST_KEY); if ($raw === null) { return null; } self::redisDelete(self::REBUILD_REQUEST_KEY); $decoded = json_decode($raw, true); return is_array($decoded) ? $decoded : null; } private static function clearPattern(string $pattern): void { try { $client = self::redisClient(); if ($client === null) { return; } $client->clear_keys($pattern); } catch (Throwable) { // Cache clear 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) { } } private static function redisSet(string $key, string $value): void { try { $client = self::redisClient(); if ($client === null) { return; } $client->set($key, $value); } catch (Throwable) { } } 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 redisDelete(string $key): void { try { $client = self::redisClient(); if ($client === null) { return; } $client->delete($key); } catch (Throwable) { } } private static function redisSetArray(string $key, array $value): void { try { $client = self::redisClient(); if ($client === null) { return; } $client->set_array($key, $value); } catch (Throwable) { } } private static function redisGetArray(string $key): array { try { $client = self::redisClient(); if ($client === null) { return []; } $value = $client->get_array($key); return is_array($value) ? $value : []; } catch (Throwable) { return []; } } private static function redisExpire(string $key, int $ttl): void { try { $client = self::redisClient(); if ($client === null) { return; } $client->expire($key, $ttl); } catch (Throwable) { } } private static function redisClient(): ?object { if (self::$adapter !== null) { return self::$adapter; } if (!defined('redis')) { return null; } $client = constant('redis'); return is_object($client) ? $client : null; } }