insertOrIgnore([ 'user_id' => $targetId, 'follower_id' => $actorId, 'created_at' => now(), ]); if ($rows === 0) { // Already following – nothing to do return; } $inserted = true; // Increment following_count for actor, followers_count for target $this->incrementCounter($actorId, 'following_count'); $this->incrementCounter($targetId, 'followers_count'); }); // Record activity event outside the transaction to avoid deadlocks if ($inserted) { try { \App\Models\ActivityEvent::record( actorId: $actorId, type: \App\Models\ActivityEvent::TYPE_FOLLOW, targetType: \App\Models\ActivityEvent::TARGET_USER, targetId: $targetId, ); } catch (\Throwable) {} } return $inserted; } /** * Unfollow $targetId on behalf of $actorId. * * @return bool true if a follow row was removed, false if wasn't following */ public function unfollow(int $actorId, int $targetId): bool { if ($actorId === $targetId) { return false; } $deleted = false; DB::transaction(function () use ($actorId, $targetId, &$deleted) { $rows = DB::table('user_followers') ->where('user_id', $targetId) ->where('follower_id', $actorId) ->delete(); if ($rows === 0) { return; } $deleted = true; $this->decrementCounter($actorId, 'following_count'); $this->decrementCounter($targetId, 'followers_count'); }); return $deleted; } /** * Toggle follow state. Returns the new following state. */ public function toggle(int $actorId, int $targetId): bool { if ($this->isFollowing($actorId, $targetId)) { $this->unfollow($actorId, $targetId); return false; } $this->follow($actorId, $targetId); return true; } public function isFollowing(int $actorId, int $targetId): bool { return DB::table('user_followers') ->where('user_id', $targetId) ->where('follower_id', $actorId) ->exists(); } /** * Current followers_count for a user (from cached column, not live count). */ public function followersCount(int $userId): int { return (int) DB::table('user_statistics') ->where('user_id', $userId) ->value('followers_count'); } // ─── Private helpers ───────────────────────────────────────────────────── private function incrementCounter(int $userId, string $column): void { DB::table('user_statistics')->updateOrInsert( ['user_id' => $userId], [ $column => DB::raw("COALESCE({$column}, 0) + 1"), 'updated_at' => now(), 'created_at' => now(), // ignored on update ] ); } private function decrementCounter(int $userId, string $column): void { DB::table('user_statistics') ->where('user_id', $userId) ->where($column, '>', 0) ->update([ $column => DB::raw("{$column} - 1"), 'updated_at' => now(), ]); } }