In this article I’ll try to explain how to use the repository pattern in Laravel framework and why it’s useful.
Let’s start with a simple example. Here’s what a typical Laravel controller looks like:
<?php
class PostsController extends Controller {
public function index() {
$posts = Post::all();
return View::make('posts.index', compact('posts'));
}
}
This looks good in most cases but when the application grows and things like testability and scalability become important, the code above is not really satisfactory.
So, what problems are introduced here?
1. Coupling
First of all, the above code couples the controller with the Post model.
Generally, a tight coupling means that a single change in a single module can lead to changes in other modules, so, before doing any change, a developer is required to understand the whole system.
Tightly coupled applications are not flexible and in most cases the cost of changes is much higher than the cost of development.
Just imagine how hard it will be to migrate your application from MySQL to another database management system if there are such dependencies in every single controller.
2. Hard-coded database query
Post::all()
works fine when thre are only 10 or 20 posts in our database. But what will happen when the company becomes popular and publishes tens of posts every single day? In a very short time we’ll have hundreds or even thousands of blog posts and this will make our application slow.
To avoid this, we’ll have to rewrite the code and use something like this:
Post::take($x)->skip($y)->get();
Looks easy but do you remember that models are reusable? If we’ve already used the hard-coded Post::all()
query in several places, all of these have to be rewritten by hand. This is just a simple example and things can be much trickier in a real project. Of course, this violates the DRY (Don’t Repeat Yourself) principle.
3. Fat controllers
As I mentioned above, in real projects we hardly use Post::all()
. In fact, chances are that the code looks like something similar to the following snippet:
if ($somethingHappened) {
$posts = Post::recent()
->orderBy('created_at', 'desc')
->take($x)
->skip($y)
->blah()->blah()->blah()
->get();
} else {
$posts = Post::whereNothingHappened(1)
->orderBy('created_at', 'desc')
->take($x)
->skip($y)
->bleh()->bleh()->bleh()
->get();
}
This bloats our controllers and, again, we’ll have to repeat all this code in different places just to get a list of posts.
4. Testability
Good programmers write tests and most of them have a habit to ask themselves: “How do I test this?”.
This article is not about testing, so I don’t want to dive in too deeply, but the code I showed above is not easy to test. One can actually do this involving such things as Aspect Mock, but, from my point of view, it’s always better to avoid extra complexity.
And it’s actually possible.
The solution
As you have probably guessed, the repository pattern helps us solve all the problems stated above.
A typical repository looks like this:
<?php namespace Example\Repositories;
class PostRepository {
public function getPosts() {
return Post::all();
}
}
And the controller now looks like this:
<?php
use Example\Repositories\PostRepository;
class PostsController extends Controller {
public $repository;
public function __construct(PostRepository $repository) {
$this->repository = $repository;
}
public function index() {
$posts = $this->repository->getPosts();
return View::make('posts.index', compact('posts'));
}
}
Note that Laravel is smart enough to inject an instance of the PostRepository
into the controller automagically
A simple wrapper around our model helps us increase testability because now we can inject a basic repository mock into a controller. The name of this technique is Dependency Injection and with this approach we can test the class without Aspect Mock or any other extra tool.
Now our controllers are in a much better shape. No need to write complex Eloquent queries in them because we can move them to our repository.
We don’t have to repeat ourselves anymore. When the logic changes, we just change the code in the repository.
But this code is still not perfect. Imagine that we’ll need to change a database layer from MySQL to, say, MongoDB. The current code is much better than the initial one, but should we replace all the MySQL code in repositories with completely new MongoDB code? We can, but there’s a better option: we can create a separate repository for MongoDB and switch between implementations as many times as we want.
Dependency Inversion
In the code above we are coupling our controller again. This time with a particular repository. This means that if we create a separate repository for MongoDB, we’ll be required to change the following line in every single place where we use the repository:
use Example\Repositories\PostRepository;
To avoid this, let us create a contract or, as we call it in PHP, an interface:
<?php namespace Example\Repositories;
interface PostRepositoryInterface {
public function getPosts();
}
Now every single post repository must implement this interface:
<?php namespace Example\Repositories;
class MongoPostRepository implements PostRepositoryInterface {
public function getPosts() {
//mongo specific implementation
}
}
The controller now should look like this:
<?php
use Example\Repositories\PostRepositoryInterface;
class PostsController extends Controller {
public $repository;
public function __construct(PostRepositoryInterface $repository) {
$this->repository = $repository;
}
public function index() {
$posts = $this->repository->getPosts();
return View::make('posts.index', compact('posts'));
}
}
Laravel was smart enough to inject an instance of the PostRepository last time and it’s smart enough to try to inject an instance of the PostRepositoryInterface now. But wait, interfaces are not instantiable! Therefore, we have to help Laravel and provide some instructions. For that reason we’ll create a Service Provider with the following contents:
<?php namespace Example;
use Illuminate\Support\ServiceProvider as BaseServiceProvider;
use Example\Repositories\MongoPostRepository;
class RepositoryServiceProvider extends BaseServiceProvider
{
public function register()
{
$this->app->bind(
'Example\Repositories\PostRepositoryInterface',
'Example\Repositories\MongoPostRepository'
);
}
}
This code basically says that “whenever I request an instance of the PostRepositoryInterface, give me an instance of the MongoPostRepository”.
As soon as we register this service provider in the app.php
config, Laravel will be able create an instance of the PostRepositoryInterface
.
If we change our minds and want to go back to MySQL implementation, we just have to replace a single line of code in the service provider.
Conclusion
While being pretty simple to implement, the repository pattern gives a huge benefit. Most people think that they’ll never need this kind of flexibility, they don’t plan to write tests for their small project, but it’s a bad approach.
One never knows when the project will become bigger and when it’ll gain more popularity. A little effort of implementing repositories guarantees that growth and rise in popularity of a project can be smoother and less painful.
Hope you enjoyed the article and it was helpful. To make this even more practically useful, check out my Scope Applicator package which makes data filtering and sorting easy (and it works with repositories!).