Ich sehe regelmäßig Artikel im Stil von "Wie man die Repository-Vorlage mit Eloquent verwendet" (von denen einer in einen kürzlich erschienenen PHP-Digest aufgenommen wurde ). Ihr üblicher Inhalt: Erstellen wir eine Schnittstelle PostRepositoryInterface , EloquentPostRepository- Klasse, binden Sie sie gut in den Abhängigkeitscontainer und verwenden Sie save and find anstelle der Standardmethoden von Ekovent.
Warum diese Vorlage benötigt wird, schreiben sie manchmal überhaupt nicht ("Dies ist eine Vorlage ! Ist es nicht genug?"). Manchmal schreiben sie etwas über eine mögliche Datenbankänderung (ein sehr häufiges Ereignis in jedem Projekt) sowie über Tests und Mokka-Stubs. Die Vorteile der Einführung einer solchen Vorlage in ein reguläres Laravel-Projekt in solchen Artikeln sind schwer zu erfassen.
Versuchen wir herauszufinden, was was ist. Mit der Repository-Vorlage können Sie von einem bestimmten Speichersystem (das wir normalerweise als Datenbank haben) abstrahieren und ein abstraktes Konzept einer Sammlung von Entitäten bereitstellen.
Beispiele mit Eloquent Repository werden in zwei Typen unterteilt:
- Dual Eloquent-Array-Variante
- Reines eloquentes Repository
Dual Eloquent-Array-Variante
Ein Beispiel für den ersten (aus einem zufälligen Artikel entnommen):
<?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); } }
Die Methoden all , find und paginate geben eloquente Objekte zurück. Create , updateWithIdAndInput wartet jedoch auf ein Array.
Der Name updateWithIdAndInput selbst bedeutet, dass dieses "Repository" nur für CRUD-Operationen verwendet wird.
Es wird keine normale Geschäftslogik erwartet, aber wir werden versuchen, die einfachste zu implementieren:
<?php class FaqController extends Controller { public function publish($id, FaqRepository $repository) { $faq = $repository->find($id);
Und wenn ohne Repository:
<?php class FaqController extends Controller { public function publish($id) { $faq = Faq::findOrFail($id);
Zweimal einfacher.
Warum eine Abstraktion in das Projekt einführen, die es nur kompliziert?
- Unit Testing?
Jeder weiß, dass ein reguläres CRUD-Projekt auf Laravel durch Unit-Tests zu etwas mehr als 100% abgedeckt wird.
Aber wir werden etwas später auf Unit-Tests eingehen. - Um die Datenbank ändern zu können?
Eloquent bietet jedoch bereits mehrere Datenbankoptionen.
Die Verwendung eloquenter Entitäten für eine nicht unterstützte Basis für eine Anwendung, die nur CRUD-Logik enthält, ist schmerzhaft und Zeitverschwendung.
In diesem Fall sieht das Repository, das ein reines PHP-Array zurückgibt und auch nur Arrays akzeptiert, viel natürlicher aus.
Durch das Entfernen von Eloquent erhalten wir eine echte Abstraktion aus dem Data Warehouse.
Reines eloquentes Repository
Ein Beispiel für ein Repository, das nur mit Eloquent funktioniert (auch in einem Artikel enthalten):
<?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(); } }
Ich werde das unnötige Suffix Interface in diesem Artikel nicht schelten.
Diese Implementierung ähnelt eher der Beschreibung der Vorlage.
Die Implementierung der einfachsten Logik sieht etwas natürlicher aus:
<?php class FaqController extends Controller { public function publish($id, PostRepositoryInterface $repository) { $post = $repository->find($id);
Die Implementierung eines Repositorys für die einfachsten Blog-Beiträge ist jedoch ein Spielzeug für Kinder.
Versuchen wir etwas komplizierteres.
Eine einfache Entität mit Unterentitäten. Zum Beispiel eine Umfrage mit möglichen Antworten (regelmäßige Abstimmung auf der Website oder im Chat).
Fall der Erstellung des Objekts einer solchen Umfrage. Zwei Optionen:
- Erstellen Sie PollRepository und PollOptionRepository und verwenden Sie beide.
Das Problem bei dieser Option ist, dass die Abstraktion nicht funktioniert hat.
Eine Umfrage mit möglichen Antworten ist eine Entität, und ihre Speicherung in der Datenbank sollte von einer PollRepository- Klasse implementiert worden sein .
PollOptionRepository :: delete ist nicht einfach, da er ein Poll-Objekt benötigt, um zu verstehen, ob diese Antwortoption gelöscht werden kann (denn wenn die Umfrage nur eine Option hat, handelt es sich nicht um eine Umfrage).
Die Repository-Vorlage impliziert nicht die Implementierung der Geschäftslogik im Repository. - Fügen Sie im PollRepository die Methoden saveOption und deleteOption hinzu .
Die Probleme sind fast gleich. Die Abstraktion vom Speicher stellt sich als eine Art Drecksack heraus ... Antwortoptionen müssen separat gewählt werden.
Was aber, wenn die Essenz noch komplexer ist? Mit einer Reihe anderer Subentitäten?
Die gleiche Frage stellt sich: Warum ist das alles?
Holen Sie sich mehr Abstraktion vom Speichersystem als Eloquent gibt - wird nicht funktionieren.
Unit Testing?
Hier ist ein Beispiel für einen möglichen Komponententest aus meinem Buch - https://gist.github.com/adelf/a53ce49b22b32914879801113cf79043
Es reicht nicht aus, so große Unit-Tests für die einfachsten Operationen durchzuführen.
Ich bin fast sicher, dass solche Tests im Projekt abgebrochen werden.
Niemand will sie unterstützen. Ich war in einem Projekt mit solchen Tests, ich weiß.
Es ist viel einfacher und korrekter, sich auf Funktionstests zu konzentrieren.
Besonders wenn es sich um ein API-Projekt handelt.
Wenn die Geschäftslogik so komplex ist, dass Sie sie wirklich mit Tests abdecken möchten, ist es besser, eine Data Mapper-Bibliothek wie Doctrine zu verwenden und die Geschäftslogik vollständig vom Rest der Anwendung zu trennen. Unit-Tests werden 10-mal einfacher sein.
Wenn Sie sich in Ihrem Eloquent-Projekt wirklich mit Entwurfsmustern beschäftigen möchten, werde ich im nächsten Artikel zeigen, wie Sie die Repository-Vorlage teilweise anwenden und davon profitieren können.