Ich habe gerade einen Überblick über das MVC-Framework und frage mich oft, wie viel Code im Modell enthalten sein soll. Ich neige dazu, eine Datenzugriffsklasse zu haben, die Methoden wie diese hat:
public function CheckUsername($connection, $username)
{
try
{
$data = array();
$data['Username'] = $username;
//// SQL
$sql = "SELECT Username FROM" . $this->usersTableName . " WHERE Username = :Username";
//// Execute statement
return $this->ExecuteObject($connection, $sql, $data);
}
catch(Exception $e)
{
throw $e;
}
}
Meine Modelle sind in der Regel eine Entitätsklasse, die der Datenbanktabelle zugeordnet ist.
Sollte das Modellobjekt alle Eigenschaften der Datenbankzuordnung sowie den obigen Code haben, oder ist es in Ordnung, den Code zu trennen, der tatsächlich für die Datenbank funktioniert?
Werde ich am Ende vier Schichten haben?
php
oop
model-view-controller
architecture
model
Dietpixel
quelle
quelle
Exception
hat nicht viel Dokumentationswert. Persönlich würde ich, wenn ich mich auf diesen Weg begeben würde, PHPDocs@exception
oder einen ähnlichen Mechanismus wählen , damit er in der generierten Dokumentation angezeigt wird.Antworten:
Das erste, was ich klären muss, ist: Das Modell ist eine Ebene .
Zweitens: Es gibt einen Unterschied zwischen klassischem MVC und dem, was wir in der Webentwicklung verwenden. Hier ist eine ältere Antwort, die ich geschrieben habe und die kurz beschreibt, wie sie sich unterscheiden.
Was für ein Modell ist NICHT:
Das Modell ist keine Klasse oder ein einzelnes Objekt. Es ist ein sehr häufiger Fehler (ich habe es auch getan, obwohl die ursprüngliche Antwort geschrieben wurde, als ich anfing, etwas anderes zu lernen) , weil die meisten Frameworks dieses Missverständnis aufrechterhalten.
Es handelt sich weder um eine objektrelationale Zuordnungstechnik (ORM) noch um eine Abstraktion von Datenbanktabellen. Jeder, der Ihnen etwas anderes sagt, versucht höchstwahrscheinlich , ein anderes brandneues ORM oder ein ganzes Framework zu "verkaufen" .
Was für ein Modell ist:
Bei einer ordnungsgemäßen MVC-Anpassung enthält das M die gesamte Geschäftslogik der Domäne, und die Modellschicht besteht hauptsächlich aus drei Arten von Strukturen:
Domänenobjekte
Hier legen Sie fest, wie Daten vor dem Senden einer Rechnung überprüft oder die Gesamtkosten einer Bestellung berechnet werden sollen. Gleichzeitig wissen Domänenobjekte nichts von der Speicherung - weder von wo (SQL-Datenbank, REST-API, Textdatei usw.) noch selbst wenn sie gespeichert oder abgerufen werden.
Datenmapper
Diese Objekte sind nur für die Speicherung verantwortlich. Wenn Sie Informationen in einer Datenbank speichern, befindet sich hier SQL. Oder Sie verwenden eine XML-Datei zum Speichern von Daten, und Ihre Data Mapper analysieren von und zu XML-Dateien.
Dienstleistungen
Sie können als von ihnen denken , „higher level Domain Objects“, sondern von Geschäftslogik, Dienstleistungen sind für die Interaktion verantwortlich zwischen Domain - Objekte und Mapper . Diese Strukturen schaffen schließlich eine "öffentliche" Schnittstelle für die Interaktion mit der Domänengeschäftslogik. Sie können sie vermeiden, aber mit der Strafe, dass einige Domänenlogiken in Controller übertragen werden .
In der Frage zur ACL-Implementierung finden Sie eine entsprechende Antwort auf dieses Thema. Dies kann hilfreich sein.
Die Kommunikation zwischen der Modellschicht und anderen Teilen der MVC-Triade sollte nur über Services erfolgen . Die klare Trennung hat einige zusätzliche Vorteile:
Wie interagiere ich mit einem Modell?
Zugriff auf Dienstinstanzen erhalten
Es gibt zwei allgemeine Ansätze, damit sowohl die View- als auch die Controller- Instanz (was Sie als "UI-Schicht" bezeichnen könnten) auf diese Dienste zugreifen können:
Wie Sie vielleicht vermuten, ist der DI-Container eine viel elegantere Lösung (für Anfänger jedoch nicht die einfachste). Die beiden Bibliotheken, die ich für diese Funktionalität in Betracht ziehen möchte, sind die eigenständige DependencyInjection-Komponente von Syfmony oder Auryn .
Mit beiden Lösungen, die eine Factory und einen DI-Container verwenden, können Sie auch die Instanzen verschiedener Server gemeinsam nutzen, die von der ausgewählten Steuerung gemeinsam genutzt und für einen bestimmten Anforderungs- / Antwortzyklus angezeigt werden sollen.
Änderung des Modellzustands
Nachdem Sie in den Controllern auf die Modellebene zugreifen können, müssen Sie diese tatsächlich verwenden:
Ihre Controller haben eine sehr klare Aufgabe: Nehmen Sie die Benutzereingaben und ändern Sie basierend auf diesen Eingaben den aktuellen Status der Geschäftslogik. In diesem Beispiel sind die Status, zwischen denen geändert wird, "anonymer Benutzer" und "angemeldeter Benutzer".
Der Controller ist nicht für die Überprüfung der Benutzereingaben verantwortlich, da dies Teil der Geschäftsregeln ist und der Controller definitiv keine SQL-Abfragen aufruft, wie Sie sie hier oder hier sehen würden (bitte hassen Sie sie nicht, sie sind fehlgeleitet, nicht böse).
Zeigt dem Benutzer die Statusänderung an.
Ok, Benutzer hat sich angemeldet (oder ist fehlgeschlagen). Was jetzt? Der Benutzer ist sich dessen immer noch nicht bewusst. Sie müssen also tatsächlich eine Antwort erstellen, und das liegt in der Verantwortung einer Ansicht.
In diesem Fall ergab die Ansicht eine von zwei möglichen Antworten, basierend auf dem aktuellen Status der Modellschicht. Für einen anderen Anwendungsfall hätten Sie die Ansicht, verschiedene Vorlagen zum Rendern auszuwählen, basierend auf etwas wie "aktuell aus Artikel ausgewählt".
Die Präsentationsebene kann tatsächlich ziemlich aufwändig werden, wie hier beschrieben: Grundlegendes zu MVC-Ansichten in PHP .
Aber ich mache gerade eine REST-API!
Natürlich gibt es Situationen, in denen dies ein Overkill ist.
MVC ist nur eine konkrete Lösung für das Prinzip der Trennung von Bedenken . MVC trennt die Benutzeroberfläche von der Geschäftslogik und in der Benutzeroberfläche die Behandlung der Benutzereingaben und der Präsentation. Das ist entscheidend. Während die Leute es oft als "Triade" beschreiben, besteht es nicht aus drei unabhängigen Teilen. Die Struktur ist eher so:
Wenn die Logik Ihrer Präsentationsebene so gut wie nicht vorhanden ist, besteht der pragmatische Ansatz darin, sie als einzelne Ebene beizubehalten. Es kann auch einige Aspekte der Modellschicht wesentlich vereinfachen.
Mit diesem Ansatz kann das Anmeldebeispiel (für eine API) wie folgt geschrieben werden:
Dies ist zwar nicht nachhaltig, aber wenn Sie eine komplizierte Logik zum Rendern eines Antwortkörpers haben, ist diese Vereinfachung für trivialere Szenarien sehr nützlich. Aber seien Sie gewarnt , dieser Ansatz wird zu einem Albtraum, wenn Sie versuchen, ihn in großen Codebasen mit komplexer Präsentationslogik zu verwenden.
Wie baue ich das Modell?
Da es keine einzige "Modell" -Klasse gibt (wie oben erläutert), erstellen Sie das Modell wirklich nicht. Stattdessen beginnen Sie mit der Erstellung von Diensten , die bestimmte Methoden ausführen können. Und dann implementieren Domain - Objekte und Mapper .
Ein Beispiel für eine Servicemethode:
In beiden oben genannten Ansätzen gab es diese Anmeldemethode für den Identifikationsdienst. Wie würde es eigentlich aussehen? Ich verwende eine leicht modifizierte Version derselben Funktionalität aus einer Bibliothek , die ich geschrieben habe. Weil ich faul bin:
Wie Sie sehen können, gibt es auf dieser Abstraktionsebene keinen Hinweis darauf, woher die Daten abgerufen wurden. Es kann sich um eine Datenbank handeln, es kann sich jedoch auch nur um ein Scheinobjekt zu Testzwecken handeln. Sogar die Datenmapper, die tatsächlich dafür verwendet werden, sind in den
private
Methoden dieses Dienstes versteckt .Möglichkeiten zum Erstellen von Mappern
Um eine Abstraktion der Persistenz zu implementieren, müssen bei den flexibelsten Ansätzen benutzerdefinierte Datenzuordnungen erstellt werden .
Aus: PoEAA- Buch
In der Praxis werden sie für die Interaktion mit bestimmten Klassen oder Oberklassen implementiert. Nehmen wir an, Sie haben
Customer
undAdmin
in Ihrem Code (beide erben von einerUser
Oberklasse). Beide würden wahrscheinlich einen separaten passenden Mapper haben, da sie unterschiedliche Felder enthalten. Sie werden aber auch gemeinsame und häufig verwendete Vorgänge haben. Zum Beispiel: Aktualisieren der "zuletzt online gesehenen" Zeit. Und anstatt die vorhandenen Mapper komplizierter zu machen, besteht der pragmatischere Ansatz darin, einen allgemeinen "User Mapper" zu haben, der nur diesen Zeitstempel aktualisiert.Einige zusätzliche Kommentare:
Datenbanktabellen und Modell
Während manchmal eine direkte 1: 1: 1-Beziehung zwischen einer Datenbanktabelle, einem Domänenobjekt und Mapper besteht , ist diese in größeren Projekten möglicherweise weniger häufig als erwartet:
Informationen, die von einem einzelnen Domänenobjekt verwendet werden, können aus verschiedenen Tabellen zugeordnet werden, während das Objekt selbst keine Persistenz in der Datenbank aufweist.
Beispiel: Wenn Sie einen monatlichen Bericht erstellen. Dies würde Informationen aus verschiedenen Tabellen sammeln, aber es gibt keine magische
MonthlyReport
Tabelle in der Datenbank.Ein einzelner Mapper kann mehrere Tabellen betreffen.
Beispiel: Wenn Sie Daten aus dem
User
Objekt speichern , kann dieses Domänenobjekt eine Sammlung anderer Domänenobjekte -Group
Instanzen - enthalten . Wenn Sie sie ändern und speichernUser
, muss der Data Mapper Einträge in mehreren Tabellen aktualisieren und / oder einfügen.Daten von einem einzelnen Domänenobjekt werden in mehr als einer Tabelle gespeichert.
Beispiel: In großen Systemen (denken Sie an ein mittelgroßes soziales Netzwerk) kann es pragmatisch sein, Benutzerauthentifizierungsdaten und Daten, auf die häufig zugegriffen wird, getrennt von größeren Inhaltsblöcken zu speichern, was selten erforderlich ist. In diesem Fall haben Sie möglicherweise noch eine einzelne
User
Klasse, aber die darin enthaltenen Informationen hängen davon ab, ob alle Details abgerufen wurden.Für jedes Domänenobjekt kann es mehr als einen Mapper geben
Beispiel: Sie haben eine Nachrichtenseite mit einem gemeinsam genutzten Code, der sowohl für die Öffentlichkeit als auch für die Verwaltungssoftware verwendet wird. Obwohl beide Schnittstellen dieselbe
Article
Klasse verwenden, benötigt das Management viel mehr Informationen. In diesem Fall hätten Sie zwei separate Mapper: "intern" und "extern". Jeder führt unterschiedliche Abfragen durch oder verwendet sogar unterschiedliche Datenbanken (wie bei Master oder Slave).Eine Ansicht ist keine Vorlage
Ansichtsinstanzen in MVC (wenn Sie nicht die MVP-Variante des Musters verwenden) sind für die Präsentationslogik verantwortlich. Dies bedeutet, dass jede Ansicht normalerweise mindestens einige Vorlagen jongliert. Es erfasst Daten aus der Modellebene und wählt dann basierend auf den empfangenen Informationen eine Vorlage aus und legt Werte fest.
Einer der Vorteile, die Sie daraus ziehen, ist die Wiederverwendbarkeit. Wenn Sie eine
ListView
Klasse erstellen , können Sie mit gut geschriebenem Code dieselbe Klasse die Präsentation der Benutzerliste und der Kommentare unter einem Artikel übergeben lassen. Weil beide dieselbe Präsentationslogik haben. Sie wechseln einfach die Vorlagen.Sie können entweder native PHP-Vorlagen oder eine Template-Engine eines Drittanbieters verwenden. Möglicherweise gibt es auch Bibliotheken von Drittanbietern, die View- Instanzen vollständig ersetzen können .
Was ist mit der alten Version der Antwort?
Die einzige wesentliche Änderung besteht darin, dass das, was in der alten Version als Modell bezeichnet wird, tatsächlich ein Dienst ist . Der Rest der "Bibliotheksanalogie" hält ziemlich gut mit.
Der einzige Fehler, den ich sehe, ist, dass dies eine wirklich seltsame Bibliothek wäre, da sie Ihnen Informationen aus dem Buch zurückgeben würde, Sie das Buch selbst jedoch nicht berühren könnten, da sonst die Abstraktion "auslaufen" würde. Ich muss mir vielleicht eine passendere Analogie überlegen.
Welche Beziehung besteht zwischen View- und Controller- Instanzen?
Die MVC-Struktur besteht aus zwei Schichten: UI und Modell. Die Hauptstrukturen in der UI-Ebene sind Ansichten und Controller.
Wenn Sie mit Websites arbeiten, die MVC-Entwurfsmuster verwenden, ist es am besten, eine 1: 1-Beziehung zwischen Ansichten und Controllern herzustellen. Jede Ansicht stellt eine ganze Seite Ihrer Website dar und verfügt über einen dedizierten Controller, der alle eingehenden Anforderungen für diese bestimmte Ansicht verarbeitet.
Um beispielsweise einen geöffneten Artikel darzustellen, hätten Sie
\Application\Controller\Document
und\Application\View\Document
. Dies würde alle Hauptfunktionen für die UI-Ebene enthalten, wenn es um den Umgang mit Artikeln geht (natürlich haben Sie möglicherweise einige XHR- Komponenten, die nicht direkt mit Artikeln zusammenhängen) .quelle
Alles, was Geschäftslogik ist, gehört in ein Modell, sei es eine Datenbankabfrage, Berechnungen, ein REST-Aufruf usw.
Sie können den Datenzugriff im Modell selbst haben. Das MVC-Muster hindert Sie nicht daran. Sie können es mit Services, Mappern und was nicht beschönigen, aber die eigentliche Definition eines Modells ist eine Ebene, die die Geschäftslogik handhabt, nicht mehr und nicht weniger. Es kann eine Klasse, eine Funktion oder ein komplettes Modul mit einer Unmenge von Objekten sein, wenn Sie dies wünschen.
Es ist immer einfacher, ein separates Objekt zu haben, das die Datenbankabfragen tatsächlich ausführt, anstatt sie direkt im Modell ausführen zu lassen. Dies ist besonders nützlich beim Komponententest (da es einfach ist, eine Scheindatenbankabhängigkeit in Ihr Modell einzufügen):
Außerdem müssen Sie in PHP selten Ausnahmen abfangen / erneut auslösen, da die Rückverfolgung erhalten bleibt, insbesondere in einem Fall wie Ihrem Beispiel. Lassen Sie die Ausnahme einfach auslösen und fangen Sie sie stattdessen im Controller ab.
quelle
User
Klasse erweitert das Modell grundsätzlich, ist aber kein Objekt. Der Benutzer sollte ein Objekt sein und Eigenschaften wie ID, Name ... haben, die Sie bereitstellenUser
Klasse ist ein Helfer.User
für ein Objekt, und es sollte Eigenschaften eines Benutzers haben, nicht Methoden wieCheckUsername
: Was sollten Sie tun, wenn Sie ein neuesUser
Objekt erstellen möchten ?new User($db)
In Web- "MVC" können Sie tun, was Sie wollen.
Das ursprüngliche Konzept (1) beschrieb das Modell als Geschäftslogik. Es sollte den Anwendungsstatus darstellen und eine gewisse Datenkonsistenz erzwingen. Dieser Ansatz wird oft als "Fettmodell" bezeichnet.
Die meisten PHP-Frameworks verfolgen einen flacheren Ansatz, bei dem das Modell nur eine Datenbankschnittstelle ist. Zumindest sollten diese Modelle jedoch die eingehenden Daten und Beziehungen noch validieren.
In beiden Fällen sind Sie nicht weit entfernt, wenn Sie die SQL-Inhalte oder Datenbankaufrufe in eine andere Ebene unterteilen. Auf diese Weise müssen Sie sich nur mit den tatsächlichen Daten / Verhaltensweisen befassen, nicht mit der eigentlichen Speicher-API. (Es ist jedoch unvernünftig, es zu übertreiben. Sie können beispielsweise ein Datenbank-Backend niemals durch ein Dateispeicher ersetzen, wenn dies nicht im Voraus geplant wurde.)
quelle
Die meisten Anwendungen mehr oftenly haben Daten, die Anzeige und Verarbeitung von Teil und wir alle nur diejenigen , die in den Buchstaben setzen
M
,V
undC
.Model (
M
) -> Hat die Attribute, die den Anwendungsstatus enthalten, und es weiß nichts überV
undC
.View (
V
) -> Hat ein Anzeigeformat für die Anwendung und kennt nur das Digest-Modell und kümmert sich nicht darumC
.Controller (
C
) ----> hat einen Teil der Verarbeitung Anwendung und wirkt als Verdrahtung zwischen M und V , und es hängt sowohl vonM
, imV
Gegensatz zuM
undV
.Insgesamt gibt es eine Trennung der Bedenken zwischen jedem. In Zukunft können Änderungen oder Erweiterungen sehr einfach hinzugefügt werden.
quelle
In meinem Fall habe ich eine Datenbankklasse, die alle direkten Datenbankinteraktionen wie Abfragen, Abrufen usw. abwickelt. Wenn ich also meine Datenbank von MySQL auf PostgreSQL ändern müsste, gäbe es kein Problem. Das Hinzufügen dieser zusätzlichen Ebene kann daher nützlich sein.
Jede Tabelle kann eine eigene Klasse und spezifische Methoden haben. Um die Daten tatsächlich abzurufen, kann die Datenbankklasse damit umgehen:
Datei
Database.php
Tabellenobjekt classL
Ich hoffe, dieses Beispiel hilft Ihnen, eine gute Struktur zu schaffen.
quelle
Database
im Beispiel ist keine Klasse. Es ist nur ein Wrapper für Funktionen. Wie kann man auch eine "Tabellenobjektklasse" ohne Objekt haben?