Please stop talking about the Repository template with Eloquent

Original author: Adel F
  • Transfer

I regularly see articles in the style of "How to use the Repository template with Eloquent" (one of which got into a recent PHP digest ). Their usual content: let's create an interface PostRepositoryInterface , EloquentPostRepository class, nicely bind them in the dependency container and use save and find instead of the standard Ekovent methods .

Why this template is needed sometimes they don’t write at all ("This is a template ! Isn’t it enough?"), Sometimes they write something about a possible database change (a very common occurrence in every project), as well as about testing and mocha-stubs. The benefits of introducing such a template into a regular Laravel project in such articles are difficult to catch.

Let's try to figure out what's what? The Repository template allows you to abstract from a specific storage system (which we usually have as a database), providing an abstract concept of a collection of entities.

Examples with Eloquent Repository are divided into two types:

  1. Dual Eloquent-array variation
  2. Pure Eloquent Repository

Dual Eloquent-array variation

An example of the first (taken from a random article):

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);

The all , find , paginate methods return Eloquent objects, however create , updateWithIdAndInput are waiting for an array.

The name updateWithIdAndInput itself means that this "repository" will be used only for CRUD operations.

No normal business logic is expected, but we will try to implement the simplest:

        //...Какая-нибудь проверка с $faq->... 
        $faq->published = true;
        $repository->updateWithIdAndInput($id, $faq->toArray());

And if without a repository:

        $faq->published = true;

Two times easier.

Why introduce an abstraction into the project that will only complicate it?

  • Unit testing?
    Everyone knows that a regular CRUD project on Laravel is covered by unit tests a little more than 100%.
    But we will discuss unit testing a bit later.
  • For the sake of being able to change the database?
    But Eloquent already provides several database options.
    Using Eloquent entities for an unsupported base for an application that contains only CRUD logic will be a pain and a waste of time.
    In this case, the repository, which returns a pure PHP array and accepts only arrays too, looks much more natural.
    By removing Eloquent, we get a real abstraction from the data warehouse.

Pure Eloquent Repository

An example of a repository with work only with Eloquent (also found in one article):


I will not scold the unnecessary suffix Interface in this article .

This implementation is a bit more like what the template description says.

The implementation of the simplest logic looks a little more natural:

        //...Какая-нибудь проверка с $post->... 
        $post->published = true;

However, implementing a repository for the simplest blog posts is a toy for kids to indulge.

Let's try something more complicated.

A simple entity with sub-entities. For example, a survey with possible answers (regular voting on the site or in chat).

Case of creating the object of such a survey. Two options:

  • Create PollRepository and PollOptionRepository and use both.
    The problem with this option is that the abstraction did not work out.
    A survey with possible answers is one entity and its storage in the database should have been implemented by one PollRepository class .
    PollOptionRepository :: delete will not be easy, because he will need a Poll object to understand whether this answer option can be deleted (because if the poll has only one option, it will not be a poll).
    And the Repository template does not imply the implementation of business logic inside the repository.
  • Inside the PollRepository add saveOption and deleteOption methods .
    The problems are almost the same. The abstraction from storage turns out to be some kind of scumbag ... answer options need to be taken separately.
    But what if the essence is even more complex? With a bunch of other subentities?

The same question arises: why is this all?
Get more abstraction from the storage system than Eloquent gives - will not work.

Unit testing?

Here is an example of a possible unit test from my book -
There are few people who enjoy doing such huge unit tests for simple operations.

I am almost sure that such tests in the project will be abandoned.

No one wants to support them. I was on a project with such tests, I know.

It is much easier and more correct to focus on functional testing.
Especially if it is an API project.

If the business logic is so complex that you really want to cover it with tests, then it is better to take a data mapper library like Doctrine and completely separate the business logic from the rest of the application. Unit testing will be 10 times easier.

If you really want to indulge in design patterns in your Eloquent project, then in the next article I will show how you can partially apply the Repository template and get benefit from it.

Also popular now: