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; } } }