cult3

Creating an advanced Invitation System in Laravel 4 Part 3

May 12, 2014

Table of contents:

  1. How this is going to work
  2. Updating the database tables
  3. Updating the model
  4. Storing the referral code
  5. Use referral_code when requesting a new invitation
  6. Testing
  7. The invitation family tree
  8. Conclusion

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!

  1. Creating an advanced Invitation System in Laravel 4 Part 1
  2. 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 the invites 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 existing boot() 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 https://cribbb.com?referral=referral_code. To keep things simple, I’m only going to store the referral_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 the referral_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 the users 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 your invites 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.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.