Deje de hablar sobre la plantilla de repositorio con Eloquent

Regularmente veo artículos en el estilo de "Cómo usar la plantilla de repositorio con Eloquent" (uno de los cuales entró en un resumen reciente de PHP ). Su contenido habitual: creemos una interfaz PostRepositoryInterface , clase EloquentPostRepository , únalas en el contenedor de dependencias y use guardar y buscar en lugar de los métodos Ekovent estándar.


¿Por qué se necesita esta plantilla? A veces no escriben nada ("¡Esta es una plantilla ! ¿No es suficiente?"), A veces escriben algo sobre un posible cambio en la base de datos (una ocurrencia muy común en cada proyecto), así como sobre pruebas y mocha-stubs. Los beneficios de introducir dicha plantilla en un proyecto regular de Laravel en dichos artículos son difíciles de captar.


Tratemos de averiguar qué es qué. La plantilla de repositorio le permite hacer un resumen de un sistema de almacenamiento específico (que generalmente tenemos como base de datos), proporcionando un concepto abstracto de una colección de entidades.


Los ejemplos con el repositorio elocuente se dividen en dos tipos:


  1. Variación de matriz doble elocuente
  2. Repositorio elocuente puro

Variación de matriz doble elocuente


Un ejemplo de lo primero (tomado de un artículo aleatorio):


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

Los métodos all , find , paginate devuelven objetos Eloquent, sin embargo create , updateWithIdAndInput están esperando una matriz.


El nombre updateWithIdAndInput en sí mismo significa que este "repositorio" se usará solo para operaciones CRUD.


No se espera una lógica comercial normal, pero intentaremos implementar la más simple:


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

Y si sin un repositorio:


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

Dos veces más fácil.


¿Por qué introducir una abstracción en el proyecto que solo lo complicará?


  • Prueba de unidad?
    Todo el mundo sabe que un proyecto CRUD normal en Laravel está cubierto por pruebas unitarias un poco más del 100%.
    Pero discutiremos la prueba de la unidad un poco más tarde.
  • ¿Por el hecho de poder cambiar la base de datos?
    Pero Eloquent ya ofrece varias opciones de base de datos.
    El uso de entidades elocuentes para una base no compatible para una aplicación que solo contiene lógica CRUD será una molestia y una pérdida de tiempo.
    En este caso, el repositorio, que devuelve una matriz PHP pura y acepta solo matrices también, parece mucho más natural.
    Al eliminar Eloquent, obtenemos una abstracción real del almacén de datos.

Repositorio elocuente puro


Un ejemplo de un repositorio con trabajo solo con Eloquent (también encontrado en un artículo):


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

No voy a regañar la interfaz innecesaria sufijo en este artículo.


Esta implementación es un poco más parecida a lo que dice la descripción de la plantilla.


La implementación de la lógica más simple parece un poco más natural:


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

Sin embargo, implementar un repositorio para las publicaciones de blog más simples es un juguete para los niños.


Probemos algo más complicado.


Una entidad simple con subentidades. Por ejemplo, una encuesta con posibles respuestas (votación regular en el sitio o en el chat).


Caso de crear el objeto de dicha encuesta. Dos opciones:


  • Cree PollRepository y PollOptionRepository y use ambos.
    El problema con esta opción es que la abstracción no funcionó.
    Una encuesta con posibles respuestas es una entidad y su almacenamiento en la base de datos debería haber sido implementado por una clase PollRepository .
    PollOptionRepository :: delete no será fácil, porque necesitará un objeto Poll para comprender si esta opción de respuesta se puede eliminar (porque si la encuesta solo tiene una opción, no será una encuesta).
    Y la plantilla de repositorio no implica la implementación de la lógica de negocios dentro del repositorio.
  • Dentro del PollRepository agregue los métodos saveOption y deleteOption .
    Los problemas son casi los mismos. La abstracción del almacenamiento resulta ser una especie de basura ... las opciones de respuesta deben tomarse por separado.
    Pero, ¿y si la esencia es aún más compleja? ¿Con un montón de otras subentidades?

Surge la misma pregunta: ¿por qué es todo esto?
Obtenga más abstracción del sistema de almacenamiento de lo que ofrece Eloquent : no funcionará.


Prueba de unidad?


Aquí hay un ejemplo de una posible prueba unitaria de mi libro : https://gist.github.com/adelf/a53ce49b22b32914879801113cf79043
Hacer pruebas unitarias tan grandes para las operaciones más simples no es suficiente para que cualquiera lo disfrute.


Estoy casi seguro de que tales pruebas en el proyecto serán abandonadas.


Nadie quiere apoyarlos. Estaba en un proyecto con tales pruebas, lo sé.


Es mucho más fácil y correcto centrarse en las pruebas funcionales.
Especialmente si es un proyecto API.


Si la lógica de negocios es tan compleja que realmente desea cubrirla con pruebas, entonces es mejor tomar una biblioteca de mapeador de datos como Doctrine y separar completamente la lógica de negocios del resto de la aplicación. Las pruebas unitarias serán 10 veces más fáciles.


Si realmente desea disfrutar de patrones de diseño en su proyecto Eloquent, en el próximo artículo le mostraré cómo puede aplicar parcialmente la plantilla de Repository y obtener beneficios de ella.

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


All Articles