Veuillez arrĂȘter de parler du modĂšle de rĂ©fĂ©rentiel avec Eloquent

Je vois réguliÚrement des articles dans le style de "Comment utiliser le modÚle de référentiel avec Eloquent" (dont l'un est entré dans un récent résumé PHP ). Leur contenu habituel: créons une interface PostRepositoryInterface , classe EloquentPostRepository , liez-les bien dans le conteneur de dépendances et utilisez save and find au lieu des méthodes Ekovent standard.


Pourquoi ce modÚle est nécessaire, parfois ils n'écrivent pas du tout ("Ceci est un modÚle ! N'est-ce pas suffisant?"), Parfois ils écrivent quelque chose sur un éventuel changement de base de données (une occurrence trÚs courante dans chaque projet), ainsi que sur les tests et les mocha-stubs. Les avantages de l'introduction d'un tel modÚle dans un projet Laravel régulier dans de tels articles sont difficiles à saisir.


Essayons de comprendre ce qui est quoi? Le modÚle de référentiel vous permet d'abstraire d'un systÚme de stockage spécifique (que nous avons généralement comme base de données), fournissant un concept abstrait d'une collection d'entités.


Les exemples avec Eloquent Repository sont divisés en deux types:


  1. Variation double tableau éloquent
  2. Référentiel Pure Eloquent

Variation double tableau éloquent


Un exemple du premier (tiré d'un article aléatoire):


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

Les méthodes all , find , paginate renvoient des objets Eloquent, mais create , updateWithIdAndInput attendent un tableau.


Le nom updateWithIdAndInput lui-mĂȘme signifie que ce "rĂ©fĂ©rentiel" sera utilisĂ© uniquement pour les opĂ©rations CRUD.


Aucune logique métier normale n'est attendue, mais nous essaierons d'implémenter la plus 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()); } } 

Et si sans référentiel:


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

Deux fois plus facile.


Pourquoi introduire une abstraction dans le projet qui ne fera que la compliquer?


  • Tests unitaires?
    Tout le monde sait qu'un projet CRUD régulier sur Laravel est couvert par des tests unitaires un peu plus de 100%.
    Mais nous discuterons des tests unitaires un peu plus tard.
  • Pour pouvoir changer la base de donnĂ©es?
    Mais Eloquent propose déjà plusieurs options de base de données.
    L'utilisation d'entités Eloquent pour une base non prise en charge pour une application qui ne contient que la logique CRUD sera une douleur et une perte de temps.
    Dans ce cas, le référentiel, qui renvoie un tableau PHP pur et n'accepte que les tableaux, semble beaucoup plus naturel.
    En supprimant Eloquent, nous obtenons une véritable abstraction de l'entrepÎt de données.

Référentiel Pure Eloquent


Un exemple de référentiel fonctionnant uniquement avec Eloquent (également trouvé dans un 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(); } } 

Je ne réprimanderai pas l' interface de suffixe inutile dans cet article.


Cette implémentation ressemble un peu plus à ce que dit la description du modÚle.


L'implémentation de la logique la plus simple semble un peu plus naturelle:


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

Cependant, la mise en place d'un référentiel pour les articles de blog les plus simples est un jouet pour les enfants.


Essayons quelque chose de plus compliqué.


Une entitĂ© simple avec des sous-entitĂ©s. Par exemple, une enquĂȘte avec des rĂ©ponses possibles (vote rĂ©gulier sur le site ou en chat).


Cas de crĂ©ation de l'objet d'une telle enquĂȘte. Deux options:


  • CrĂ©ez PollRepository et PollOptionRepository et utilisez les deux.
    Le problÚme avec cette option est que l'abstraction n'a pas fonctionné.
    Une enquĂȘte avec des rĂ©ponses possibles est une entitĂ© et son stockage dans la base de donnĂ©es aurait dĂ» ĂȘtre implĂ©mentĂ© par une classe PollRepository .
    PollOptionRepository :: delete ne sera pas facile, car il aura besoin d'un objet Poll pour comprendre si cette option de rĂ©ponse peut ĂȘtre supprimĂ©e (car si le sondage n'a qu'une seule option, ce ne sera pas un sondage).
    Et le modÚle de référentiel n'implique pas l'implémentation de la logique métier à l'intérieur du référentiel.
  • Dans PollRepository, ajoutez les mĂ©thodes saveOption et deleteOption .
    Les problĂšmes sont presque les mĂȘmes. L'abstraction du stockage s'avĂšre ĂȘtre une sorte de scumbag ... les options de rĂ©ponse doivent ĂȘtre prises sĂ©parĂ©ment.
    Mais que faire si l'essence est encore plus complexe? Avec un tas d'autres sous-entités?

La mĂȘme question se pose: pourquoi tout cela?
Obtenez plus d'abstraction du systĂšme de stockage qu'Eloquent ne donne - ne fonctionnera pas.


Tests unitaires?


Voici un exemple d'un test unitaire possible de mon livre - https://gist.github.com/adelf/a53ce49b22b32914879801113cf79043
Faire de tels tests unitaires énormes pour les opérations les plus simples ne suffit pas à quiconque d'en profiter.


Je suis presque sûr que de tels tests dans le projet seront abandonnés.


Personne ne veut les soutenir. J'étais sur un projet avec de tels tests, je sais.


Il est beaucoup plus facile et plus correct de se concentrer sur les tests fonctionnels.
Surtout s'il s'agit d'un projet API.


Si la logique métier est si complexe que vous voulez vraiment la couvrir de tests, il est préférable de prendre une bibliothÚque de mappeur de données comme Doctrine et de séparer complÚtement la logique métier du reste de l'application. Les tests unitaires seront 10 fois plus faciles.


Si vous voulez vraiment vous adonner aux modÚles de conception dans votre projet Eloquent, dans l'article suivant, je montrerai comment vous pouvez appliquer partiellement le modÚle de référentiel et en tirer parti.

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


All Articles