Creating an advanced Invitation System in Laravel 4 Part 3
The build up towards launching a new consumer web application is almost as important as the actual application itself. Launching to the world without any kind of groundswell will more often than not be a failure.
Building an invitation process pre-launch is certainly not a new idea. Many different types of applications have launched with a clever invite system to both success and failure.
In my opinion, one of the best features of a good invite system is the incentive for people who request an invite to share their link with their followers on Twitter and Facebook. By sharing their link they will get into the application quicker, and it’s always good to be the person who knows the new hot thing.
In this week’s tutorial I’m going to show you how to build this into the functionality of the Cribbb invitation system. If you haven’t already read the first two tutorials, go read them now!
- Creating an advanced Invitation System in Laravel 4 Part 1
- Creating an advanced Invitation System in Laravel 4 Part 2
How this is going to work
So just to be clear, here is how I envision the Cribbb invitation system will work.When a new user requests an invite, they submit their email address to get on the invitation list.
This will present them with a url that they can share on Twitter and Facebook to spread the word and jump the queue.
When someone clicks on the link, the referral code will be recorded in the session. If the person requests an invite too, the original invite will increase in rank.
Once I’m ready to start accepting new users, I will sort the
invites
table by the number of referrals to find the next batch of users to invite.Updating the database tables
The first thing I will do is to update theinvites
table with some additional columns:$table->string('invitation_code'); $table->string('referral_code'); $table->integer('referral_count')->default(0);Here I’ve added a
referral_code
string field and a referral_count
integer field that defaults to 0. You will also notice that I’ve renamed the code
field to invitation_code
to differentiate between the two codes. This meant I also had to update the getValidInviteByCode()
in the EloquentInviteRepository
class.Updating the model
Now that I need to generate two codes for the model when it is created instead of one, I can tweak the existingboot()
and generateCode()
methods:/** * Register the model events * * @return void */ protected static function boot() { parent::boot(); static::creating(function($model) { $model->invitation_code = $model->generateCode(); $model->referral_code = $model->generateCode(); }); } /** * Generate a code * * @return string */ protected function generateCode() { return bin2hex(openssl_random_pseudo_bytes(16)); }This should be pretty self explanatory as I’m simply calling the
generateCode()
method twice and assigning the returned value to the model’s class properties before it is saved to the database.Storing the referral code
When a user requests an invite, they will be presented with a url in the form of http://cribbb.com?referral=referral_code. To keep things simple, I’m only going to store thereferral_code
in the session from the HomeController
:/** * Index * * @return View */ public function index() { $this->storeReferralCode(); return View::make('home.index'); } /** * Store Referral code * * @return void */ protected function storeReferralCode() { if(Input::has('referral')) { Session::put('referral_code', Input::get('referral')); } }If you wanted to check for a referral code in the query string and store it in the session for every page of your application, or even just a handful of pages you would be better of moving this to either the
App::before
filter or into the __construct()
method of the Controller class.Use referral_code when requesting a new invitation
Now that thereferral_code
will be stored in the session when a new user is referred, I need to update the Request
class for creating a new invitation.Firstly, in the
InviteController
update the store()
method to submit the referral_code
from the session if it is available:/** * Create a new invite * * @return Response */ public function store() { $invite = $this->requester->create(Input::all(), Session::get('referral_code', null)); }The
null
value as the second parameter to the get()
method is a default value if the value you are looking for is not available.Next I can update the
create()
method on the Requester
class:/** * Create a new Invite * * @param array $data * @return Illuminate\Database\Eloquent\Model */ public function create(array $data, $referral = null) { if($this->runValidationChecks($data)) { if($referral) { $referer = $this->inviteRepository->getBy('referral_code', $referral)->first(); if($referer) { $referer->increment('referral_count'); } } return $this->inviteRepository->create($data); } }Firstly I’ve set the
$referral
as null
by default so this method does not have to be hampered by requiring a $referral
.If the
$data
passes the validation checks the method will check to see if a $referral
has been passed. Next It will attempt to find the $referer
from the $inviteRepository
by the referral_code
.If a
$referer
is found, the referrals
counter is incremented.Finally the invitation is created in the
InviteRepository
and returned.Testing
To ensure that the process works correctly, I can use the following test:public function testRequestSentByReferral() { $requester = App::make('Cribbb\Inviters\Requester'); $invite1 = $requester->create(array('email' => 'name@domain.com.com')); $invite2 = $requester->create(array('email' => 'other@domain.com'), $invite1->referral_code); $invite1 = Invite::find($invite1->id); $this->assertEquals(1, count($invite1->referral_count)); }In this test I create an invitation and then use the
referral_code
to create a second invitation.The test here is to ensure that the invitation’s
referral_count
is incremented when it’s referal_code
is used to refer another user.The invitation family tree
The final aspect of the invitation system that I wanted to build was to be able to generate a “family tree” of how user’s were invited by other users. I really like the way in Dribbble you can see which users were invited by another user.A user will be invited by one user, and they might subsequently invited many other users. This is a classic one to many relationship, but we are linking two entities from the same table.
Add the invited_by
So the first thing to do is to add a column to theusers
to store the invited_by
id.$table->integer('invited_by')->nullable();This column will store the
id
of the user who invited the current user. A user will only have a invited_by
if they were invited by another user and so I will make this field nullable.Again as I mentioned last week, if you haven’t shipped your application yet, just throw this line into the current
users
table migration.Add the referrer_id
In order to know who invited the new user when they are registering for an account, I need to be able to track who give them the invitation. Add the following line to yourinvites
table migration:$table->integer('referrer_id')->nullable();Next I need to set the
referrer_id
when an existing users invites another user.In
Inviter.php
update the create()
method to merge in the referrer_id
form the User
entity:/** * Create a new Invite * * @param array User * @param array $data * @return Illuminate\Database\Eloquent\Model */ public function create(User $user, $data) { $data = array_merge($data, ['referrer_id' => $user->id]); foreach($this->policies as $policy) { if($policy instanceof Policy) { $policy->run($user); } else { throw new Exception("{$policy} is not an instance of Cribbb\Inviters\Policies\Policy"); } } if($this->runValidationChecks($data)) { return $this->inviteRepository->create($data); } }Finally I can update the test to ensure this functionality is working correctly:
public function testUserInviteAnotherUser() { $inviter = App::make('Cribbb\Inviters\Inviter'); $user = User::create(['username' => 'philipbrown', 'email' => 'name@domain.com']); $user->invitations = 1; $invite = $inviter->create($user, ['email' => 'other@domain.com']); $this->assertInstanceOf('Invite', $invite); $this->assertEquals(1, $invite->referrer_id); }In this test I’m asserting that the
referrer_id
has been set correctly.Now when a new user uses an invitation to register with Cribbb I can take the
referrer_id
and set the invited_by
column on the new user entity.Within Cribbb I probably use these family tree like structure to display how users know each other on a user’s profile page, although there are many other possible things you could do with this kind of data.
Conclusion
This is the final tutorial in the mini series of building an invitation system. Hopefully over the last couple of weeks you will have seen my iterative process for creating a process such as this.I think it’s very important to take these kinds of things one step at a time. In the first tutorial, my code was very basic, but it did do the job it was intending to do.
Over the last three weeks I’ve taken each part of the process and either updated it or introduced my desired functionality. I think when you try to bite off more than you can chew, you end up in the weeds of implementation details and you lose sight of what you are actually trying to accomplish.
By breaking it down into much smaller, achievable chunks we can make progress and end up with easier to understand and maintain code.
This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.
To view a full listing of the tutorials in this series, click here.
No comments:
Post a Comment