Apr 28, 2014
Table of contents:
Last week I looked at setting up a basic invitation system in Laravel 4. I looked at creating the invites
table, the model and the repository and I showed you how to very easily create a new invitation and check for valid invitations when a new user tries to sign up for your application.
This basic foundation has put the right components together, but there is still quite a bit missing from the system to make it robust and capable of meeting all of my requirements.
In this week’s tutorial I’m going to look at building on top of last week’s foundation. In today’s tutorial I’ll be looking at:
The first thing I will do is to clean up how the invitation code is generated. You might remember from last time that I was creating the code in the create()
method of the EloquentInviteRepository
. This did the job but it wasn’t ideal.
The invitation code should be generated only when the new invitation is created. This means it will never have to be updated so we don’t need to pollute any of the other code with references to it. Instead, the code should just be generated for us.
Fortunately, Eloquent fires events that allow you to hook on to the various stages of the model’s lifecycle. This means we can write code that should be run based upon what the model is doing.
You can read more about the Eloquent’s Model Events and what you have available to you in the documentation.
In this situation, I want to hook on to the creating
event to automatically generate the invitation code before the model is saved.
The first thing to do is to remove the code
from the fillable
array in the Invite
model. This will prevent the code from ever being mass-assigned:
/**
* Properties that can be mass assigned
*
* @var array
*/
protected $fillable = array('email');
Next I need to override the static boot
method to register my event:
/**
* Register the model events
*
* @return void
*/
protected static function boot()
{
parent::boot();
static::creating(function($model) {
$model->generateInvitationCode();
});
}
First you need to call the boot
method on the parent Model
class.
Next you can call the event method and pass in a closure. In this example, I’m calling the creating
method.
The closure accepts an instance of the $model
. I will then call the generateInvitationCode()
method so I don’t end up with lots of code in this closure.
Finally, my generateInvitationCode()
looks like this:
/**
* Generate an invitation code
*
* @return void
*/
protected function generateInvitationCode()
{
$this->code = bin2hex(openssl_random_pseudo_bytes(16));
}
As you can see, I simply set the randomly generated code to the code
property. There’s no need to call the save()
method as this will automatically called as part of the model’s lifecycle.
Now I can clean up the create()
method on the EloquentInviteRepository
as the code will be automatically generated:
/**
* Create
*
* @param array $data
* @return Illuminate\Database\Eloquent\Model
*/
public function create(array $data)
{
return $this->model->create($data);
}
One of the problems of the implementation from last week was, there was no validation to prevent someone from requesting multiple invites for the same email address.
In order to ensure that Cribbb will only accept valid data I need to insert a layer of validation before the invitation is created.
If you remember back to Advanced Validation as a Service for Laravel 4, I’ve already got a foundation for creating validation classes in place.
However, should this go in the Controller? Or in the Repository? Or somewhere else?
In this instance I’m going to create a new service class for creating invitations. The reason for this is, I’m going to need to have different ways of creating invitations, and so separating the logic into it’s own class makes more sense than trying to crowbar it into the repository or duplicating my efforts in the controller.
The invitation classes are their own component of Cribbb and so they deserve their own namespace. I’m going to keep all of these classes under the Cribbb\Inviters
namespace.
The class I will create is for requesting a new invite. This class will be called Requester
.
Here is the basic outline for Requester.php
:
<?php namespace Cribbb\Inviters;
use Illuminate\Support\MessageBag;
use Cribbb\Repositories\Invite\InviteRepository;
class Requester
{
/**
* Invite Repository
*
* @var Cribbb\Repositories\Invite\InviteRepository
*/
protected $inviteRepository;
/**
* MessageBag errors
*
* @var Illuminate\Support\MessageBag;
*/
protected $errors;
/**
* Create a new instance of the Invite Requester
*
* @param Cribbb\Repositories\Invite\InviteRepository $inviteRepository
* @return void
*/
public function __construct(InviteRepository $inviteRepository)
{
$this->inviteRepository = $inviteRepository;
$this->errors = new MessageBag();
}
/**
* Return the errors message bag
*
* @return Illuminate\Support\MessageBag
*/
public function errors()
{
return $this->errors;
}
}
As you can see from the code above, I’m injecting the InviteRepository
into this class and setting the instance as a class property. This means I no longer need to inject the repository into the controller.
I will also set up the $errors
property as a new instance of MessageBag
. I prefer this to be an empty instance of MessageBag
rather than setting it as null
because the class has a couple of nice public methods for working with an empty bag. You will also notice that I instantiate a new instance of MessageBag
inside the controller, rather than injecting it. This violates the dependency injection principle, but I think of the MessageBag
as an enhancement of the class, rather than a dependency, so I’m cool with that.
Next I need to create a validator class that will validate the request email address. Under Cribbb\Inviters
I will create a new directory called Validators
to hold my validator classes.
To validate email addresses, I will use the following class:
<?php namespace Cribbb\Inviters\Validators;
use Cribbb\Validators\Validable;
use Cribbb\Validators\LaravelValidator;
class EmailValidator extends LaravelValidator implements Validable
{
/**
* Validation rules
*
* @var array
*/
protected $rules = [
"email" => "required|email|unique:users,email|unique:invites,email",
];
}
If the above code doesn’t make sense, read my post on Advanced Validation as a Service for Laravel 4.
Now that I’ve got my validator class set up, I need to inject it into the Request
class. Due to the evolving nature of this kind of code, I will inject my Validable
class as an array into the class. This means should I want to add more Validable
instances, I won’t have to modify the Request
class.
My __construct()
method now looks like this:
/**
* An array of Validators
*
* @var array
*/
protected $validators;
/**
* Create a new instance of the Invite Requester
*
* @param Cribbb\Repositories\Invite\InviteRepository $inviteRepository
* @param array $validators
* @return void
*/
public function __construct(InviteRepository $inviteRepository, array $validators)
{
$this->inviteRepository = $inviteRepository;
$this->validators = $validators;
$this->errors = new MessageBag;
}
This might seem like an over-engineered solution, and for a lot of instances it probably is. If your class will never need to be modified for evolving business rules, then injecting the single class would be totally fine too.
Finally I can write the create()
method that will actually create the new invitation:
/**
* Create a new Invite
*
* @param array $data
* @return Illuminate\Database\Eloquent\Model
*/
public function create(array $data)
{
foreach ($this->validators as $validator) {
if ($validator instanceof Validable) {
if (! $validator->with($data)->passes()) {
$this->errors = $validator->errors();
}
} else {
throw new Exception("{$validator} is not an instance of Cribbb\Validiators\Validable");
}
}
if ($this->errors->isEmpty()) {
return $this->inviteRepository->create($data);
}
}
First I spin through all of the validators and check that they are an instance of Cribbb\Validators\Validable
. If any one of the array values is not an instance of Validable
I will throw an Exception
. You could argue that the Exception
is a bit overkill, but I think for instances where we are accepting data into the application, it’s better to be safe than sorry.
Next I check that the $data
meets the requirements of the rules of the validator. If it does not, I will add the errors to the $errors
class property.
Finally if there are no errors I will pass the data to the repository to create the new invitation.
The full Request
class looks like this:
<?php namespace Cribbb\Inviters;
use Exception;
use Cribbb\Validators\Validable;
use Illuminate\Support\MessageBag;
use Cribbb\Repositories\Invite\InviteRepository;
class Requester
{
/**
* Invite Repository
*
* @var Cribbb\Repositories\Invite\InviteRepository
*/
protected $inviteRepository;
/**
* An array of Validators
*
* @var array
*/
protected $validators;
/**
* MessageBag errors
*
* @var Illuminate\Support\MessageBag;
*/
protected $errors;
/**
* Create a new instance of the Invite Creator
*
* @param Cribbb\Repositories\Invite\InviteRepository $inviteRepository
* @param array $validators
* @return void
*/
public function __construct(
InviteRepository $inviteRepository,
array $validators
) {
$this->inviteRepository = $inviteRepository;
$this->validators = $validators;
$this->errors = new MessageBag();
}
/**
* Create a new Invite
*
* @param array $data
* @return Illuminate\Database\Eloquent\Model
*/
public function create(array $data)
{
foreach ($this->validators as $validator) {
if ($validator instanceof Validable) {
if (!$validator->with($data)->passes()) {
$this->errors = $validator->errors();
}
} else {
throw new Exception(
"{$validator} is not an instance of Cribbb\Validiators\Validable"
);
}
}
if ($this->errors->isEmpty()) {
return $this->inviteRepository->create($data);
}
}
/**
* Return the errors message bag
*
* @return Illuminate\Support\MessageBag
*/
public function errors()
{
return $this->errors;
}
}
Now that I’ve set up the Request
service class I can inject it into the Controller to replace the injected repository:
use Cribbb\Inviters\Requester;
class InviteController extends BaseController
{
/**
* The Invite Request service
*
* @var Cribbb\Inviters\Requester
*/
protected $requester;
/**
* Create a new instance of the InviteController
*
* @param Cribbb\Inviters\Requester
*/
public function __construct(Requester $requester)
{
$this->requester = $requester;
}
/**
* Create a new invite
*
* @return Response
*/
public function store()
{
$invite = $this->requester->create(Input::all());
if ($invite) {
// yay
}
// oh no
$this->requester->errors();
}
}
As you can see from the code above, this is a simple switch-a-roo of the repository for the service class. However, now we don’t have any weird logic in the controller, and we can report errors back if the input data did not meet the requirements.
Another problem the initial implementation had was protecting certain routes to only allow user’s with a valid invitation. This is required so I can limit who has access to the registration form.
In last week’s tutorial I simply checked for a valid invitation in the Controller method. This works, but it’s a kinda messy way of doing it. The valid invite check isn’t really the concern of the Controller method, and if I needed that same logic in another method on a different Controller I would have to duplicate my code.
Instead I can create a filter to ensure only users with a valid invitation are allowed to hit the route. If the user does not have a valid invitation I can just 404 the response because I can just pretend the registration route does not exist.
If you are new to Laravel filters, I would recommend that you also read How to use Laravel 4 Filters.
Here is my route filter:
/*
|————————————————————————-
| Invitation Filter
|————————————————————————-
|
| The invite filter will only allow requests that include a valid
| "code" as part of the query string to access the given route.
| If the request does not have a valid code in the query
| string a "404 Not Found" response will be returned
|
*/
Route::filter("invite", function () {
if (!Input::has("code")) {
App::abort(404);
}
$repository = App::make("Cribbb\Repositories\Invite\InviteRepository");
if (!$repository->getValidInviteByCode(Input::get("code"))) {
App::abort(404);
}
});
First I check to see if the request has a code
parameter. If the request does not have a code, we can just bail out here.
Next I resolve an instance of the InviteRepository
out of the IoC container. I then check to see if the code in the request is valid. If the code is not valid I can throw a 404 error.
Remember, a request is considered valid if the filter does not throw an exception or return a response.
Next in my RegisterContoller
I can delete the checking logic from last week and replace it with the beforeFilter
:
class RegisterController extends BaseController
{
/**
* Create a new instance of the RegisterController
*
* @return void
*/
public function __construct()
{
$this->beforeFilter("invite", ["only" => "index"]);
}
/**
* Display the form for creating a new user
*
* @return View
*/
public function index()
{
return "Sign up here";
}
}
Now if you try to hit the /register
route, you should be returned a 404 Not Found
exception. Next, sign up with a valid email and grab the code from the database. Now if you try to hit /register?code=your_code
you should be allowed to hit the index
method on the controller.
In part 1 of building an advanced invitation system, I looked at leveraging Eloquent’s event lifecycle to automatically generate invitation codes, and setting up a service class to deal with the logic of validating and creating a new invitation.
I think the thing to take away from this tutorial is, how much easier your life will be if you fence off bits of functionality so you don’t have to repeat yourself and you don’t have to rethink about how something should be implemented.
Generating the invitation codes should only happen when the invite is first created and should just be taken care for us just like the auto-incrementing id. None of the code for working with an invitation should need to know how to generate a code.
The question of should I put this in the Controller, the Repository or should I create a new Service class comes up often. My response is usually, “well, it depends”. In this example, I think you definitely do need a separate service class. In my case, there will be multiple ways for creating invitations in my application and the rules around what is considered “valid” data required for a new invitation is also likely to change. Hopefully this was a solid example of how to make the decision that you can use in your applications.
Over the next couple of weeks I will look at how existing users can invite new users, how to create a queue jump system and how to build a family tree that will record how new users were invited to the application.
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.