From 29c423c7eff3c43aac36e12ff46b07872f268ec5 Mon Sep 17 00:00:00 2001 From: Simone Date: Sat, 12 Oct 2024 13:49:55 +0200 Subject: [PATCH 1/6] allow model customization --- config/level-up.php | 23 +++++++++++++---------- src/Concerns/GiveExperience.php | 16 ++++++++++------ src/Concerns/HasAchievements.php | 10 +++++----- src/Concerns/HasStreaks.php | 6 ++++-- src/Listeners/PointsIncreasedListener.php | 10 ++++++---- src/Models/Activity.php | 2 +- src/Models/Experience.php | 8 ++------ src/Models/Streak.php | 2 +- 8 files changed, 42 insertions(+), 35 deletions(-) diff --git a/config/level-up.php b/config/level-up.php index fb251cd..9c32b64 100644 --- a/config/level-up.php +++ b/config/level-up.php @@ -1,6 +1,19 @@ [ + 'achievement' => LevelUp\Experience\Models\Achievement::class, + 'activity' => LevelUp\Experience\Models\Activity::class, + 'experience' => LevelUp\Experience\Models\Experience::class, + 'experience_audit' => LevelUp\Experience\Models\ExperienceAudit::class, + 'level' => LevelUp\Experience\Models\Level::class, + 'streak' => LevelUp\Experience\Models\Streak::class, + 'streak_history' => LevelUp\Experience\Models\StreakHistory::class, + 'achievement_user' => LevelUp\Experience\Models\Pivots\AchievementUser::class, + ], + + /* |-------------------------------------------------------------------------- | User Foreign Key @@ -15,16 +28,6 @@ 'users_table' => 'users', ], - /* - |-------------------------------------------------------------------------- - | Experience Table - |-------------------------------------------------------------------------- - | - | This value is the name of the table that will be used to store experience data. - | - */ - 'table' => 'experiences', - /* |----------------------------------------------------------------------- | Starting Level diff --git a/src/Concerns/GiveExperience.php b/src/Concerns/GiveExperience.php index c69f222..695b9de 100644 --- a/src/Concerns/GiveExperience.php +++ b/src/Concerns/GiveExperience.php @@ -34,7 +34,9 @@ public function addPoints( $type = AuditType::Add->value; } - $lastLevel = Level::orderByDesc(column: 'level')->first(); + $levelClass = config(key: 'level-up.models.level'); + + $lastLevel = $levelClass::orderByDesc(column: 'level')->first(); throw_if( condition: isset($lastLevel->next_level_experience) && $amount > $lastLevel->next_level_experience, message: 'Points exceed the last level\'s experience points.', @@ -63,7 +65,7 @@ public function addPoints( * If the User does not have an Experience record, create one. */ if ($this->experience()->doesntExist()) { - $level = Level::query() + $level = $levelClass::query() ->where(column: 'next_level_experience', operator: '<=', value: $amount) ->orderByDesc(column: 'next_level_experience') ->first(); @@ -118,7 +120,7 @@ protected function getMultipliers(int $amount): int public function experience(): HasOne { - return $this->hasOne(related: Experience::class); + return $this->hasOne(related: config('level-up.models.experience')); } protected function dispatchEvent(int $amount, string $type, ?string $reason): void @@ -146,7 +148,7 @@ public function getLevel(): int public function experienceHistory(): HasMany { - return $this->hasMany(related: ExperienceAudit::class); + return $this->hasMany(related: config('level-up.models.experience_audit')); } public function deductPoints(int $amount, ?string $reason = null): Experience @@ -196,7 +198,9 @@ public function withMultiplierData(array|callable $data): static public function nextLevelAt(?int $checkAgainst = null, bool $showAsPercentage = false): int { - $nextLevel = Level::firstWhere(column: 'level', operator: '=', value: is_null($checkAgainst) ? $this->getLevel() + 1 : $checkAgainst); + $levelClass = config(key: 'level-up.models.level'); + + $nextLevel = $levelClass::firstWhere(column: 'level', operator: '=', value: is_null($checkAgainst) ? $this->getLevel() + 1 : $checkAgainst); if ($this->levelCapExceedsUserLevel()) { return 0; @@ -206,7 +210,7 @@ public function nextLevelAt(?int $checkAgainst = null, bool $showAsPercentage = return 0; } - $currentLevelExperience = Level::firstWhere(column: 'level', operator: '=', value: $this->getLevel())->next_level_experience; + $currentLevelExperience = $levelClass::firstWhere(column: 'level', operator: '=', value: $this->getLevel())->next_level_experience; if ($showAsPercentage) { return (int) ((($this->getPoints() - $currentLevelExperience) / ($nextLevel->next_level_experience - $currentLevelExperience)) * 100); diff --git a/src/Concerns/HasAchievements.php b/src/Concerns/HasAchievements.php index 3a5236e..7e32d35 100644 --- a/src/Concerns/HasAchievements.php +++ b/src/Concerns/HasAchievements.php @@ -33,11 +33,11 @@ public function grantAchievement(Achievement $achievement, $progress = null): vo public function achievements(): BelongsToMany { - return $this->belongsToMany(related: Achievement::class) + return $this->belongsToMany(related: config(key: 'level-up.models.achievement')) ->withTimestamps() ->withPivot(columns: 'progress') ->where('is_secret', false) - ->using(AchievementUser::class); + ->using(config(key: 'level-up.models.achievement_user')); } public function incrementAchievementProgress(Achievement $achievement, int $amount = 1) @@ -53,13 +53,13 @@ public function incrementAchievementProgress(Achievement $achievement, int $amou public function allAchievements(): BelongsToMany { - return $this->belongsToMany(related: Achievement::class) + return $this->belongsToMany(related: config(key: 'level-up.models.achievement')) ->withPivot(columns: 'progress'); } public function achievementsWithProgress(): BelongsToMany { - return $this->belongsToMany(related: Achievement::class) + return $this->belongsToMany(related: config(key: 'level-up.models.achievement')) ->withPivot(columns: 'progress') ->where('is_secret', false) ->wherePivotNotNull(column: 'progress'); @@ -67,7 +67,7 @@ public function achievementsWithProgress(): BelongsToMany public function secretAchievements(): BelongsToMany { - return $this->belongsToMany(related: Achievement::class) + return $this->belongsToMany(related: config(key: 'level-up.models.achievement')) ->withPivot(columns: 'progress') ->where('is_secret', true); } diff --git a/src/Concerns/HasStreaks.php b/src/Concerns/HasStreaks.php index 5ecab6f..85b5113 100644 --- a/src/Concerns/HasStreaks.php +++ b/src/Concerns/HasStreaks.php @@ -68,7 +68,7 @@ protected function hasStreakForActivity(Activity $activity): bool public function streaks(): HasMany { - return $this->hasMany(related: Streak::class); + return $this->hasMany(related: config('level-up.models.streak')); } protected function startNewStreak(Activity $activity): Model|Streak @@ -112,7 +112,9 @@ protected function archiveStreak(Activity $activity): void { $latestStreak = $this->getStreakLastActivity($activity); - StreakHistory::create([ + $streakHistoryClass = config(key: 'level-up.models.streak_history'); + + $streakHistoryClass::create([ config(key: 'level-up.streaks.foreign_key', default: 'user_id') => $this->id, 'activity_id' => $activity->id, 'count' => $latestStreak->count, diff --git a/src/Listeners/PointsIncreasedListener.php b/src/Listeners/PointsIncreasedListener.php index 1d7a43f..2c017f7 100644 --- a/src/Listeners/PointsIncreasedListener.php +++ b/src/Listeners/PointsIncreasedListener.php @@ -9,6 +9,8 @@ class PointsIncreasedListener { public function __invoke(PointsIncreased $event): void { + $levelModel = config(key: 'level-up.models.level'); + if (config(key: 'level-up.audit.enabled')) { $event->user->experienceHistory()->create([ 'points' => $event->pointsAdded, @@ -17,15 +19,15 @@ public function __invoke(PointsIncreased $event): void ]); } - if (Level::count() === 0) { - Level::add([ + if ($levelModel::count() === 0) { + $levelModel::add([ 'level' => config(key: 'level-up.starting_level'), 'next_level_experience' => null, ]); } // Get the next level experience needed for the user's current level - $nextLevel = Level::firstWhere(column: 'level', operator: '=', value: $event->user->getLevel() + 1); + $nextLevel = $levelModel::firstWhere(column: 'level', operator: '=', value: $event->user->getLevel() + 1); if (! $nextLevel) { // If there is no next level, return @@ -35,7 +37,7 @@ public function __invoke(PointsIncreased $event): void // Check if user's points are equal or greater than the next level's required experience if ($event->user->getPoints() >= $nextLevel->next_level_experience) { // Find the highest level the user can achieve with current points - $highestAchievableLevel = Level::query() + $highestAchievableLevel = $levelModel::query() ->where(column: 'next_level_experience', operator: '<=', value: $event->user->getPoints()) ->orderByDesc(column: 'level') ->first(); diff --git a/src/Models/Activity.php b/src/Models/Activity.php index e33673c..4bf863a 100644 --- a/src/Models/Activity.php +++ b/src/Models/Activity.php @@ -16,6 +16,6 @@ class Activity extends Model public function streaks(): HasMany { - return $this->hasMany(related: Streak::class); + return $this->hasMany(related: config(key: 'level-up.models.streak')); } } diff --git a/src/Models/Experience.php b/src/Models/Experience.php index 4b7ba7c..a761c8c 100755 --- a/src/Models/Experience.php +++ b/src/Models/Experience.php @@ -10,11 +10,7 @@ class Experience extends Model { // use HasFactory; - public function __construct(array $attributes = []) - { - parent::__construct($attributes); - $this->table = config(key: 'level-up.table'); - } + protected $table = 'experiences'; protected $guarded = []; @@ -25,6 +21,6 @@ public function user(): BelongsTo public function status(): BelongsTo { - return $this->belongsTo(related: Level::class, foreignKey: 'level_id'); + return $this->belongsTo(related: config('level-up.models.level'), foreignKey: 'level_id'); } } diff --git a/src/Models/Streak.php b/src/Models/Streak.php index 17ce0bc..ac47898 100644 --- a/src/Models/Streak.php +++ b/src/Models/Streak.php @@ -24,6 +24,6 @@ public function user(): BelongsTo public function activity(): BelongsTo { - return $this->belongsTo(related: Activity::class); + return $this->belongsTo(related: config(key: 'level-up.models.activity')); } } From ba3ef9aa2dcecd84aabb591699d67cf58bbc5f03 Mon Sep 17 00:00:00 2001 From: Simone Date: Sat, 12 Oct 2024 13:52:38 +0200 Subject: [PATCH 2/6] remove breaking changes --- config/level-up.php | 9 +++++++++ src/Models/Experience.php | 8 ++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/config/level-up.php b/config/level-up.php index 9c32b64..eeaf1b6 100644 --- a/config/level-up.php +++ b/config/level-up.php @@ -13,6 +13,15 @@ 'achievement_user' => LevelUp\Experience\Models\Pivots\AchievementUser::class, ], + /* + |-------------------------------------------------------------------------- + | Experience Table + |-------------------------------------------------------------------------- + | + | This value is the name of the table that will be used to store experience data. + | + */ + 'table' => 'experiences', /* |-------------------------------------------------------------------------- diff --git a/src/Models/Experience.php b/src/Models/Experience.php index a761c8c..34893eb 100755 --- a/src/Models/Experience.php +++ b/src/Models/Experience.php @@ -9,8 +9,12 @@ class Experience extends Model { // use HasFactory; - - protected $table = 'experiences'; + + public function __construct(array $attributes = []) + { + parent::__construct($attributes); + $this->table = config(key: 'level-up.table'); + } protected $guarded = []; From 14b0cf1e94603d5c6b181f27e7ecee58372b7cee Mon Sep 17 00:00:00 2001 From: Simone Date: Sat, 12 Oct 2024 13:53:34 +0200 Subject: [PATCH 3/6] move --- config/level-up.php | 20 ++++++++++---------- src/Models/Experience.php | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/config/level-up.php b/config/level-up.php index eeaf1b6..995aff8 100644 --- a/config/level-up.php +++ b/config/level-up.php @@ -13,16 +13,6 @@ 'achievement_user' => LevelUp\Experience\Models\Pivots\AchievementUser::class, ], - /* - |-------------------------------------------------------------------------- - | Experience Table - |-------------------------------------------------------------------------- - | - | This value is the name of the table that will be used to store experience data. - | - */ - 'table' => 'experiences', - /* |-------------------------------------------------------------------------- | User Foreign Key @@ -37,6 +27,16 @@ 'users_table' => 'users', ], + /* + |-------------------------------------------------------------------------- + | Experience Table + |-------------------------------------------------------------------------- + | + | This value is the name of the table that will be used to store experience data. + | + */ + 'table' => 'experiences', + /* |----------------------------------------------------------------------- | Starting Level diff --git a/src/Models/Experience.php b/src/Models/Experience.php index 34893eb..ac4f3ed 100755 --- a/src/Models/Experience.php +++ b/src/Models/Experience.php @@ -9,7 +9,7 @@ class Experience extends Model { // use HasFactory; - + public function __construct(array $attributes = []) { parent::__construct($attributes); From e6925cdc290ac9b58250690ab753edd579422067 Mon Sep 17 00:00:00 2001 From: Simone Date: Sat, 12 Oct 2024 13:56:12 +0200 Subject: [PATCH 4/6] lint code --- src/Concerns/GiveExperience.php | 1 - src/Concerns/HasAchievements.php | 3 +-- src/Concerns/HasStreaks.php | 3 +-- src/LevelUpServiceProvider.php | 2 +- tests/Pest.php | 2 +- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Concerns/GiveExperience.php b/src/Concerns/GiveExperience.php index 695b9de..5d3622e 100644 --- a/src/Concerns/GiveExperience.php +++ b/src/Concerns/GiveExperience.php @@ -14,7 +14,6 @@ use LevelUp\Experience\Events\PointsIncreased; use LevelUp\Experience\Events\UserLevelledUp; use LevelUp\Experience\Models\Experience; -use LevelUp\Experience\Models\ExperienceAudit; use LevelUp\Experience\Models\Level; use LevelUp\Experience\Services\MultiplierService; diff --git a/src/Concerns/HasAchievements.php b/src/Concerns/HasAchievements.php index 7e32d35..f781eb3 100644 --- a/src/Concerns/HasAchievements.php +++ b/src/Concerns/HasAchievements.php @@ -7,7 +7,6 @@ use LevelUp\Experience\Events\AchievementAwarded; use LevelUp\Experience\Events\AchievementProgressionIncreased; use LevelUp\Experience\Models\Achievement; -use LevelUp\Experience\Models\Pivots\AchievementUser; trait HasAchievements { @@ -33,7 +32,7 @@ public function grantAchievement(Achievement $achievement, $progress = null): vo public function achievements(): BelongsToMany { - return $this->belongsToMany(related: config(key: 'level-up.models.achievement')) + return $this->belongsToMany(related: config(key: 'level-up.models.achievement')) ->withTimestamps() ->withPivot(columns: 'progress') ->where('is_secret', false) diff --git a/src/Concerns/HasStreaks.php b/src/Concerns/HasStreaks.php index 85b5113..36f7b17 100644 --- a/src/Concerns/HasStreaks.php +++ b/src/Concerns/HasStreaks.php @@ -12,7 +12,6 @@ use LevelUp\Experience\Events\StreakUnfroze; use LevelUp\Experience\Models\Activity; use LevelUp\Experience\Models\Streak; -use LevelUp\Experience\Models\StreakHistory; trait HasStreaks { @@ -152,7 +151,7 @@ public function freezeStreak(Activity $activity, ?int $days = null): bool public function unFreezeStreak(Activity $activity): bool { - Event::dispatch(new StreakUnfroze()); + Event::dispatch(new StreakUnfroze); return $this->getStreakLastActivity($activity) ->update(['frozen_until' => null]); diff --git a/src/LevelUpServiceProvider.php b/src/LevelUpServiceProvider.php index ee9c407..e7233dc 100644 --- a/src/LevelUpServiceProvider.php +++ b/src/LevelUpServiceProvider.php @@ -37,7 +37,7 @@ public function register(): void parent::register(); $this->app->register(provider: EventServiceProvider::class); - $this->app->singleton(abstract: 'leaderboard', concrete: fn () => new LeaderboardService()); + $this->app->singleton(abstract: 'leaderboard', concrete: fn () => new LeaderboardService); $this->app->register(provider: MultiplierServiceProvider::class); } } diff --git a/tests/Pest.php b/tests/Pest.php index f2c211b..32fe3ec 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -7,7 +7,7 @@ uses(TestCase::class, FastRefreshDatabase::class) ->beforeEach(hook: function (): void { - $this->user = new User(); + $this->user = new User; $this->user->fill(attributes: [ 'name' => 'Chris Mellor', From 8b1c5353c168080cc84b70cb2775580816b7dad0 Mon Sep 17 00:00:00 2001 From: Simone Date: Sat, 12 Oct 2024 14:13:16 +0200 Subject: [PATCH 5/6] add tests --- tests/Fixtures/Level.php | 19 ++++++++++ tests/Models/LevelTestWithCustomModel.php | 43 +++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 tests/Fixtures/Level.php create mode 100644 tests/Models/LevelTestWithCustomModel.php diff --git a/tests/Fixtures/Level.php b/tests/Fixtures/Level.php new file mode 100644 index 0000000..052461e --- /dev/null +++ b/tests/Fixtures/Level.php @@ -0,0 +1,19 @@ +group('levels'); + +beforeEach(function (): void { + config()->set('level-up.models.level', \LevelUp\Experience\Tests\Fixtures\Level::class); +}); + + +it(description: 'is a custom model', closure: function (): void { + $levelClass = config('level-up.models.level'); + + $level = $levelClass::add([ + 'level' => 6, + 'next_level_experience' => 750, + ]); + + expect(value: $level[0])->toBeInstanceOf($levelClass); + expect(value: $level[0]->extra_function())->toBe(expected: 'extra_function'); +}); + +it(description: 'can create a level with custom model', closure: function (): void { + + $level = config('level-up.models.level')::add([ + 'level' => 6, + 'next_level_experience' => 750, + ]); + + expect(value: $level[0]->level)->toBe(expected: 6); + + $this->assertDatabaseHas(table: 'levels', data: [ + 'level' => 6, + 'next_level_experience' => 750, + ]); +}); + +it(description: 'throws an error if a level exists with custom model', closure: function (): void { + config('level-up.models.level')::add(level: 1, pointsToNextLevel: 100); + config('level-up.models.level')::add(level: 1, pointsToNextLevel: 100); +})->throws(exception: LevelExistsException::class, exceptionMessage: 'The level with number "1" already exists'); From e5002029b4510bcc5a0515af19d9250759b87e35 Mon Sep 17 00:00:00 2001 From: Simone Date: Sat, 12 Oct 2024 14:14:23 +0200 Subject: [PATCH 6/6] lint --- tests/Fixtures/Level.php | 8 -------- tests/Models/LevelTestWithCustomModel.php | 2 -- 2 files changed, 10 deletions(-) diff --git a/tests/Fixtures/Level.php b/tests/Fixtures/Level.php index 052461e..548226f 100644 --- a/tests/Fixtures/Level.php +++ b/tests/Fixtures/Level.php @@ -2,14 +2,6 @@ namespace LevelUp\Experience\Tests\Fixtures; -use Illuminate\Database\Eloquent\Factories\Factory; -use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Model; -use LevelUp\Experience\Concerns\GiveExperience; -use LevelUp\Experience\Concerns\HasAchievements; -use LevelUp\Experience\Concerns\HasStreaks; -use LevelUp\Experience\Tests\Fixtures\Factories\UserFactory; - class Level extends \LevelUp\Experience\Models\Level { public function extra_function(): string diff --git a/tests/Models/LevelTestWithCustomModel.php b/tests/Models/LevelTestWithCustomModel.php index 5ab16ec..e01d098 100644 --- a/tests/Models/LevelTestWithCustomModel.php +++ b/tests/Models/LevelTestWithCustomModel.php @@ -1,7 +1,6 @@ group('levels'); @@ -9,7 +8,6 @@ config()->set('level-up.models.level', \LevelUp\Experience\Tests\Fixtures\Level::class); }); - it(description: 'is a custom model', closure: function (): void { $levelClass = config('level-up.models.level');