I regularly see articles like "How to use Repository pattern with Eloquent". The usual contents of them: let's create PostRepositoryInterface interface, EloquentPostRepository class, bind them nicely in DI container and use them instead of the standard Eloquent save and find methods.
Why this pattern is needed is not written at all ("This is a pattern! Isn’t it enough?"). Sometimes they write something about possible database engine change (which happens very often in every project), as well as testing and mock+stubs. The benefits of introducing this pattern into a regular Laravel project are difficult to grasp.
Let's try to analyze it and find some benefits. Repository pattern allows to abstract from a specific storage (which is usually a database), providing an abstract concept of collection of entities.
Examples with Eloquent Repository are divided into two types:
An example (I found it in one article):
<?php
interface FaqRepository
{
public function all($columns = array('*'));
public function newInstance(array $attributes = array());
public function paginate($perPage = 15, $columns = array('*'));
public function create(array $attributes);
public function find($id, $columns = array('*'));
public function updateWithIdAndInput($id, array $input);
public function destroy($id);
}
class FaqRepositoryEloquent implements FaqRepository
{
protected $faqModel;
public function __construct(Faq $faqModel)
{
$this->faqModel = $faqModel;
}
public function newInstance(array $attributes = array())
{
if (!isset($attributes['rank'])) {
$attributes['rank'] = 0;
}
return $this->faqModel->newInstance($attributes);
}
public function paginate($perPage = 0, $columns = array('*'))
{
$perPage = $perPage ?: Config::get('pagination.length');
return $this->faqModel
->rankedWhere('answered', 1)
->paginate($perPage, $columns);
}
public function all($columns = array('*'))
{
return $this->faqModel->rankedAll($columns);
}
public function create(array $attributes)
{
return $this->faqModel->create($attributes);
}
public function find($id, $columns = array('*'))
{
return $this->faqModel->findOrFail($id, $columns);
}
public function updateWithIdAndInput($id, array $input)
{
$faq = $this->faqModel->find($id);
return $faq->update($input);
}
public function destroy($id)
{
return $this->faqModel->destroy($id);
}
}
all, find, paginate methods return Eloquent-objects, however create, updateWithIdAndInput takes an arrays. The name updateWithIdAndInput says that this "repository" will be used only for CRUD operations. No normal business logic is assumed, but I'll try to implement the simplest:
<?php
class FaqController extends Controller
{
public function publish($id, FaqRepository $repository)
{
$faq = $repository->find($id);
//...Some check with $faq->...
$faq->published = true;
$repository->updateWithIdAndInput($id, $faq->toArray());
}
}
Without repository:
<?php
class FaqController extends Controller
{
public function publish($id)
{
$faq = Faq::findOrFail($id);
//...Some check with $faq->...
$faq->published = true;
$faq->save();
}
}
Much easier! Why this abstraction should be introduced to project? It only makes it more complicated.
An example of pure Eloquent repository (also found in one article):
<?php
interface PostRepositoryInterface
{
public function get($id);
public function all();
public function delete($id);
public function save(Post $post);
}
class PostRepository implements PostRepositoryInterface
{
public function get($id)
{
return Post::find($id);
}
public function all()
{
return Post::all();
}
public function delete($id)
{
Post::destroy($id);
}
public function save(Post $post)
{
$post->save();
}
}
This implementation is more like what the Repository pattern description says. The implementation of simple logic looks a bit more natural:
<?php
class FaqController extends Controller
{
public function publish($id, PostRepositoryInterface $repository)
{
$post = $repository->find($id);
//... some checks with $post->...
$post->published = true;
$repository->save($post);
}
}
However, the implementation of Repository pattern for blog posts is just a toy for kids. Let's try something more complicated. Simple entity with sub-entities. For example, a poll with possible options. The poll creation case. Two ways:
The same question: why?
It's not possible to get an abstraction from database more than Eloquent gives.
Unit testing? Here is an example of a possible unit test from my book - https://gist.github.com/adelf/a53ce49b22b32914879801113cf79043
Very few people will enjoy making such a huge unit tests for simple actions like poll creation. I am pretty sure that these tests will be abandoned. No one wants to support them (I was on a project with such tests, I know what I'm talking about). It is much easier and optimal to focus on functional testing, especially if it's an API project.
If the business logic is so complex that developers really want to cover it with unit tests, then it is better to take a data mapper library like Doctrine and completely extract the business logic from the rest of the application. Unit testing will be 10 times easier.
If you are using an Eloquent and want to play with design patterns, in the next article I will show how you can partially use the Repository pattern and get some benefit from it.