Howto add an Achievement / Gamification System to Laravel 8

As you may have noticed, we've added Achievements based on a Gamification system. Users are rewarded with points for completing various tasks across the platform.

For us these points will earn you rewards like early beta access, feedback sessions, and more. (Maybe one day swag?!). Some can be received multiple times (like running a Test), others are unique events (like using the Wizard). Achievements and Gamification on your system is a great way to increase user stickiness, get users to explore your features, and connect with your best users.

This guide will take you through:

  • Installation laravel-gamify
  • Setting up laravel-gamify
  • Creating your first "award"
  • Reading your users points and reputations

Feel free to comment at the bottom with any questions or suggestions.

achievements-header.png

Installation

This guide is based on Laravel 8. We are going to be using the excellent qcod/laravel-gamify package.

Install the laravel-gamify package, migrations and configs as shown below:

composer require qcod/laravel-gamify

php artisan vendor:publish --provider="QCod\Gamify\GamifyServiceProvider" --tag="migrations"
php artisan vendor:publish --provider="QCod\Gamify\GamifyServiceProvider" --tag="config"

php artisan migrate

Setup your User model

Add Gamify to your User model, or whatever model behaves as a user on your system.

use QCod\Gamify\Gamify;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable, Gamify;

Create your first award

Name it something sensible. In our example it's for adding your first Host to our system.

php artisan gamify:point HostAdded

This will create a file for you in app/Gamify/Points

<?php

namespace App\Gamify\Points;

use QCod\Gamify\PointType;

class HostAdded extends PointType
{
    /**
     * Number of points
     *
     * @var int
     */
    public $points = 20;

    /**
     * Point constructor
     *
     * @param $subject
     */
    public function __construct($subject)
    {
        $this->subject = $subject;
    }

    /**
     * User who will be receive points
     *
     * @return mixed
     */
    public function payee()
    {
        return $this->getSubject()->user;
    }
}

Award your first points

Now it's time to award your points to your user, in this case we're awarding HostAdded to a user. In your class that is creating the Host (or your version, where you want to award the points) you can call the givePoint helper function.

givePoint(new HostAdded($host));

You are calling your newly created PointType, with the object you want to pass to it ($host, $post, etc). This guide also assumes that $host->user exists. If it does not you may replace the payee() function with another way to get the user ID. For example, you can pass it into the constructor.

Getting the points per user

To retrieve a users points you can call getPoints() on the user object. If you pass true as the first argument it will format it (e.g. 1000 becomes 1k).

// get integer point
$user->getPoints(); 

// formatted result
$user->getPoints(true);

Listing Achievements

listing-achievements.png

To list each achievement and the date and point values for them you will call reputations() on your user model.

$reputations = $user->reputations

As an example here is a blade snippet (with CSS for tailwind) for listing the dates, reputations, and points awarded to a user.

<table class="min-w-full divide-y divide-gray-200">
    <thead>
    <tr>
        <th class="px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
            Date
        </th>
        <th class="px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
            Achievement
        </th>
        <th class="px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
            Points
        </th>
    </tr>
    </thead>
    <tbody class="bg-white divide-y divide-gray-200">
    @forelse ($reputations as $rep)
        <tr>
            <td class="px-6 py-4 whitespace-no-wrap text-xs leading-5 font-medium text-gray-500">
                {{ $rep->created_at->format('y/m/d H:i') }}<br />
                {{ $rep->created_at->diffForHumans() }}
            </td>
            <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900">
                {{ preg_replace('/([a-z])([A-Z])/s','$1 $2', $rep->name) }}
            </td>
            <td class="px-6 py-4 whitespace-no-wrap text-md font-bold leading-5 text-gray-500">
                {{ $rep->point }}
            </td>
        </tr>
    @empty
        <tr>
            <td colspan="5" class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
                You will see all your achievements here, once you get some!
            </td>
        </tr>
    @endforelse
    </tbody>
</table>    

Additional Tinker Tips

Using laravel tinker can be extremely useful when debugging. In the below example you can see we award a user points and then check for the latest award.

php artisan tinker
Psy Shell v0.10.8 (PHP 7.4.14 — cli) by Justin Hileman

>>> $user = User::first();
=> App\Models\User {#4608
[...]

>>> $user->getPoints()
=> 0

>>> $host = Host::first();
=> App\Models\Host {#4136
[...]

>>> givePoint(new HostCreated($host, $user));
=> null

>>> $user->getPoints()
=> 40

>>> $user->reputations()->first();
[...]

Final Words

You now have a fully functional, gamified Laravel 8 system. You can add new achievements with the php artisan gamify:point helper and implement them across your application with the givePoint() helper. Feel free to ask any questions in the comments.

If you're interested in load testing your Laravel application, that is what we do! Read our Laravel Load Testing guide, or sign up for an account.

Ready to run that test?
Start your first test within minutes.