Skip to content

v3.0.0

Latest

Choose a tag to compare

@cjmellor cjmellor released this 10 Jul 21:12
· 3 commits to 3.x since this release
Immutable release. Only release title and notes can be modified.
v3.0.0
da74858

The v3 major line: a metric-driven leaderboard, leagues, tiers, typed multiplier scopes, and an optional absolute count on achievements.

Upgrading from v2.x? See UPGRADE.md.

Breaking changes

  • Multiplier::scopeTo(Model ...$models) removed. Replaced with typed methods: scopeToUser(Model ...$users), scopeToTier(Tier ...$tiers), plus companion unscopeFromUser / unscopeFromTier / isGlobal. See UPGRADE.md.
  • multiplier_scopes polymorphic table replaced with two typed pivotsmultiplier_user and multiplier_tier. A backfill migration runs automatically during php artisan migrate and migrates existing data into the new schema. Removes the MorphToManyWithTextCast Postgres workaround that shipped in v2.1.
  • MultiplierScope model removed. Both config('level-up.models.multiplier_scope') and config('level-up.tables.multiplier_scopes') config keys are no longer read.
  • 'level-up.table' legacy config key removed. Deprecated since v2.0 in favour of 'level-up.tables.experiences'.
  • LevelUp\Experience\Support\UserForeignKey helper class removed. Replaced by a $table->userForeignId() Blueprint macro that reads the same config and routes to foreignId() / foreignUuid() / foreignUlid().
  • Trait method aliasing helpers removed (cherry-picked from v2.1's revert of PR #123). Host User models with colliding challenges() / streaks() / experience() / experienceHistory() methods need to rename or compose into a wrapper model.
  • setPoints() recalculates level and tier. Previously a raw column write with no side effects; now fires UserLevelledUp / UserTierUpdated events when the new point total implies a different placement.
  • level-up.audit.enabled now defaults to true (was false). Time-windowed leaderboards source their scores from the experience_audits ledger, so auditing is on out of the box. Set AUDIT_POINTS=false to opt out — but periodic XP boards then throw MetricRequiresAuditingException. See UPGRADE.md. (#163)
  • Leaderboard::generate() returns LeaderboardEntry objects$entry->user, $entry->score, $entry->rank — instead of a bare collection of User models. Score and rank live on the entry, never on the User model. See UPGRADE.md. (#160)
  • Leaderboard ranks require a window-function-capable database — SQLite 3.25+ or MySQL 8+; MariaDB 10.2+ and PostgreSQL support window functions natively. MySQL 5.7 (EOL) is not supported. (#161)
  • Excess points cap at the top level instead of throwing. addPoints($amount) where $amount exceeds the highest defined level's threshold no longer throws — the user is capped at the highest level.

Added

  • Metric-driven leaderboard — rank by any metric via the RankingMetric contract and Leaderboard::by(); built-in metrics are registered in level-up.leaderboard.metrics, with MetricNotFoundException / MetricDisabledException on bad input. (#160)
  • Rank numbers and ties — competition semantics (1, 1, 3) computed with SQL window functions, deterministic tiebreak ordering, plus rankOf() and around(). (#161)
  • level and streak state metrics — rank by current level, or by streak count for an Activity via new StreakMetric(activity: $activity). (#162)
  • Time Periodsperiod(Period::Day|Week|Month) and since(start:, until:) window a board to activity inside the range; XP windows are sourced from the experience_audits ledger; week_starts_on / timezone config controls boundaries. (#163)
  • achievements and challenges flow metrics — rank by achievements earned (secret ones count) or challenges completed, all-time or per Period. (#164)
  • restrictTo() — host-defined populations (friends boards, guilds, tournament brackets); ranks are computed within the restricted set. (#165)
  • Named Boards — declare metric/period(/tier) combinations under level-up.leaderboard.boards and resolve them with Leaderboard::board(); only declared Boards are tracked over time. (#166)
  • Snapshots and rank events — the level-up:snapshot-boards command persists each Board's top entries down to its Tracked Depth (track_top, default 100), diffs consecutive runs, dispatches LeaderboardRankChanged / UserEnteredTrackedDepth / UserLeftTrackedDepth, and prunes runs per snapshots.retention_days. (#167)
  • leaderboard_rank challenge condition — "finish top N on a named Board", progressed by snapshot runs and validated against the Board's Tracked Depth at creation. (#168)
  • Leagues — a Division ladder with lazy Cohort enrollment on one periodic Board; HasLeagues trait with currentDivision(), currentCohort(), and cohortStandings(). (#169)
  • League rollover — the level-up:league-rollover command computes each Cohort's final standings live, promotes and relegates per the Division's configured counts, and dispatches UserDivisionChanged. (#170)
  • Multiplier::scopeToUser, scopeToTier, unscopeFromUser, unscopeFromTier, isGlobal methods.
  • $table->userForeignId() Blueprint macro alongside entityId() / entityForeignId().
  • migrate_multiplier_scopes_to_typed_pivots migration — backfills v2.x data into the v3 schema, no-op on fresh installs.
  • Optional absolute count on Achievements — track the real number behind a percentage (games played, articles read) via an open-ended count set with grantAchievement() and increased alongside progress with incrementAchievementProgress(); the increment is carried on the AchievementProgressionIncreased event. (#179)

Fixed

  • addPoints(), deductPoints(), and setPoints() are now wrapped in DB::transaction() so the points/level/tier writes are atomic. The package's points/level/tier/multiplier events (PointsIncreased, PointsDecreased, UserLevelledUp, UserTierUpdated, MultiplierApplied) now implement ShouldDispatchAfterCommit, so listeners run only once the surrounding transaction has committed — external side effects (HTTP, mail, non-ShouldQueueAfterCommit queue jobs) no longer fire on a transaction that later rolls back.
  • grantAchievement(), incrementAchievementProgress(), and revokeAchievement() clear the cached achievements relation (and allAchievements, achievementsWithProgress, secretAchievements) so subsequent reads on the same instance reflect the mutation without needing a manual refresh().
  • addPoints() first-time-experience branch resolves the starting level via orderByDesc('level') to match PointsIncreasedListener. Previously it ordered by next_level_experience, which would diverge from the listener when level thresholds aren't strictly monotonic.
  • alter_experience_audits_type_to_string migration's down() now works on PostgreSQL (reported by @christoph-kluge in discussion #121). Driver-aware fallback splits the ALTER COLUMN TYPE and ADD CONSTRAINT into separate statements on pgsql; MySQL and SQLite use the existing Blueprint path.

Removed

  • Multiplier::scopeTo, Multiplier::scopes (HasMany to MultiplierScope).
  • src/Models/MultiplierScope.php.
  • src/Support/UserForeignKey.php.
  • database/migrations/create_multiplier_scopes_table.php.stub.
  • config('level-up.table') (legacy experiences-table key).
  • config('level-up.models.multiplier_scope') config key.
  • config('level-up.tables.multiplier_scopes') config key (replaced by tables.multiplier_user + tables.multiplier_tier).