The Repository Pattern is very commonly used. Let's say you have a script like this:
<?php
$blog_posts_in_category = Posts::where("category_id", $category_id)
->orderBy("id","desc")
->where("is_published",true)
->get();
var_dump($blog_posts_in_category);
This isn't awful code (this specific line is quite easy to read). However what if:
- You end up duplicating the same line elsewhere in your code base?
- Or it is much more complicated and messy?
It is a much better idea to put that in a different class, so you can do something like this (which is much cleaner):
<?php
$postsRepo = new PostsRepo;
$blog_posts_in_category = $postsRepo->postsInCategory($category_id);
var_dump($blog_posts_in_category);
This is much cleaner, and much easier to duplicate and use elsewhere.
The PostsRepo class would look something like this:
<?php
class PostsRepo {
function postsInCategory($category_id) {
return Posts::where("category_id",$category_id)->orderBy("id","desc")->where("is_published",true)->get();
}
}
The Posts::where...
part is the same as from the first example. But it is now in its own class.
This has a few advantages:
- The original class (with the
$blog_posts_in_category =...
line) is much cleaner and easier to read. - The controller is much simpler - it has no idea what is happening in the repository (and this is good - it shouldn't really be up to the controller to be doing
where()->orderBy()->join()->paginate()
code) - You can easily use the same bit of code again without copy/pasting. You would just call the
postsInCategory()
method on aPostsRepo
object. - And if you read on you will see how it is much easier to change the implementation of the repo throughout your whole code base, and easier for testing.
My favourite way to structure most Laravel applications is to split it up into a few layers - controllers, which call services. And those services will make calls to repos. This makes testing so easy - when testing your services or controllers you can mock out the repository.
Using The Repository Pattern in Laravel Apps
A big advantage of using a framework such as Laravel is that you get so many features built into the core system. One of those is being able to automatically injecting classes via class hinting.
Let's make some changes to the above code.
To begin with, let's make an interface for the repository...
<?php
interface PostsRepoInterface {
function postsInCategory();
}
And make the PostsRepo class implement it.
<?php
class PostsRepo implements PostsRepoInterface {
// ( same code as last time here )
function postsInCategory($category_id) {
return Posts::where("category_id",$category_id)->orderBy("id","desc")->where("is_published",true)->get();
}
}
Then in our controllers (for example, a ViewPostsController
) you can get Laravel to automatically inject in the PostsRepo, by requiring a param that type hints the interface. (We have to tell Laravel what class to use when the interface is specified).
Binding PostsRepoInterface to PostsRepo
In your App Service Provider (app/Providers/AppServiceProvider.php
file) you should bind the interface to PostsRepo with this:
<?php
App::bind(PostsRepoInterface::class, PostsRepo::class)
(In the real world these would be namespaced)
Now in your controller (ViewPostsController
) you can type hint the interface, and Laravel will create a new PostsRepo object.
<?php
Class ViewPostsController extends Controller {
protected $postsRepo;
public function __construct(PostsRepoInterface $postsRepo) {
$this->postsRepo = $postsRepo;
}
public function viewCategory($category_id) {
$blog_posts_in_category = $this->postsRepo->postsInCategory($category_id);
var_dump($blog_posts_in_category);
}
}
So now you can do things like changing the App::bind()
code and instantly use a different repo everywhere in your code. The first version of this code (the first example above) used Eloquent's where()->orderBy()->get()
. The PostsRepo
class also uses this. But what you wanted to change to another source (maybe you went crazy and converted your DB to flat files), you could then make a new class PostsFilesRepo
, have it implement PostsRepoInterface
, and then change the App::bind()
to App::bind(PostsRepoInterface::class, PostsFlatFileRepo::class)
. Now Laravel will inject the PostsFlatFileRepo
into your controller (and no changes need to be made to your controller at all).
This also means that you can easily test things. Before (when you were statically calling the BlogPosts::where()...
) it would have been a bit hard to test. Now you can mock the PostsRepo, have it automatically injected into your controller and control the test much better.
Things to note:
- Make sure that every (public) method in PostsRepo is also in the interface. Then you can swap PostsRepo with anything else that implements PostsRepoInterface and know that all methods that will be called are covered. If you forgot to add a method (such as
PostsInCategory()
PHP would throw an error straight away, letting you know. - If you end up having different implementations of a repository then can consider using abstract classes to share common code)
- It also might have been a better idea if I had called the original
PostsRepo
something likePostsDBRepo
, as it might have made things clearer once I introduced the idea ofPostsFlatFileRepo
Comments →The Repository Pattern in PHP (and Laravel)