Jul 08, 2013
Table of contents:
Last week I looked at setting up your first Controller in Laravel 4. Controllers are what dictate how data is transferred between your Models and Views and vice versa. We set up our first RESTful controller and I described what each of the methods should be used for.
This week’s tutorial is all about creating Controllers that are flexible. Your Controllers are going to be one of the key components of your application and so you need to ensure that you build them so that if future circumstances change you don’t have to completely rewrite them.
To make the Controllers flexible, I’m going to use Repositories to abstract the database layer away.
This might seem a little bit confusing, but stick with me, it will all make sense by the end of this post.
So to really understand what I’m trying to achieve here, first I’ll give you an example of the situation I’m trying to avoid.
For example, we have the following index method in the UserController.php
file:
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
// Return all Users
return User::all();
}
This method simply returns a list of all users from out application.
However, there is a problem with this:
It’s not flexible - User::all()
is tied to Laravel’s Eloquent ORM. What would you do if you needed to switch databases to use Redis or Mongo instead? Well, you would have to find all of the instances that you used Eloquent in your application and update them. This isn’t very flexible in a larger application.
This might not seem like big problem, but trust me it is! You might be thinking, “hey, as long as it works, it’s all good right?”. Well building a solid application foundation is really not that hard and Laravel makes it really easy to solve both of these problems. There’s no need to tightly couple your application to one particular ORM or database if you don’t have to. You should seize any opportunity to build future flexibility into your application!
In order to cleanly separate our Controllers from the database, we are going to abstract that interaction into repositories. A repository is simply an interface between two things.
So instead of referencing Eloquent directly, we can reference UserRepository
. We can then bind UserRepository
to EloquentUserRepository
so that Laravel knows that whenever we mention UserRepository
we want an instance of EloquentUserRepository
.
Now that we have abstracted the database layer into repositories it makes it much easier to switch database ORM.
For example, if you wanted to use Mongo instead, you would simply create a MongoUserRepository
and bind UserRepository
to it rather than EloquentUserRepository
.
Now whenever Laravel wants a UserRepository
it will return MongoUserRepository
.
This means that you don’t have to change any of the code in your Controllers!
As I mentioned above, Repositories are simply an interface between two things. You can think of them as a contract that states that certain methods will be made available when using this interface.
For example:
You might have the following UserRepository
:
interface UserRepository {
public function all() {}
public function find() {}
}
And the following EloquentUserRepository
:
class EloquentUserRepository implements UserRepository
{
public function all()
{
return User::all();
}
public function find($id)
{
return User::find($id);
}
}
The UserRepository
is just an interface for the EloquentUserRepository
it doesn’t care what method of storing data is used, all it cares about is that those methods are available.
Now in your Controller you can use the UserRepository
interface as an abstraction from the database layer.
So hopefully that all makes sense to you. We are basically just making ORM specific classes and then using an interface in the Controller as a way of talking to the database. The interface is simply the connection between the Controller and the Repository.
In order for our application to use Repositories, first we need to set things up.
We are going to need:
UserRepository
EloquentUserRepository
UserRepository
and EloquentUserRepository
If you remember back to building your first Laravel package, we can use Service Providers to bind things together. Service Providers are just like bootstrap classes that allow you to set things up in a certain way.
As with a lot of things, you can get away with placing any of these things in a lot of random areas in your project. However, it’s pretty bad practice to just dump stuff in a random file or leave things in the wrong directory. As your project gets bigger, things will become a mess and everything will be unmaintainable.
Instead, I’m going to create a new directory called app/lib
to store all of this kind of stuff.
In order for this directory to be included in the autoload, you also need to add it to your composer.json
file.
In the classmap
array, add your new directory:
{
"autoload": {
"classmap": [
// —
"app/lib"
]
}
Run the following command in Terminal to update your autoload classmap:
$ composer dump-autoload
Next, in the app/lib
directory, I’m going to create another directory call Cribbb
to keep all of the Cribbb specific things together.
Next, I’m going to create another directory under app/lib/Cribbb
called Storage
to keep all of my database repositories together. And finally, I’m going to separate each resource into its own directory.
So my final directory structure is: app/lib/Cribbb/Storage/User
.
The first thing to create is the UserRepository.php
interface.
<?php namespace Cribbb\Storage\User;
interface UserRepository
{
public function all();
public function find($id);
public function create($input);
}
Interfaces are extremely simple because all I’m doing here is declaring that these methods should be made available. In a future tutorial I will add the other methods I’m going to need to for updating or deleting, but for now we’ll keep it simple.
Next I will create the EloquentUserRepository.php
file. Remember, this is simply an abstraction from the database that implements UserRepository
:
<?php namespace Cribbb\Storage\User;
use User;
class EloquentUserRepository implements UserRepository
{
public function all()
{
return User::all();
}
public function find($id)
{
return User::find($id);
}
public function create($input)
{
return User::create($input);
}
}
Next I need to create the Service Provider which will bind the two repositories together.
In app/lib/cribbb/storage
create a new file called StorageServiceProvider.php
, and copy the following code:
<?php namespace Cribbb\Storage;
use Illuminate\Support\ServiceProvider;
class StorageServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(
"Cribbb\Storage\User\UserRepository",
"Cribbb\Storage\User\EloquentUserRepository"
);
}
}
If you remember back to my article on creating a Laravel 4 Package, the register
method is automatically called on the Service Provider. This allows you to bootstrap your files so everything is loaded correctly.
In this example, I’m binding the User Repository to the Eloquent User Repository. This means, whenever I want to use the User Repository, Laravel will automatically know that I want to use the Eloquent User Repository. If in the future I wanted to use Mongo instead, I would simply have to create a Mongo User Repository and update this binding.
Finally you need to make Laravel aware of this Service Provider by placing it in the providers
array under app/config/app.php
:
'providers' => array(
// —
'Cribbb\Storage\StorageServiceProvider'
),
Now we can start using the Repository in the User Controller. Open up UserController.php
and add the use
line to the top of your file:
use Cribbb\Storage\User\UserRepository as User;
class UserController extends BaseController
{
}
Next you need to create a __construct
method that injects an instance of the User Repository into the Controller and sets it to the $this->user
property:
public function __construct(User $user)
{
$this->user = $user;
}
And finally, you can set the index
method to return all of the users:
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
return $this->user->all();
}
Now all we need to do to ensure that everything is working correctly is to set up a route to hit so we can see what is being returned in the browser.
Open up your routes.php
file and copy the following to define a new route to the User Controller:
Route::resource("user", "UserController");
Now if you fire up the server and hit /user in your browser, you should see a JSON array displayed on the screen (as long as you actually have users in your database!).
Now if you ever need to switch ORM or database in the future, all you have to do is to create a new database Repository and update the bindings in your Service Provider.
For example, to switch to Mongo, all you would have to do is to copy the EloquentUserRepository.php
and create MongoUserRepository.php
. You would then replace the Eloquent code with Mongo code so that the same data is returned.
In your Service Provider, you would simply update the binding to use the Mongo repository instead of the Eloquent repository:
$this->app->bind(
"Cribbb\Storage\User\UserRepository",
"Cribbb\Storage\User\MongoUserRepository"
);
Now you have completely switched databases without having to change any of your Controller code!
Repositories allow you to create a flexible abstraction layer between your database and your Controller. Doing this enables you to separate those concerns and it prevents your Controllers being too tightly coupled with your Database.
Your Controllers don’t care what storage facility you are using to persist data, and so by using Repositories, you are able to make a clean abstraction.
This makes it beautifully simple to switch database types at some point in the future.
Next week I’m going to look at how you should structure your Controllers to make them easier to test. Again this is because your Controllers should only be concerned with their job and not what the database is doing.
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.