Pare de falar sobre o modelo de repositório com Eloquent

Eu vejo regularmente artigos no estilo "Como usar o modelo de repositório com o Eloquent" (um dos quais entrou em um resumo recente do PHP ). Seu conteúdo usual: vamos criar uma interface PostRepositoryInterface , classe EloquentPostRepository , vinculá-los agradavelmente ao contêiner de dependência e usar save and find ao invés dos métodos Ekovent padrão.


Por que esse modelo é necessário, às vezes, eles não escrevem nada ("Este é um modelo ! Não é suficiente?"); Às vezes, eles escrevem algo sobre uma possível alteração no banco de dados (uma ocorrência muito comum em todos os projetos), bem como sobre testes e stubs de mocha. Os benefícios de introduzir esse modelo em um projeto regular do Laravel nesses artigos são difíceis de capturar.


Vamos tentar descobrir o que é o quê? O modelo do Repositório permite abstrair de um sistema de armazenamento específico (que geralmente temos como banco de dados), fornecendo um conceito abstrato de uma coleção de entidades.


Os exemplos com o Repositório Eloquent são divididos em dois tipos:


  1. Variação de matriz eloquente dupla
  2. Repositório Pure Eloquent

Variação de matriz eloquente dupla


Um exemplo do primeiro (extraído de um artigo aleatório):


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

Os métodos all , find , paginate retornam objetos Eloquent, no entanto, create , updateWithIdAndInput estão aguardando uma matriz.


O nome updateWithIdAndInput significa que esse "repositório" será usado apenas para operações CRUD.


Nenhuma lógica comercial normal é esperada, mas tentaremos implementar o mais simples:


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

E se sem um repositório:


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

Duas vezes mais fácil.


Por que introduzir uma abstração no projeto que apenas a complicará?


  • Teste de unidade?
    Todo mundo sabe que um projeto CRUD regular no Laravel é coberto por testes de unidade um pouco mais de 100%.
    Mas discutiremos o teste de unidade um pouco mais tarde.
  • Por uma questão de poder alterar o banco de dados?
    Mas o Eloquent já fornece várias opções de banco de dados.
    Usar entidades Eloquent para uma base não suportada para um aplicativo que contém apenas lógica CRUD será uma dor e uma perda de tempo.
    Nesse caso, o repositório, que retorna uma matriz PHP pura e aceita apenas matrizes também, parece muito mais natural.
    Ao remover o Eloquent, obtemos uma abstração real do data warehouse.

Repositório Pure Eloquent


Um exemplo de repositório com trabalho apenas com Eloquent (também encontrado em um artigo):


 <?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(); } } 

Não vou repreender o sufixo desnecessário Interface neste artigo.


Essa implementação é um pouco mais parecida com o que a descrição do modelo diz.


A implementação da lógica mais simples parece um pouco mais natural:


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

No entanto, implementar um repositório para as postagens mais simples do blog é um brinquedo para as crianças se deliciarem.


Vamos tentar algo mais complicado.


Uma entidade simples com subentidades. Por exemplo, uma pesquisa com possíveis respostas (votação regular no site ou no chat).


Caso de criar o objeto dessa pesquisa. Duas opções:


  • Crie PollRepository e PollOptionRepository e use os dois.
    O problema com esta opção é que a abstração não deu certo.
    Uma pesquisa com possíveis respostas é uma entidade e seu armazenamento no banco de dados deve ter sido implementado por uma classe PollRepository .
    PollOptionRepository :: delete não será fácil, porque ele precisará de um objeto Poll para entender se essa opção de resposta pode ser excluída (porque se a pesquisa tiver apenas uma opção, não será uma pesquisa).
    E o modelo de repositório não implica a implementação da lógica de negócios dentro do repositório.
  • Dentro do PollRepository, adicione os métodos saveOption e deleteOption .
    Os problemas são quase os mesmos. A abstração do armazenamento acaba sendo algum tipo de desprezível ... as opções de resposta precisam ser tomadas separadamente.
    Mas e se a essência for ainda mais complexa? Com várias outras subentidades?

Surge a mesma pergunta: por que isso é tudo?
Obtenha mais abstração do sistema de armazenamento do que o Eloquent fornece - não funcionará.


Teste de unidade?


Aqui está um exemplo de um possível teste de unidade do meu livro - https://gist.github.com/adelf/a53ce49b22b32914879801113cf79043
Realizar testes de unidade tão grandes para as operações mais simples não é suficiente para ninguém se divertir.


Tenho quase certeza de que esses testes no projeto serão abandonados.


Ninguém quer apoiá-los. Eu estava em um projeto com esses testes, eu sei.


É muito mais fácil e mais correto focar nos testes funcionais.
Especialmente se for um projeto de API.


Se a lógica de negócios é tão complexa que você realmente deseja cobri-la com testes, é melhor usar uma biblioteca de mapeadores de dados como o Doctrine e separar completamente a lógica de negócios do restante do aplicativo. O teste de unidade será 10 vezes mais fácil.


Se você realmente deseja entrar em padrões de design em seu projeto Eloquent, no próximo artigo mostrarei como você pode aplicar parcialmente o modelo de Repositório e obter benefícios com ele.

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


All Articles