Open source October 2023

Larawards

Larawards implements achievements, trophies and badges in Laravel. An award is a class that knows how to compute a score for a model, and tiers turn one class into a whole family of related achievements.

Installation

composer require edulazaro/larawards
php artisan migrate

Awards are persisted, so the migration is required.

Creating an award

php artisan make:award FooAchievement

That creates app/Awards/FooAchievement.php.

An award has a type: achievement, badge, or whatever you need:

/** @var string The award type. */
public $type = 'achievement';

Tiers

A tier is a single thing that can be awarded. Tiers let one class define a whole ladder of similar achievements instead of one class per milestone:

protected array $tiers = [
    'comment_written' => [
        'score' => 1,
        'title' => 'First Comment Written',
    ],
    '3_comments_written' => [
        'score' => 3,
        'title' => '3 Comments Written',
    ],
    '10_comments_written' => [
        'score' => 10,
        'title' => '10 Comments Written',
    ],
];

Each tier has a title, the name shown to the user, and a score, the threshold at which it is awarded.

The score method

This is where the logic lives. It returns the current score for the awardable model:

/**
 * Get the awardable score for a user.
 *
 * @return int
 */
public function score(): int
{
    return $this->rewardable->comments()->count();
}

Making a model awardable

namespace App\Models;

use EduLazaro\Larawards\Concerns\HasRewards;

class User
{
    use HasRewards;
}

Any model using HasRewards can receive awards.

Registering awards

User::awardable(AchievementsBadge::class);

Or grouped:

User::awardableGroup('achievements', [
    CommentsAchievement::class,
    LessonsWatchedAchievement::class,
]);

Checking awards

An award is scoped to a model first, then checked. The check awards any tier whose requirement is now met:

FooAchievement::scope($user)->check();

Several at once:

User::awardables()->check();                              // every award for the user
User::awardables()->group('top_awards')->check();         // one group
User::awardables()->where('type', 'achievement')->check(); // one type

Awarded tiers land in the rewards table.

Events

Set the $event property and it fires each time the award is granted:

namespace App\Awards;

use EduLazaro\Larawards\Concerns\IsAward;
use EduLazaro\Larawards\Contracts\AwardInterface;
use App\Events\AchievementUnlocked;

class CommentsAchievement implements AwardInterface
{
    use IsAward;

    protected string $event = AchievementUnlocked::class;
}

Reading rewards back

$rewards = $user->rewards;

$isRewarded = $user->rewards()->where('name', 'comment_written')->exists();

Across users:

use EduLazaro\Larawards\Models\Reward;

Reward::where('award_id', 'comments_achievement')->get();

Awards morph map

Works like Laravel's morph maps. By default the stored award id is the fully qualified class name, which is ugly in the database. enforceMap assigns readable aliases:

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use EduLazaro\Larawards\Collections\Awards;

use App\Awards\CommentsAchievement;
use App\Awards\LikesAchievement;

class AwardServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Awards::enforceMap([
            'comments_achievement' => CommentsAchievement::class,
            'likes_achievement'    => LikesAchievement::class,
        ]);
    }
}

App\Awards\CommentsAchievement is then stored as comments_achievement.

built and maintained by Edu Lazaro · MIT license