Sollte ein Modell in MVC die Validierung handhaben?

25

Ich versuche, eine Webanwendung, die ich für die Verwendung des MVC-Musters entwickelt habe, neu zu erstellen, bin mir jedoch nicht sicher, ob die Validierung im Modell erfolgen soll oder nicht. Zum Beispiel richte ich eines meiner Modelle so ein:

class AM_Products extends AM_Object 
{
    public function save( $new_data = array() ) 
    {
        // Save code
    }
}

Erste Frage: Ich frage mich also, ob meine Speichermethode eine Validierungsfunktion für $ new_data aufrufen soll oder davon ausgehen soll, dass die Daten bereits validiert wurden.

Wenn es eine Validierung bieten würde, würde der Modellcode zum Definieren von Datentypen wie folgt aussehen:

class AM_Products extends AM_Object
{
    protected function init() // Called by __construct in AM_Object
    {
        // This would match up to the database column `age`
        register_property( 'age', 'Age', array( 'type' => 'int', 'min' => 10, 'max' => 30 ) ); 
    }
}

Zweite Frage: Jede untergeordnete Klasse von AM_Object würde register_property für jede Spalte in der Datenbank dieses bestimmten Objekts ausführen. Ich bin mir nicht sicher, ob dies eine gute Möglichkeit ist oder nicht.

Dritte Frage: Soll das Modell eine Fehlermeldung oder einen Fehlercode zurückgeben und die Ansicht den Code verwenden, um eine entsprechende Meldung anzuzeigen, wenn die Validierung vom Modell durchgeführt werden soll?

Brandon Wamboldt
quelle

Antworten:

30

Erste Antwort: Eine Schlüsselrolle des Modells ist die Aufrechterhaltung der Integrität. Die Verarbeitung von Benutzereingaben liegt jedoch in der Verantwortung eines Controllers.

Das heißt, der Controller muss Benutzerdaten (die meistens nur Zeichenfolgen sind) in etwas Sinnvolles übersetzen. Dies erfordert eine Syntaxanalyse (und hängt möglicherweise vom Gebietsschema ab, da es beispielsweise verschiedene Dezimaloperatoren usw. gibt).
Die eigentliche Validierung, wie in "Sind die Daten korrekt aufgebaut?", Sollte also von der Steuerung durchgeführt werden. Allerdings ist die Überprüfung, wie in "Sind die Daten sinnvoll?" sollte innerhalb des Modells durchgeführt werden.

Um dies an einem Beispiel zu verdeutlichen:
Angenommen, Sie können in Ihrer Anwendung Entitäten mit einem Datum hinzufügen (z. B. ein Problem mit einer Frist). Möglicherweise verfügen Sie über eine API, in der Datumsangaben als reine Unix-Zeitstempel dargestellt werden. Wenn Sie von einer HTML-Seite stammen, handelt es sich um eine Reihe unterschiedlicher Werte oder eine Zeichenfolge im Format MM / TT / JJJJ. Sie möchten diese Informationen nicht im Modell haben. Sie möchten, dass jeder Controller einzeln versucht, das Datum herauszufinden. Wenn das Datum dann an das Modell übergeben wird, muss das Modell jedoch die Integrität beibehalten. Beispielsweise kann es sinnvoll sein, Termine in der Vergangenheit oder Termine an Feiertagen / Sonntagen usw. nicht zuzulassen.

Ihr Controller enthält Eingaberegeln (Verarbeitungsregeln). Ihr Modell enthält Geschäftsregeln. Sie möchten, dass Ihre Geschäftsregeln immer durchgesetzt werden, egal was passiert. Angenommen, Sie hätten Geschäftsregeln im Controller, dann müssten Sie diese duplizieren, falls Sie jemals einen anderen Controller erstellen sollten.

Zweite Antwort: Der Ansatz ist sinnvoll, die Methode könnte jedoch leistungsfähiger gemacht werden. Anstatt dass der letzte Parameter ein Array ist, sollte es sich um eine Instanz handeln, IContstraintdie wie folgt definiert ist:

interface IConstraint {
     function test($value);//returns bool
}

Und für Zahlen könnte man so etwas haben

class NumConstraint {
    var $grain;
    var $min;
    var $max;
    function __construct($grain = 1, $min = NULL, $max = NULL) {
         if ($min === NULL) $min = INT_MIN;
         if ($max === NULL) $max = INT_MAX;
         $this->min = $min;
         $this->max = $max;
         $this->grain = $grain;
    }
    function test($value) {
         return ($value % $this->grain == 0 && $value >= $min && $value <= $max);
    }
}

Ich sehe auch nicht, was 'Age'gemeint ist, um ehrlich zu sein. Ist es der tatsächliche Name der Immobilie? Unter der Annahme, dass es standardmäßig eine Konvention gibt, kann der Parameter einfach an das Ende der Funktion gehen und optional sein. Wenn nicht festgelegt, wird standardmäßig der Wert to_camel_case des DB-Spaltennamens verwendet.

So würde der Beispielaufruf aussehen:

register_property('age', new NumConstraint(1, 10, 30));

Der Sinn der Verwendung von Schnittstellen besteht darin, dass Sie im Laufe der Zeit immer mehr Einschränkungen hinzufügen können, und diese können so kompliziert sein, wie Sie möchten. Damit eine Zeichenfolge mit einem regulären Ausdruck übereinstimmt. Für einen Termin mindestens 7 Tage voraus sein. Und so weiter.

Dritte Antwort: Jede Model-Entität sollte eine Methode wie haben Result checkValue(string property, mixed value). Die Steuerung sollte es vor dem Einstellen der Daten aufrufen . Sie Resultsollten alle Informationen darüber haben, ob die Überprüfung fehlgeschlagen ist, und in diesem Fall Gründe angeben, damit der Controller diese an die Ansicht entsprechend weitergeben kann.
Wenn ein falscher Wert an das Modell übergeben wird, sollte das Modell einfach durch Auslösen einer Ausnahme reagieren.

back2dos
quelle
Vielen Dank für diesen Artikel. Es hat eine Menge Dinge über MVC geklärt.
AmadeusDrZaius
5

Ich bin mit "back2dos" nicht ganz einverstanden: Ich empfehle, immer eine separate Formular- / Validierungsschicht zu verwenden, mit der der Controller die Eingabedaten validieren kann, bevor sie an das Modell gesendet werden.

Theoretisch gesehen basiert die Modellvalidierung auf vertrauenswürdigen Daten (interner Systemstatus) und sollte im Idealfall zu jedem Zeitpunkt wiederholbar sein, während die Eingabevalidierung explizit einmal auf Daten angewendet wird, die aus nicht vertrauenswürdigen Quellen stammen (abhängig vom Anwendungsfall und den Benutzerrechten).

Diese Trennung ermöglicht es, wiederverwendbare Modelle, Steuerungen und Formulare zu erstellen, die durch Abhängigkeitsinjektion lose gekoppelt werden können. Stellen Sie sich die Eingabevalidierung als Whitelist-Validierung vor („als gut bekannt akzeptieren“) und die Modellvalidierung als Blacklist-Validierung („als schlecht bekannt ablehnen“). Die Validierung von Whitelists ist sicherer, während die Validierung von Blacklists verhindert, dass Ihr Modell-Layer zu sehr auf bestimmte Anwendungsfälle beschränkt wird.

Ungültige Modelldaten sollten immer dazu führen, dass eine Ausnahme ausgelöst wird (andernfalls kann die Anwendung weiter ausgeführt werden, ohne dass der Fehler bemerkt wird), während ungültige Eingabewerte aus externen Quellen nicht unerwartet, sondern häufig auftreten (es sei denn, Sie haben Benutzer, die niemals Fehler machen).

Siehe auch: https://lastzero.net/2015/11/why-im-using-a-separate-layer-for-input-data-validation/

lastzero
quelle
Nehmen wir der Einfachheit halber an, dass es eine Validator-Klassenfamilie gibt und dass alle Validierungen mit einer strategischen Hierarchie durchgeführt werden. Konkrete untergeordnete Validierer können auch aus speziellen Validierern bestehen: E-Mail, Telefonnummer, Formulartoken, Captcha, Kennwort und andere. Controller Eingabevalidierung ist von zweierlei Art:?. 1) Überprüfen der Existenz eines Controllers und Verfahren / Befehl, und 2) einer ersten Prüfung der Daten (dh die HTTP - Anfrageverfahren, wie viele Dateneingänge (zu viele zu wenig)
Anthony Rutledge
Nachdem die Anzahl der Eingaben überprüft wurde, müssen Sie wissen, dass die richtigen HTML-Steuerelemente namentlich übermittelt wurden, wobei zu berücksichtigen ist, dass die Anzahl der Eingaben pro Anforderung variieren kann, da nicht alle Steuerelemente eines HTML-Formulars etwas übermitteln, wenn sie leer gelassen werden ( besonders Ankreuzfelder). Danach ist die letzte vorläufige Prüfung ein Test der Eingabegröße. Meiner Meinung nach sollte dies früh und nicht spät sein. Die Überprüfung der Menge, des Steuerungsnamens und der grundlegenden Eingabegröße in einem Steuerungsvalidierer würde bedeuten, dass für jeden Befehl / jede Methode in der Steuerung ein Validierer vorhanden ist. Ich bin der Meinung, dass dies Ihre Bewerbung sicherer macht.
Anthony Rutledge
Ja, der Controller-Validator für einen Befehl ist eng an die für eine Modellmethode erforderlichen Argumente gekoppelt (sofern vorhanden) , der Controller selbst jedoch nicht, mit Ausnahme des Verweises auf den Controller-Validator . Dies ist ein würdiger Kompromiss, da man nicht davon ausgehen darf, dass die meisten Eingaben legitim sind. Je früher Sie den unberechtigten Zugriff auf Ihre Anwendung beenden können, desto besser. Wenn Sie dies in einer Controller-Validierungsklasse tun (Anzahl, Name und maximale Größe der Eingaben), müssen Sie nicht das gesamte Modell instanziieren, um eindeutig böswillige HTTP-Anforderungen abzulehnen.
Anthony Rutledge
Vor dem Beheben von Problemen mit der maximalen Eingabegröße muss jedoch sichergestellt werden, dass die Codierung korrekt ist. Alles in allem ist dies zu viel für das Modell, selbst wenn die Arbeit gekapselt ist. Es wird unnötig teuer, böswillige Anfragen abzulehnen. Zusammenfassend muss der Controller mehr Verantwortung für das übernehmen, was er an das Modell sendet. Ein Fehler auf Controllerebene sollte schwerwiegend sein und keine anderen Informationen als 200 OK an den Anforderer zurückgeben. Protokollieren Sie die Aktivität. Wirft eine schwerwiegende Ausnahme. Beende alle Aktivitäten. Stoppen Sie alle Prozesse so schnell wie möglich.
Anthony Rutledge
Minimale Steuerelemente, maximale Steuerelemente, korrekte Steuerelemente, Eingabecodierung und maximale Eingabegröße hängen alle von der Art der Anforderung ab (auf die eine oder andere Weise). Einige Leute haben diese fünf Kernaspekte nicht als bestimmend dafür identifiziert, ob eine Anfrage berücksichtigt werden sollte. Wenn all diese Dinge nicht erfüllt sind, warum senden Sie diese Informationen an das Modell? Gute Frage.
Anthony Rutledge
3

Ja, das Modell sollte validiert werden. Die Benutzeroberfläche sollte auch die Eingabe validieren.

Es liegt eindeutig in der Verantwortung des Modells, gültige Werte und Zustände zu bestimmen. Manchmal ändern sich solche Regeln oft. In diesem Fall würde ich das Modell aus Metadaten füttern und / oder dekorieren.

Falke
quelle
Was ist mit Fällen, in denen die Absicht des Benutzers eindeutig böswillig oder irrtümlich ist? Beispielsweise sollte eine bestimmte HTTP-Anforderung nicht mehr als sieben (7) Eingabewerte haben, aber Ihr Controller erhält siebzig (70). Werden Sie wirklich zulassen, dass die Anzahl der zulässigen Werte zehnmal (10x) auf das Modell trifft, wenn die Anforderung eindeutig beschädigt ist? In diesem Fall handelt es sich um den Status der gesamten Anforderung und nicht um den Status eines bestimmten Werts. Eine Tiefenverteidigungsstrategie schlägt vor, dass die Art der HTTP-Anforderung geprüft wird, bevor Daten an das Modell gesendet werden.
Anthony Rutledge
(Fortsetzung) Auf diese Weise überprüfen Sie nicht, ob bestimmte vom Benutzer angegebene Werte und Zustände gültig sind, sondern ob die Gesamtheit der Anforderung gültig ist. Es ist noch nicht nötig, einen Drilldown durchzuführen. Das Öl ist bereits an der Oberfläche.
Anthony Rutledge
(Fortsetzung) Es gibt keine Möglichkeit, die Front-End-Validierung zu erzwingen. Es muss berücksichtigt werden, dass automatisierte Tools als Schnittstelle zu Ihrer Webanwendung verwendet werden können.
Anthony Rutledge
(Nachdenken) Gültige Werte und Datenzustände im Modell sind wichtig, aber was ich beschrieben habe, trifft in der Absicht zu, dass die Anforderung über den Controller eingeht. Wenn Sie die Überprüfung der Absicht auslassen, ist Ihre Anwendung anfälliger. Absicht kann nur gut sein (nach deinen Regeln spielen) oder schlecht (außerhalb deiner Regeln gehen). Die Absicht kann durch grundlegende Überprüfungen der Eingabe überprüft werden: minimale Steuerelemente, maximale Steuerelemente, korrekte Steuerelemente, Eingabecodierung und maximale Eingabegröße. Es ist alles oder nichts. Alles ist bestanden oder die Anfrage ist ungültig. Sie müssen nichts an das Modell senden.
Anthony Rutledge
2

Gute Frage!

Was die Entwicklung des World Wide Web angeht, was ist, wenn Sie auch Folgendes fragen?

"Wenn einer Steuerung eine falsche Benutzereingabe von einer Benutzerschnittstelle aus übermittelt wird, sollte die Steuerung die Ansicht in einer Art zyklischen Schleife aktualisieren, um zu erzwingen, dass Befehle und Eingabedaten genau sind, bevor sie verarbeitet werden ? Wie? Wie wird die Ansicht unter normalen Bedingungen aktualisiert Bedingungen Ist eine Ansicht eng an ein Modell gekoppelt? Ist die Kerngeschäftslogik der Benutzereingabevalidierung des Modells oder ist sie vorläufig und sollte daher innerhalb des Controllers erfolgen (da Benutzereingabedaten Teil der Anforderung sind)?

(Kann und sollte eine Instanziierung eines Modells tatsächlich verzögert werden, bis eine gute Eingabe erfolgt?)

Meiner Meinung nach sollten Modelle einen reinen und makellosen Umstand (so weit wie möglich) handhaben, der nicht durch die grundlegende Validierung der HTTP-Anforderungseingaben beeinträchtigt wird , die vor der Modellinstanziierung (und definitiv bevor das Modell Eingabedaten erhält) erfolgen sollte. Da das Verwalten von Statusdaten (persistent oder anderweitig) und API-Beziehungen die Welt des Modells ist, können grundlegende Überprüfungen der HTTP-Anforderungseingaben im Controller erfolgen.

Zusammenfassend.

1) Überprüfen Sie Ihre Route (aus der URL analysiert), da der Controller und die Methode vorhanden sein müssen, bevor etwas anderes ausgeführt werden kann. Dies sollte auf jeden Fall im Front-Controller-Bereich (Router-Klasse) geschehen, bevor Sie zum wahren Controller gelangen. Duh. :-)

2) Ein Modell kann viele Quellen für Eingabedaten haben: eine HTTP-Anfrage, eine Datenbank, eine Datei, eine API und ja, ein Netzwerk. Wenn Sie Ihre gesamte Eingabevalidierung in das Modell einfügen möchten, betrachten Sie die Eingabevalidierung für HTTP-Anforderungen als Teil der Geschäftsanforderungen für das Programm. Fall abgeschlossen.

3) Es ist jedoch kurzsichtig, die Ausgaben für die Instanziierung vieler Objekte zu durchlaufen, wenn die HTTP-Anforderungseingabe nicht gut ist! Sie können feststellen, ob die Eingabe von ** HTTP-Anforderungen ** in Ordnung ist ( die mit der Anforderung eingegangen ist ), indem Sie sie validieren, bevor Sie das Modell und alle seine Komplexitäten instanziieren (ja, vielleicht noch mehr Validatoren für API- und DB-Eingabe- / Ausgabedaten).

Testen Sie Folgendes:

a) Die HTTP-Anforderungsmethode (GET, POST, PUT, PATCH, DELETE ...)

b) Minimale HTML-Steuerelemente (haben Sie genug?).

c) Maximale HTML-Steuerelemente (haben Sie zu viele?).

d) Richtige HTML-Steuerelemente (haben Sie die richtigen?).

