From 564d8d10913cad424b5699eac06006cec17b5ac2 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 31 Aug 2025 17:53:52 -0600 Subject: [PATCH] Update StoryIndexService, improve predis + phpredis support --- app/Services/StoryIndexService.php | 81 +++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 8 deletions(-) diff --git a/app/Services/StoryIndexService.php b/app/Services/StoryIndexService.php index ef18302dc..4a5bfd66e 100644 --- a/app/Services/StoryIndexService.php +++ b/app/Services/StoryIndexService.php @@ -34,6 +34,71 @@ class StoryIndexService return 'story:rebuilding'; } + /** + * Safely convert Redis result to integer, handling both predis and phpredis + */ + private function redisToInt($value): int + { + if (is_int($value)) { + return $value; + } + + if (is_string($value) || is_numeric($value)) { + return (int) $value; + } + + // Handle phpredis object returns + if (is_object($value) && method_exists($value, '__toString')) { + return (int) $value->__toString(); + } + + // Fallback for unexpected types + return 0; + } + + /** + * Safely convert Redis result to boolean + */ + private function redisToBool($value): bool + { + if (is_bool($value)) { + return $value; + } + + if (is_int($value)) { + return $value > 0; + } + + if (is_string($value)) { + return $value === '1' || strtolower($value) === 'true'; + } + + // Handle phpredis object returns + if (is_object($value) && method_exists($value, '__toString')) { + $str = $value->__toString(); + + return $str === '1' || strtolower($str) === 'true'; + } + + return false; + } + + /** + * Safely execute Redis commands that return integers + */ + private function redisInt(callable $command): int + { + return $this->redisToInt($command()); + } + + /** + * Safely execute Redis commands that return booleans + */ + private function redisBool(callable $command): bool + { + return $this->redisToBool($command()); + } + public function indexStory($story): void { if (! $story->active) { @@ -95,7 +160,7 @@ class StoryIndexService $pipe->del($this->storyKey($sid)); }); - if ((int) Redis::zcard($this->authorKey($aid)) === 0) { + if ($this->redisInt(fn () => Redis::zcard($this->authorKey($aid))) === 0) { Redis::srem('story:active_authors', $aid); } } @@ -109,7 +174,7 @@ class StoryIndexService $secondsUntilExpiry = $expiresAt->getTimestamp() - time(); $ttl = max(1, $secondsUntilExpiry); - $currentTtl = Redis::ttl($key); + $currentTtl = $this->redisInt(fn () => Redis::ttl($key)); if ($currentTtl < 0) { $currentTtl = 0; } @@ -233,7 +298,7 @@ class StoryIndexService private function ensureStoryCacheHealth(): bool { - $activeCount = (int) Redis::scard('story:active_authors'); + $activeCount = $this->redisInt(fn () => Redis::scard('story:active_authors')); if ($activeCount > 0) { return true; @@ -277,8 +342,8 @@ class StoryIndexService $this->hydrateFollowingFromSql($viewerId); } - $followingCount = (int) Redis::scard("following:{$pid}"); - $activeCount = (int) Redis::scard('story:active_authors'); + $followingCount = $this->redisInt(fn () => Redis::scard("following:{$pid}")); + $activeCount = $this->redisInt(fn () => Redis::scard('story:active_authors')); $authorIds = []; @@ -293,19 +358,19 @@ class StoryIndexService if (is_array($results) && count($results) === count($active)) { foreach ($active as $i => $aid) { - if ($results[$i] ?? false) { + if ($this->redisToBool($results[$i] ?? false)) { $authorIds[] = $aid; } } } else { - $authorIds = array_filter($active, fn ($aid) => Redis::sismember("following:{$pid}", $aid)); + $authorIds = array_filter($active, fn ($aid) => $this->redisBool(fn () => Redis::sismember("following:{$pid}", $aid))); } } } else { $authorIds = Redis::sinter("following:{$pid}", 'story:active_authors'); } - if (Redis::zcard($this->authorKey($pid)) > 0) { + if ($this->redisInt(fn () => Redis::zcard($this->authorKey($pid))) > 0) { array_unshift($authorIds, $pid); $authorIds = array_values(array_unique($authorIds)); }