Skip to content

Commit

Permalink
Merge pull request #39 from cjmellor/dispatch-multiple-events
Browse files Browse the repository at this point in the history
Fire Multiple Levelled Up Events
  • Loading branch information
cjmellor authored Aug 31, 2023
2 parents a441fb6 + 7b3c4ec commit d04f99b
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 30 deletions.
45 changes: 23 additions & 22 deletions src/Concerns/GiveExperience.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function addPoints(

$lastLevel = Level::orderByDesc(column: 'level')->first();
throw_if(
condition: isset($lastLevel->next_level_experience) && $amount > Level::orderByDesc(column: 'level')->first()->next_level_experience,
condition: isset($lastLevel->next_level_experience) && $amount > $lastLevel->next_level_experience,
message: 'Points exceed the last level\'s experience points.',
);

Expand Down Expand Up @@ -69,14 +69,15 @@ public function addPoints(
'level_id' => $experience->level_id,
])->save();

if ($level?->level > config(key: 'level-up.starting_level')) {
Event::dispatch(event: new UserLevelledUp(user: $this, level: $level->level));
for ($lvl = config(key: 'level-up.starting_level'); $lvl <= $level?->level; $lvl++) {
Event::dispatch(event: new UserLevelledUp(user: $this, level: $lvl));
}

$this->dispatchEvent($amount, $type, $reason);

return $this->experience;
}

/**
* If the User does have an Experience record, update it.
*/
Expand Down Expand Up @@ -105,16 +106,6 @@ public function experience(): HasOne
return $this->hasOne(related: Experience::class);
}

public function experienceHistory(): HasMany
{
return $this->hasMany(related: ExperienceAudit::class);
}

public function getPoints(): int
{
return $this->experience->experience_points;
}

protected function dispatchEvent(int $amount, string $type, ?string $reason): void
{
event(new PointsIncreased(
Expand All @@ -138,6 +129,11 @@ public function getLevel(): int
return $this->experience->status->level;
}

public function experienceHistory(): HasMany
{
return $this->hasMany(related: ExperienceAudit::class);
}

public function deductPoints(int $amount): Experience
{
$this->experience->decrement(column: 'experience_points', amount: $amount);
Expand Down Expand Up @@ -191,22 +187,27 @@ public function nextLevelAt(int $checkAgainst = null, bool $showAsPercentage = f
return max(0, ($nextLevel->next_level_experience - $currentLevelExperience) - ($this->getPoints() - $currentLevelExperience));
}

public function levelUp(): void
public function getPoints(): int
{
return $this->experience->experience_points;
}

public function levelUp(int $to): void
{
if (config(key: 'level-up.level_cap.enabled') && $this->getLevel() >= config(key: 'level-up.level_cap.level')) {
return;
}

$nextLevel = Level::firstWhere(column: 'level', operator: $this->getLevel() + 1);
$this->fill(attributes: ['level_id' => $to])
->save();

$this->experience->status()->associate(model: $nextLevel);
$this->experience->save();
$this->experience->status()->associate(model: $to);
$this->save();

$this->update(attributes: [
'level_id' => $nextLevel->level,
]);

event(new UserLevelledUp(user: $this, level: $this->getLevel()));
// Fire an event for each level gained
for ($lvl = $this->getLevel(); $lvl <= $to; $lvl++) {
event(new UserLevelledUp(user: $this, level: $lvl));
}
}

public function level(): BelongsTo
Expand Down
20 changes: 15 additions & 5 deletions src/Listeners/PointsIncreasedListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,26 @@ public function __invoke(PointsIncreased $event): void
]);
}

$nextLevel = Level::firstWhere(column: 'level', operator: $event->user->getLevel() + 1);
// Get the next level experience needed for the user's current level
$nextLevel = Level::firstWhere(column: 'level', operator: '=', value: $event->user->getLevel() + 1);

if (! $nextLevel) {
// If there is no next level, return
return;
}

if ($event->user->getPoints() < $nextLevel->next_level_experience) {
return;
// 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()
->where(column: 'next_level_experience', operator: '<=', value: $event->user->getPoints())
->orderByDesc(column: 'level')
->first();

// Update the user level to the highest achievable level
if ($highestAchievableLevel->level > $event->user->getLevel()) {
$event->user->levelUp(to: $highestAchievableLevel->level);
}
}

$event->user->levelUp();
}
}
2 changes: 1 addition & 1 deletion tests/Concerns/GiveExperienceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@

expect($this->user->experience)
->experience_points->toBe(expected: 400)
->and($this->user)->getLevel()->toBe(expected: 3);
->and($this->user)->getLevel()->toBe(expected: 4);
});

test('A multiplier can use data that was passed through to it', function () {
Expand Down
13 changes: 12 additions & 1 deletion tests/Listeners/PointsIncreasedListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
$this->user->addPoints(amount: 100);

expect($this->user)
->experienceHistory()->count()->toBe(expected: 2);
->experienceHistory()->count()->toBe(expected: 3);

$this->assertDatabaseHas(table: 'experience_audits', data: [
'user_id' => $this->user->id,
Expand Down Expand Up @@ -110,3 +110,14 @@
'reason' => 'test',
]);
});

test(description: 'a User can level up multiple times', closure: function () {
/**
* Example: a new User will start with no experience, can be given
* 300 points, and will level up to level 3
*/
$this->user->addPoints(amount: 300);
$this->user->addPoints(amount: 300);

expect($this->user)->getLevel()->toBe(expected: 5);
});
18 changes: 17 additions & 1 deletion tests/Listeners/UserLevelledUpListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
it(description: 'adds audit data when a User level\'s up', closure: function () {
$this->user->addPoints(100);

expect($this->user)->experienceHistory->count()->toBe(expected: 2);
expect($this->user)->experienceHistory->count()->toBe(expected: 3);

$this->assertDatabaseHas(table: 'experience_audits', data: [
'user_id' => $this->user->id,
Expand All @@ -31,3 +31,19 @@
Event::assertDispatched(event: UserLevelledUp::class);
Event::assertListening(expectedEvent: UserLevelledUp::class, expectedListener: UserLevelledUpListener::class);
});

test(description: 'when a User levels up more than once, an event runs for each level', closure: function (int $level) {
Event::fake();

$this->user->addPoints(300);

expect($this->user)->experience->level_id->toBe(expected: 3)
->and($this->user)->level_id->toBe(expected: 3);

Event::assertDispatchedTimes(event: UserLevelledUp::class, times: 3);

Event::assertDispatched(
event: UserLevelledUp::class,
callback: fn (UserLevelledUp $event): bool => $event->level === $level
);
})->with([1, 2, 3]);

0 comments on commit d04f99b

Please sign in to comment.