Documentation and Reference / Version 1.1
Laravel Achievements is an implementation of an Achievement System in Laravel. It's designed to provide to Laravel developers a simple and yet robust gamification system for their website.
In game design parlance, an Achievement - also known as a Trophy or a Badge - is a meta goal, an objective for a player to accomplish that is scoped outside of the default objectives of the game. [reference]
As a gamification tool, Achievements allow you to reward your Users for accomplishing tasks within your website.
Other than the default Laravel requirements, Laravel Achievements has the following requirements and recommendations:
Laravel Achievements also requires a working database connection and at least one Eloquent model to handle unlocking.
1) Run the following Composer command on the root of your Laravel application:
composer require itvoland/laravel-achievements
2) If you're using Laravel 5.3 or 5.4, add the Service
Provider to your config/app.php
file, under the providers
section.
(if you're running Laravel 5.5 or higher, you can skip this step.)
'providers' => [ ... ITVOLAND\Achievements\AchievementsServiceProvider::class,
3) Run the artisan publish command in order to get the migrations and config files set up in your system.
php artisan vendor:publish --provider="ITVOLAND\Achievements\AchievementsServiceProvider"
4) Backup your database and run the migrations in order to install all tables required by Laravel Achievements.
php artisan migrate
You can change the table names for
the database setup before migrating.
Check the newly generated config/achievements.php
file for more
details on the table naming conventions.
Once that's done, you're ready to start creating your Achievement system!
In this section you will learn how to set up your Achievement system - how to create, assign to Entities, unlock, and manage user progress.
Before creating Achievements, you must first tell your system
which Entities are supposed to be able to unlock and progress in them. You can do that by
adding the Achiever
trait to any Model (usually the User model).
In our example, let's add the Achiever
trait to our
User
model, so our Users can interact with Achievements.
namespace App; use ITVOLAND\Achievements\Achiever; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use Achiever; ... }
Once you have your Achiever
set up, it's time to create an Achievement!
Similar to Laravel's implementation of
Notifications,
each Achievement is represented by a single class, which are typically stored in the
app\Achievements
directory.
You can either create a new Achievement class manually, or use the
helper Artisan command php artisan make:achievement
in order to create a new Achievement.
php artisan make:achievement CreatedFirstComment
This command will put a fresh Achievement in your
app/Achievements
directory with only has three properties defined:
name
(a human-readable name for your Achievement), slug
(a machine-readable name for your Achievement, with only lowercase letters, numbers and hyphens)
and description
(a text that tells how to unlock the Achievement).
You should change the default values for these properties to match the Achievement.
In our example, we will be creating a new Achievement that will be unlocked when our User creates their first comment on our website. When you're done, it should look like this:
namespace App\Achievements; use ITVOLAND\Achievements\Achievement; class CreatedFirstComment extends Achievement { /* * The achievement name */ public $name = "Created your first comment"; /* * A unique text identifier for your Achievement. * It should have only lowercase characters, numbers and hyphens. */ public $slug = "created-first-comment"; /* * A small description for the achievement */ public $description = "Congratulations! You have created your first comment!"; }
That should be all you need for your first Achievement. Now you need to setup an unlock condition.
Once you have setup the Achiever
trait, all you need to do is call the helper
method unlock()
to any instance of an Achiever, by passing the Achievement class.
as a parameter.
In our example, we will add the unlock condition to our
CommentsController
and unlock the achievement as soon as the first comment is published:
use App\Http\Controller; use App\Http\Requests\CreateCommentRequest; // custom Request to handle validation use App\Comment; // Eloquent Model for Comment use App\Achievements\CreatedFirstComment; // The Achievement we have just created class CommentsController extends Controller { public function create(CreateCommentRequest $request) { $user = Auth::user(); $comment = new Comment(); $comment->fill($request->all()); $user->comments()->save($comment); $user->unlock(new CreatedFirstComment()); ...
And that should be enough!
Once your User
create a Comment
,
they will have unlocked the CreatedFirstComment
Achievement.
This example showcases two important features inherent to all Achievements:
Sometimes you don't actually want to unlock and Achievement right away; instead, your Users
should slowly gain points until they have enough to unlock the Achievement.
For example, let's
create an Achievement for when our user creates ten posts on our website.
Again, let's create the Achievement by running the
make:achievement
command and change the default properties:
php artisan make:achievement CreatedTenPosts
namespace App\Achievements; use ITVOLAND\Achievements\Achievement; class CreatedTenComments extends Achievement { /* * The achievement name */ public $name = "Created 10 comments"; /* * A unique text identifier for your Achievement. * It should have only lowercase characters, numbers and hyphens. */ public $slug = "created-ten-comments"; /* * A small description for the achievement */ public $description = "Wow! You have already created ten comments!"; /* * The amount of "points" this user need to obtain in order to complete this achievement */ public $points = 10; }
Notice that we're adding a new points
property, which will tell Laravel Achievements
how many points (in this case, comments) a user needs to have in order to unlock this Achievement.
Since we're not unlocking the achievement right away, we won't be
calling unlock()
. Instead, we will call another helper method that will
automatically handles how many progress the user has - addProgress()
.
This is how the newly updated Controller should look like:
use App\Http\Controller; use App\Http\Requests\CreateCommentRequest; use App\Comment; use App\Achievements\CreatedFirstComment; use App\Achievements\CreatedTenComments; class CommentsController extends Controller { public function create(CreateCommentRequest $request) { $user = Auth::user(); $comment = new Comment(); $comment->fill($request->all()); $user->comments()->save($comment); $user->unlock(new CreatedFirstComment()); // Adds 1 point of progress to CreatedTenComments $user->addProgress(new CreatedTenComments(), 1); ...
And that's it - no further coding is required. Laravel Achievements will handle storing and counting the progress for you.
In this example, whenever an User creates a new post, that user will receive 1 point that
will count towards the progress for the Achievement CreatedTenComments
.
Once this progress has reached the amount defined on the $points
property defined
on the Achievement
class, it will be automatically unlocked.
You can also remove progress points by using removeProgress()
, set a user's
progress to anything by using setProgress()
and reset it to 0 points of progress
by using resetProgress().
Let's see some more examples:
namespace App\Achievements; use ITVOLAND\Achievements\Achievement; class CreatedCommentWith100Characters extends Achievement { public $name = "Created comment with 100 characters"; public $slug = "created-comment-with-100-characters"; public $description = "You have created a single comment with 100 characters or more"; public $points = 10; }
namespace App\Achievements; use ITVOLAND\Achievements\Achievement; class Commented1000Characters extends Achievement { public $name = "Commented 1000 characters"; public $slug = "commented-1000-characters"; public $description = "Across all your comments, you have commented 1000 characters or more"; public $points = 10; }
namespace App\Achievements; use ITVOLAND\Achievements\Achievement; class CreatedFiveConsecutiveCommentsWithOver100Characters extends Achievement { public $name = "Created five consecutive comments with 100 characters"; public $slug = "created-five-consecutive-comments-with-100-characters"; public $description = "You have created five consecutive comments with 100 characters or more"; public $points = 10; }
use App\Http\Controller; use App\Http\Requests\CreateCommentRequest; use App\Comment; use App\Achievements\CreatedFirstComment; use App\Achievements\CreatedTenComments; use App\Achievements\Commented1000Characters; use App\Achievements\CreatedCommentWith100Characters; use App\Achievements\CreatedFiveConsecutiveCommentsWithOver100Characters; class CommentsController extends Controller { public function create(CreateCommentRequest $request) { $user = Auth::user(); $comment = new Comment(); $comment->fill($request->all()); $user->comments()->save($comment); $amountOfCharacters = strlen($comment->message); $user->unlock(new CreatedFirstComment()); $user->addProgress(new CreatedTenComments(), 1); // Adds $amountOfCharacters progress points to Commented1000Characters $user->addProgress(new Commented1000Characters(), $amountOfCharacters); // When amount of characters is equal to or higher than 100, // CreatedCommentWith100Characters will be unlocked. $user->setProgress(new CreatedCommentWith100Characters, $amountOfCharacters); // Since this is counting consecutive comments with 100 characters, // if at least one comment does not have 100 characters, we need to reset the progress to 0. if(strlen($comment->message) >= 100){ $user->addProgress(new FiveConsecutiveCommentsWithOver100Characters(), 1); } else { $user->resetProgress(new FiveConsecutiveCommentsWithOver100Characters()); } ...
That should take care of most use cases of creating and unlocking Achievements. Now let's find out how to list Achievements from the database and get details about which Achievements an Entity has unlocked.
The Achiever
trait adds many helpful features to handle Achievement retrieval
and listing.
You can retrieve all Achievement related information for a single
Achiever
by using$achiever->achievements()
. It will return a list
of all Achievements this user has interacted with.
Since achievements()
is a relationship, you
can use it as a QueryBuilder to filter data even further.
$achievements = $user->achievements; $unlocked_today = $user->achievements()->where('unlocked_at', '>=', Carbon::yesterday())->get();
You can also search for a specific achievement with achievementStatus()
.
$details = $user->achievementStatus(new CreatedTenComments());
There are also three additional helpers on the Achiever
trait:
lockedAchievements()
, inProgressAchievements()
and
unlockedAchievements()
.
In order to retrieve all achievements, you can use the static method
ITVOLAND\Achievements\Achievement::all()
.
When you have multiple Achievements that are tracking the same stats, you might have a huge block of code in a specific part of your code. For example, if you have multiple achievements for making 10, 50, 100, 200, 500 and 1000 comments, you would need to add a single line for each one of these Achievement classes.
An Achievement Chain will help you solve this issue.
An AchievementChain
is a helper class that defines a list of Achievements that track
the same stats.
Similar to Achievements, you can either create a new AchievementChain class manually, or use the
helper Artisan command php artisan make:achievement_chain
in order to create a new AchievementChain.
php artisan make:achievement_chain TrackComments
This command will put a fresh AchievementChain in your
app/Achievements
directory with only a method defined: chain()
.
After creating the AchievementChain class, you should change the chain()
method.
This method should return all achievements that belong to that chain, in the same
order as they should be unlocked.
Using the original example - if you have Achievements that track 10, 50, 100, 200, 500 and 1000 comments, you might create your chain similar to this:
namespace App\Achievements\Chains; use App\Achievements\FirstPost; use App\Achievements\TenPosts; use App\Achievements\FiftyPosts; use ITVOLAND\Achievements\AchievementChain; class CommentChain extends AchievementChain { public function chain() { return [ new Comments10(), new Comments50(), new Comments100(), new Comments200(), new Comments500(), new Comments1000() ]; } }
After declaring this class, you can track it in the same way you would if it were an Achievement - you can use all progress methods directly to the chain.
use App\Http\Controller; use App\Http\Requests\CreateCommentRequest; use App\Comment; use App\Achievements\Chains\CommentChain; class CommentsController extends Controller { public function create(CreateCommentRequest $request) { $user = Auth::user(); $comment = new Comment(); $comment->fill($request->all()); $user->comments()->save($comment); $user->addProgress(new CommentChain(), 1); ...
Laravel implements an efficient Event system that allows you to trigger and listen to events as they happen.
Laravel Achievements is fully compatible with the Event system and provides a few events that can be listened to.
ITVOLAND\Achievements\Event\Progress
: Triggers whenever an Achiever has their progress points in an Achievement changed.ITVOLAND\Achievements\Event\Unlocked
: Triggers whenever an Achiever has unlocked an Achievement.Details on how to listen to those events are explained on Laravel's Event documentation.
The event listeners mentioned above triggers for all Achievements. If you would like to add an event listener for only a specific Achievement, you can do so
by implementing the methods whenUnlocked()
or whenProgress
on the Achievement class.
use ITVOLAND\Achievements\Achievement; class UserMade50Posts extends Achievement { /* * The achievement name */ public $name = "50 Posts Created"; /* * A small description for the achievement */ public $description = "Wow! You have already created 50 posts!"; /* * The amount of "points" this user need to obtain in order to complete this achievement */ public $points = 50; /* * Triggers whenever an Achiever makes progress on this achievement */ public function whenProgress($progress) { } /* * Triggers whenever an Achiever unlocks this achievement */ public function whenUnlocked($progress) { } }
Coming soon.
This section will contain more details about the classes, entities and concepts behind the Laravel Achievements package.
An Achiever is any Eloquent Model within your
system that is able to unlock and progress in Achievements. You can define that a Model is
an Achiever by including the ITVOLAND\Achievements\Achiever
trait to that model.
namespace App; use ITVOLAND\Achievements\Achiever; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use Achiever; }
Remember that this is not limited to the User model; you can have as many Achievers as you want.
An Achievement is a class that extends from the
ITVOLAND\Achievements\Achievement
class and describes what the Achievement is.
It's designed to be implemented in a similar way to the
Laravel Notifications
package - for every Achievement you use in your system, you will need to declare a class for it.
An Achievement class looks like this:
namespace App\Achievements; use ITVOLAND\Achievements\Achievement; class UserMadeAPost extends Achievement { /* * The achievement name */ public $name = "Post Created"; /* * A small description for the achievement */ public $description = "Congratulations! You have made your first post!"; /* * The amount of "points" this user need to obtain in order to complete this achievement */ public $points = 1; }
The following list describes all available attributes for Achievement class. Attributes with a * are required.
$name
*: The name of the Achievement
$description
*: A description that
tells the user how to obtain the Achievement.
$points:
The amount of "progression points" required to unlock this
Achievement. Defaults to 1.$points
should be 10).
For more details on the usage of Achievement
, refer to the
Achievements - Introduction part of this documentation.
An AchievementDetail is an Eloquent Model that is a direct representation
of an Achievement
class in the database. It contains the same attributes as the
Achievement
class and is used as an intermediary model in order to map the
Achievement
class to an Achiever
model.
It also contains two helper methods:
progress()
(which is also a HasMany relationship) returns all users that
have made some kind of progress in this Achievement
unlocks()
returns all users that have unlocked this Achievement.An AchievementProgress is an Eloquent Model that describer a specific
Achiever
progress within a Achievement
class. It contains the
amount of points the Achiever
has gained, and the unlock timestamp if it was
already unlocked.
It also contains four helper methods:
achiever()
(which is also a MorphTo relationship) returns the specific
Achiever
instance that this AchievementDetail
belongs to.
details()
returns the instance for the AchievementDetails
entity that this AchievementProgress
refers to.
isLocked()
return false when this Achievement is unlocked.isUnlocked()
return true when this Achievement is unlocked.A Chain is a class that extends from ITVOLAND\Achievements\AchievementChain
and defines a sequence of Achievements
that are tracking the same thing.
A Chain looks like this:
namespace App\Achievements\Chains; use App\Achievements\FirstPost; use App\Achievements\TenPosts; use App\Achievements\FiftyPosts; use ITVOLAND\Achievements\AchievementChain; class PostChain extends AchievementChain { public function chain() { return [new FirstPost(), new TenPosts(), new FiftyPosts()]; } }
For more details on the usage of Chain
, refer to the
Chains - Introduction part of this documentation.
Laravel Achievements is open-source software licensed under the MIT License.
COPYRIGHT 2017-2019 Gabriel Simonetti
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.