e) Eingabecodierung (normalerweise UTF-8?).

f) Maximale Eingabegröße (ist eine der Eingaben unzulässig?).

Denken Sie daran, dass Sie möglicherweise Zeichenfolgen und Dateien abrufen, sodass das Warten auf die Instanziierung des Modells sehr teuer werden kann, wenn Anforderungen Ihren Server erreichen.

Was ich hier beschrieben habe, trifft auf die Absicht, dass die Anforderung über den Controller eingeht. Wenn Sie die Überprüfung der Absicht auslassen, ist Ihre Anwendung anfälliger. Absicht kann nur gut sein (nach deinen Grundregeln spielen) oder schlecht (außerhalb deiner Grundregeln gehen).

Die Absicht für eine HTTP-Anfrage ist alles oder nichts. Alles ist bestanden oder die Anfrage ist ungültig . Sie müssen nichts an das Modell senden.

Diese Grundstufe der HTTP - Anforderung Absicht hat nichts mit normalen Benutzereingabefehler und Validierung zu tun. In meinen Anwendungen muss eine HTTP-Anforderung auf die oben genannten fünf Arten gültig sein, damit ich sie berücksichtigen kann. In einer Verteidigung-in-Tiefe Art zu sprechen, erhalten Sie nie auf Benutzereingaben Validierung auf der Server-Seite , wenn alle diese fünf Dinge fehlschlagen.

Ja, dies bedeutet, dass auch die Dateieingabe Ihren Front-End-Versuchen entsprechen muss, um den Benutzer zu verifizieren und ihm die maximal akzeptierte Dateigröße mitzuteilen. Nur HTML? Kein JavaScript? Gut, aber der Benutzer muss über die Konsequenzen des Hochladens von zu großen Dateien informiert werden (hauptsächlich, dass sie alle Formulardaten verlieren und aus dem System geworfen werden).

4) Bedeutet dies, dass die Eingabedaten für HTTP-Anforderungen nicht Teil der Geschäftslogik der Anwendung sind? Nein, es bedeutet nur, dass Computer endliche Geräte sind und Ressourcen mit Bedacht eingesetzt werden müssen. Es ist sinnvoll, böswillige Aktivitäten früher und nicht später einzustellen. Sie zahlen mehr in Rechenressourcen für das Warten, um es später zu stoppen.

5) Wenn die HTTP-Anforderungseingabe fehlerhaft ist, ist die gesamte Anforderung fehlerhaft . So sehe ich es. Die Definition einer guten HTTP-Anforderungseingabe wird von den Geschäftsanforderungen des Modells abgeleitet, es muss jedoch einen gewissen Punkt für die Abgrenzung der Ressourcen geben. Wie lange werden Sie eine schlechte Anfrage noch leben lassen, bevor Sie sie töten und sagen: "Oh, hey, macht nichts. Schlechte Anfrage."

Das Urteil ist nicht einfach, dass der Benutzer einen vernünftigen Eingabefehler begangen hat, sondern dass eine HTTP-Anfrage so unzulässig ist, dass sie als böswillig eingestuft und sofort gestoppt werden muss.

6) Für mein Geld ist die HTTP-Anfrage (METHODE, URL / Route und Daten) entweder ALLES in Ordnung oder NICHTS anderes kann fortgesetzt werden. Ein robustes Modell muss sich bereits mit Validierungsaufgaben befassen, aber ein guter Ressourcenschäfer sagt: "Mein Weg oder der hohe Weg. Kommt richtig oder kommt gar nicht."

Es ist jedoch Ihr Programm. "Es gibt mehr als einen Weg, dies zu tun." Manche Wege kosten mehr Zeit und Geld als andere. Ein späteres Validieren von HTTP-Anforderungsdaten (im Modell) sollte über die Lebensdauer einer Anwendung (insbesondere beim Vergrößern oder Verkleinern) höhere Kosten verursachen.

Wenn Ihre Validatoren modular aufgebaut sind, sollte die Validierung der grundlegenden * HTTP-Anforderungseingabe ** im Controller kein Problem darstellen. Verwenden Sie einfach eine strategische Validator-Klasse, in der Validatoren manchmal auch aus spezialisierten Validatoren bestehen (E-Mail, Telefon, Formular-Token, Captcha, ...).

Einige halten dies für völlig falsch, aber HTTP steckte noch in den Kinderschuhen, als die Viererbande Entwurfsmuster schrieb : Elemente wiederverwendbarer objektorientierter Software .

================================================ =======================

Da es sich nun um die normale Überprüfung von Benutzereingaben handelt (nachdem die HTTP-Anforderung als gültig eingestuft wurde), wird die Ansicht aktualisiert, wenn der Benutzer Fehler macht, über die Sie nachdenken müssen! Diese Art der Benutzereingabevalidierung sollte im Modell erfolgen.

Sie haben keine Garantie für JavaScript im Front-End. Das heißt, Sie können keine asynchrone Aktualisierung der Benutzeroberfläche Ihrer Anwendung mit Fehlerstatus garantieren. Eine echte progressive Verbesserung würde auch den synchronen Anwendungsfall abdecken.

Den synchronen Anwendungsfall zu berücksichtigen, ist eine Kunst, die immer mehr verloren geht, weil einige Leute nicht die Zeit und Mühe auf sich nehmen wollen, den Status aller ihrer UI-Tricks zu verfolgen (Steuerelemente ein- / ausblenden, Steuerelemente deaktivieren / aktivieren) , Fehleranzeigen, Fehlermeldungen) im Back-End (in der Regel durch Statusverfolgung in Arrays).

Update : Im Diagramm sage ich, dass das auf Viewdas verweisen soll Model. Nein. Sie sollten Daten an Viewdas übergeben Model, um eine lose Kopplung zu erhalten. Bildbeschreibung hier eingeben

Anthony Rutledge
quelle