📜 ⬆️ ⬇️

Please stop talking about the repository template with Eloquent

I regularly see articles in the style of "how to use the repository template with Eloquent" (one such got into a recent PHP digest ). The usual contents of them: let's create the PostRepositoryInterface interface, the EloquentPostRepository class, we will beautifully bind them in the dependency container and use the save and find methods instead of the standard eloquent methods.


Why this template is sometimes needed is not written at all (“This is the same template ! Isn’t it enough?”), Sometimes they write something about a possible database change (a very frequent occurrence in every project), as well as testing and moki-stubs. The benefits of introducing such a template into a regular Laravel project in such articles are difficult to grasp.


Let's try to figure out what's what? The Repository template allows you to abstract from a specific storage system (which we usually represent 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


Example one (taken from random 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); } } 

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


The very name updateWithIdAndInput says that this "repository" will be used only for CRUD operations.


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


 <?php class FaqController extends Controller { public function publish($id, FaqRepository $repository) { $faq = $repository->find($id); //...-   $faq->... $faq->published = true; $repository->updateWithIdAndInput($id, $faq->toArray()); } } 

And if without a repository:


 <?php class FaqController extends Controller { public function publish($id) { $faq = Faq::findOrFail($id); //...-   $faq->... $faq->published = true; $faq->save(); } } 

Two times easier.


Why enter into the project abstraction, which only complicate it?



Pure Eloquent Repository


An example of a repository with work only with Eloquent (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(); } } 

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


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


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


 <?php class FaqController extends Controller { public function publish($id, PostRepositoryInterface $repository) { $post = $repository->find($id); //...-   $post->... $post->published = true; $repository->save($post); } } 

However, the implementation of the repository for the simplest blog posts is a toy for kids to indulge in.


Let's try something more complicated.


Simple essence with subentities. For example, a survey with possible answers (normal voting on the site or in a chat).


Case create an object of such a survey. Two options:



The same question arises: why all this?
Get a great abstraction from the storage system, which gives Eloquent - will not work.


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 doing such huge unit tests for simple operations.


I am pretty 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 I 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 are in the Eloquent project and really want to indulge in design patterns, in the next article I will show how you can partially apply the Repository pattern and get some benefit from it.


')

Source: https://habr.com/ru/post/444688/


All Articles