C4 – Scoring System in Laravel (PHP)

0
40

Workflow

  • Voting: Users can upvote photos, and their votes are stored in Redis as part of a sorted set (ZSET).
  • Views: Each time a photo is viewed, the view count is incremented in Redis.
  • Leaderboard: A combined score (e.g., based on votes and views) is calculated and stored for real-time ranking.

Setup Requirements

  1. Ensure Redis is configured and connected in your Laravel project:
    • In .env: CACHE_DRIVER=redis REDIS_HOST=127.0.0.1 REDIS_PORT=6379
    • Install Redis support for Laravel if not already installed: composer require predis/predis
  2. Add the Redis facade if needed: use Illuminate\Support\Facades\Redis;

Code Implementation

Voting Logic

This method increments the vote count for a photo and updates the leaderboard score.

public function voteForPhoto($userId, $photoId)
{
    $voteKey = "photo:votes:$userId"; // Track user votes
    $leaderboardKey = "photo:leaderboard"; // Redis ZSET for ranking

    // Check if the user has already voted for this photo
    if (Redis::hexists($voteKey, $photoId)) {
        return response()->json(['success' => false, 'message' => 'You have already voted for this photo.']);
    }

    // Increment the photo's score in the leaderboard
    Redis::zincrby($leaderboardKey, 1, $photoId);

    // Mark the photo as voted by the user
    Redis::hset($voteKey, $photoId, 1);

    return response()->json(['success' => true, 'message' => 'Vote recorded successfully.']);
}

View Tracking Logic

This method increments the view count for a photo.

public function recordPhotoView($photoId)
{
    $viewsKey = "photo:views"; // Redis HASH for view counts

    // Increment the view count for the photo
    Redis::hincrby($viewsKey, $photoId, 1);

    return response()->json(['success' => true, 'message' => 'View recorded successfully.']);
}

Retrieving the Leaderboard

This method fetches the top N photos from the leaderboard.

public function getLeaderboard($limit = 10)
{
    $leaderboardKey = "photo:leaderboard";

    // Get the top N photos from the leaderboard
    $leaderboard = Redis::zrevrange($leaderboardKey, 0, $limit - 1, ['WITHSCORES' => true]);

    // Format the result for easier readability
    $formattedLeaderboard = [];
    foreach ($leaderboard as $photoId => $score) {
        $formattedLeaderboard[] = [
            'photo_id' => $photoId,
            'score' => $score,
        ];
    }

    return response()->json($formattedLeaderboard);
}

Combining Votes and Views for Scoring

If you want to dynamically calculate the leaderboard score based on a combination of votes and views (e.g., score = votes * 0.7 + views * 0.3), you can create a method to recompute scores periodically.

public function recalculateLeaderboardScores()
{
    $leaderboardKey = "photo:leaderboard";
    $viewsKey = "photo:views";

    // Get all photos from the leaderboard
    $photos = Redis::zrange($leaderboardKey, 0, -1);

    foreach ($photos as $photoId) {
        // Get votes and views for each photo
        $votes = Redis::zscore($leaderboardKey, $photoId) ?? 0;
        $views = Redis::hget($viewsKey, $photoId) ?? 0;

        // Recalculate the score
        $newScore = ($votes * 0.7) + ($views * 0.3);

        // Update the score in the leaderboard
        Redis::zadd($leaderboardKey, ['NX'], $newScore, $photoId);
    }

    return response()->json(['success' => true, 'message' => 'Leaderboard scores recalculated.']);
}

API Endpoints Example

In your Laravel routes/api.php, you can define endpoints like this:

use App\Http\Controllers\LeaderboardController;

Route::post('/vote', [LeaderboardController::class, 'voteForPhoto']);
Route::post('/view', [LeaderboardController::class, 'recordPhotoView']);
Route::get('/leaderboard', [LeaderboardController::class, 'getLeaderboard']);
Route::post('/leaderboard/recalculate', [LeaderboardController::class, 'recalculateLeaderboardScores']);

Example Usage

  1. Voting for a Photo
    • API Call: POST /vote
      • Request Body: { "userId": "123", "photoId": "456" }
  2. Recording a View
    • API Call: POST /view
      • Request Body: { "photoId": "456" }
  3. Getting the Leaderboard
    • API Call: GET /leaderboard
      • Response: [ { "photo_id": "456", "score": "10" }, { "photo_id": "789", "score": "8" } ]
  4. Recalculating Leaderboard Scores
    • API Call: POST /leaderboard/recalculate

Benefits of Redis in Laravel

  • Real-Time Performance: Redis ensures low latency for updates and reads.
  • Scalable: Easily handles a growing number of votes, views, and leaderboard entries.
  • Integration: Laravel’s Redis support makes the implementation seamless.

This setup provides a robust foundation for a real-time photo leaderboard system.