workspaces() ->withCount('socialAccounts') ->get(); return [ 'workspaceCount' => $workspaces->count(), 'socialAccountCount' => (int) $workspaces->sum('social_accounts_count'), 'memberCount' => $this->users()->count(), 'pendingInviteCount' => Invite::where('account_id', $this->id) ->whereNull('accepted_at') ->count(), 'postCount' => $this->cachedPostCount($workspaces->pluck('id')->all()), 'creditsUsed' => AiUsageLog::monthlyCredits($this->id), ]; } /** * @return array{workspaceLimit: int, socialAccountLimit: int, memberLimit: int, monthlyCreditsLimit: int} */ public function featureLimits(): array { return [ 'workspaceLimit' => Feature::for($this)->value(WorkspaceLimit::class), 'socialAccountLimit' => Feature::for($this)->value(SocialAccountLimit::class), 'memberLimit' => Feature::for($this)->value(MemberLimit::class), 'monthlyCreditsLimit' => Feature::for($this)->value(MonthlyCreditsLimit::class), ]; } /** * The `(int)` cast on the cached value is load-bearing: Laravel's * RedisStore skips serialize()/unserialize() for is_numeric values so * they can be incremented atomically — the side effect is that an int * stored via `Cache::put` comes back as a string on read. Without the * cast, the strict `: int` return type throws a TypeError under the * Redis cache driver (production). File/array/database drivers don't * have this optimisation and preserve the type, which is why the bug * never surfaced in tests or local dev. * * @param array $workspaceIds */ private function cachedPostCount(array $workspaceIds): int { if (empty($workspaceIds)) { return 0; } return (int) Cache::remember( "account:{$this->id}:posts_count", self::POST_COUNT_CACHE_TTL, fn () => Post::whereIn('workspace_id', $workspaceIds)->count(), ); } }