Level Up

repository·3.x·Indexed 20 days ago

https://github.com/cjmellor/level-up

A Laravel package for gamification that enables users to earn experience points (XP), progress through levels, and unlock achievements. It includes features for managing XP multipliers with various stacking strategies, a configurable leaderboard system with competitive leagues, level caps, and tier-based status brackets with streak freezes.

Tokens
30.7K
Snippets
100
Records
122
Agent score
71%

What's inside level-up

  1. How leaderboard rank conditions work

    3.x

    The leaderboard_rank condition checks if a user's rank on a specific Board is at or above a target (rank <= N).

    Critical Requirements:

    • This condition only progresses when level-up:snapshot-boards runs.
    • The board must be declared in level-up.leaderboard.boards.
    • The rank must be a positive integer within the Board's track_top depth (default is 100). If you need to target a rank deeper than 100, you must increase the Board's track_top setting.
    Challenge::create([
        'name' => 'Podium Finish',
        'description' => 'Crack the top 3 on the weekly XP board.',
        'conditions' => [
            ['type' => 'leaderboard_rank', 'board' => 'weekly-xp', 'rank' => 3],
        ],
        'rewards' => [
            ['type' => 'points', 'amount' => 500],
        ],
        'auto_enroll' => true,
    ]);
  2. Understand built-in leaderboard metrics

    3.x

    The package provides several built-in metrics for ranking users:

    MetricTypeDescription
    xpFlowExperience points (default). Requires auditing enabled for periodic boards.
    levelStateCurrent level snapshot. Not windowable (cannot use period()).
    streakStateCurrent streak count for a specific Activity. Requires an Activity instance.
    achievementsFlowTotal achievements earned (includes secret ones). Windowable.
    challengesFlowCount of completions in the challenge_completions ledger. Windowable.

    Important Notes:

    • Zero-count users: Users with a score of 0 are excluded from boards; they are never ranked at 0.
    • Streak Metric: Using the streak key without providing an Activity instance (e.g., new StreakMetric(activity: $activity)) throws a MetricRequiresActivityException.
    • Challenges: The challenges metric is disabled by default. Enable it via level-up.challenges.enabled in your config.
  3. How Achievements work in LevelUp

    3.x

    Achievements allow you to reward users for completing tasks or reaching milestones. They can be static (earned instantly) or have progression (earned in increments, e.g., reaching 100% progress).

    To use achievements, you must include the LevelUp\Experience\Concerns\HasAchievements trait in your User model.

    Achievements support:

    • Secret status: Achievements can be hidden (is_secret => true) until unlocked.
    • Progress tracking: A percentage-based value capped at 100%.
    • Absolute count tracking: An open-ended integer (e.g., total games played) that tracks alongside progress.
    use LevelUp\Experience\Concerns\HasAchievements;
    
    class User extends Authenticable
    {
        use HasAchievements;
        // ...
    }
  4. Scope leaderboards to time periods

    3.x

    For metrics that implement LevelUp\Experience\Contracts\Windowable (like xp, achievements, and challenges), you can scope the board to a specific time window using period() or since().

    Using period():

    • Period::Day (today)
    • Period::Week (this week)
    • Period::Month (this month)

    Using since():

    • since(start: $timestamp): An open-ended start.
    • since(start: $start, until: $end): A bounded window.

    Requirements & Behavior:

    • Auditing: Periodic XP boards require auditing enabled (level-up.audit.enabled). If disabled, requesting a periodic XP board throws MetricRequiresAuditingException.
    • Non-Windowable Metrics: Calling period() on state metrics like level or streak throws MetricNotWindowableException.
    • Score Calculation: Periodic scores are calculated as the sum of points added minus points removed within the window from the experience_audits ledger.
    use LevelUp//Experience/Enums/Period;
    
    // Top earners this week
    Leaderboard::period(Period::Week)->generate();
    
    // Custom range
    Leaderboard::since(start: now()->subDays(3))->generate();
  5. Configure and manage Leagues

    3.x

    A League is a competitive cycle that groups users into Cohorts within a Division each period. This is independent of Tiers (which are based on XP status).

    Configuration: Leagues are declared under level-up.leaderboard.league. You must specify a periodic Board to serve as the basis for the league.

    'league' => [
        'board' => 'weekly-xp', // Must be a declared periodic Board
        'cohort_size' => 30,
        'divisions' => [
            'Bronze' => ['promote' => 10, 'relegate' => 0],
            'Silver' => ['promote' => 7, 'relegate' => 5],
            'Gold' => ['promote' => 0, 'relegate' => 5],
        ],
    ],

    Key Concepts:

    • Enrollment: Users join the league automatically upon their first score-earning action in the period. They enter the open cohort of their current Division.
    • Division vs Tier: A user holds a Tier (XP-based) and a Division (competition-based) simultaneously. They are separate systems.
    • Cohort Standings: Use $user->cohortStandings() to get a collection of LeaderboardEntry objects ranked specifically within the user's current cohort.

    Rollover: To move users between divisions at the end of a period, you must schedule the level-up:league-rollover command (e.g., weekly). This command calculates final standings and promotes/relegates users based on your config.

    // In routes/console.php
    $schedule->command('level-up:league-rollover')->weeklyOn(1, '00:05');
  6. Manage Achievements for Users

    3.x

    Achievements are milestones users can earn. You can create public, secret (hidden until earned), or tier-gated achievements. Once an achievement is granted, you can track progress towards it (0-100%).

    Key Operations:

    • Create: Use Achievement::create() to define name, description, image, and visibility.
    • Grant: Use $user->grantAchievement($achievement, progress: X) to award an achievement. Progress is capped at 100. An AchievementAwarded event fires when progress reaches 100 or is set to null.
    • Revoke: Use $user->revokeAchievement($achievement) to remove an achievement.
    • Progress: Use $user->incrementAchievementProgress($achievement, amount: X) to increase progress. The user must already have the achievement granted before incrementing.

    Querying Achievements:

    • $user->achievements: Non-secret achievements.
    • $user->secretAchievements: Secret achievements only.
    • $user->allAchievements: All achievements (both secret and non-secret).
    use LevelUp//Experience/Models/Achievement;
    
    // Create a standard achievement
    Achievement::create([
        'name' => 'First Login',
        'is_secret' => false,
        'description' => 'Log in for the first time',
        'image' => 'storage/app/achievements/first-login.png',
    ]);
    
    // Grant and set progress
    $user->grantAchievement($achievement, progress: 50);
    
    // Increment progress
    $newProgress = $user->incrementAchievementProgress($achievement, amount: 10);
  7. Upgrade from v2.x to v3.0

    3.x

    Upgrading to v3.0 is a breaking-change process. Most users should follow these steps:

    1. Update the package: composer require cjmellor/level-up:^3.0.
    2. Re-publish migrations: php artisan vendor:publish --tag=level-up-migrations.
    3. Re-publish configuration: php artisan vendor:publish --tag=level-up-config --force (Note: back up your customisations first, as this will overwrite your existing config).
    4. Run migrations: php artisan migrate.

    Important Note on Config: If you previously published config/level-up.php, Laravel's shallow merge means your old models array will override the new v3 defaults. You must manually add the new model bindings (e.g., multiplier_user, challenge_completion, division, etc.) to your config file to avoid errors with new features like Leagues or Snapshots.

    composer require cjmellor/level-up:^3.0
    php artisan vendor:publish --tag=level-up-migrations
    php artisan vendor:publish --tag=level-up-config --force
    php artisan migrate
  8. Implement Challenges in your User model

    3.x

    To enable challenges, add the HasChallenges trait to your User model. You should also include other relevant traits like GiveExperience, HasAchievements, HasStreaks, and HasTiers to ensure full functionality.

    use LevelUp\Experience\Concerns\HasChallenges;
    
    class User extends Model
    {
        use GiveExperience, HasAchievements, HasStreaks, HasTiers, HasChallenges;
    }
  9. Publish and run level-up migrations

    3.x

    After installation, you must publish the package migrations and run them to set up the necessary database tables:

    php artisan vendor:publish --tag="level-up-migrations"
    php artisan migrate
  10. Migrate from v1.x to v2.0

    3.x

    Upgrading from v1.x to v2.0 involves significant breaking changes, primarily due to the replacement of the class-based multiplier system with a database-backed model and the introduction of Tiers and Challenges.

    Requirements

    • PHP 8.3+ is required.
    • Laravel 12 or 13 is required.

    Multiplier System Migration

    The Multiplier contract, MultiplierService, and MultiplierServiceProvider have been removed. Multipliers are now managed via Eloquent models in the database.

    1. Run Migrations

    php artisan vendor:publish --tag="level-up-migrations"
    php artisan migrate

    2. Update Multiplier Logic Instead of using PHP classes with qualifies() logic, create database records. For complex logic, toggle is_active programmatically.

    // After (v2): Use database records
    Multiplier::create([
        'name' => 'December Holiday Bonus',
        'multiplier' => 2,
        'is_active' => true,
        'starts_at' => '2026-12-01',
        'expires_at' => '2026-12-31',
    ]);

    3. Update Tier Multipliers If you previously used config-based tier multipliers, attach them to tiers via the database:

    $multiplier = Multiplier::create([
        'name' => 'Gold Tier Bonus',
        'multiplier' => 2,
        'is_active' => true,
    ]);
    
    $multiplier->tiers()->attach(Tier::where('name', 'Gold')->first());

    API Changes

    • addPoints(): The $multiplier parameter type changed from ?int to int|float|null. This allows fractional multipliers (e.g., 1.5).
    • Level::add(): Now only accepts arrays.
      • Old: Level::add(level: 1, pointsToNextLevel: 100);
      • New: Level::add(['level' => 1, 'next_level_experience' => 100]);
    • Error Handling:
      • $user->levelUp(to: X) now throws InvalidArgumentException if the level does not exist.
      • $user->deductPoints(X) now throws an Exception if no experience record exists for the user.
      • $user->incrementAchievementProgress() now throws an Exception if the user lacks the achievement.
  11. Enable Challenges in v2.0

    3.x

    v2.0 introduces a Challenges system for multi-condition goals. Challenges are evaluated automatically when relevant events (like PointsIncreased or AchievementAwarded) fire, provided the trait is present and the feature is enabled.

    Setup Steps

    1. Run Migrations:

      php artisan vendor:publish --tag="level-up-migrations"
      php artisan migrate
    2. Add Trait to User Model:

      use LevelUp\Experience\Concerns\HasChallenges;
      
      class User extends Model
      {
          use GiveExperience, HasAchievements, HasStreaks, HasTiers, HasChallenges;
      }

    Configuration

    To disable challenges entirely, set the following in your .env file: CHALLENGES_ENABLED=false

    use LevelUp\Experience\Concerns\HasChallenges;
    
    class User extends Model
    {
        use GiveExperience, HasAchievements, HasStreaks, HasTiers, HasChallenges;
    }
  12. Upgrade cjmellor/level-up from v1 to v2

    3.x

    Follow this guide to upgrade from cjmellor/level-up v1.x to v2.x. This process involves updating the package, running new migrations, re-publishing configuration, and applying several breaking code changes.

    Prerequisites

    • PHP 8.3+
    • Laravel 12 or 13
    • Database backup completed.
    • No pending migrations (php artisan migrate:status).
    • Existing test suite passes (php artisan test).

    Upgrade Steps

    1. Update Package: Run composer require cjmellor/level-up:"^2.0".
    2. Migrations: Publish and run new migrations:
      php artisan vendor:publish --tag="level-up-migrations"
      php artisan migrate
    3. Configuration: Re-publish the config to include new v2 keys (like tiers and challenges):
      php artisan vendor:publish --tag="level-up-config" --force
    4. Code Changes: Search your codebase for the breaking changes listed in the API Breaking Changes section and apply fixes.
    5. Optional Features: Add HasTiers or HasChallenges traits to your User model if you wish to use the new features.
    6. Verify: Run php artisan test to ensure everything is working correctly.
    composer require cjmellor/level-up:"^2.0"
    php artisan vendor:publish --tag="level-up-migrations"
    php artisan migrate
    php artisan vendor:publish --tag="level-up-config" --force