it-swarm-eu.dev

Sollte ein Modell in MVC die Validierung übernehmen?

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 wie folgt 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 oder davon ausgehen soll, dass die Daten bereits validiert wurden.

Wenn es eine Validierung bieten würde, würde ein Teil des Modellcodes zum Definieren von Datentypen folgendermaßen 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 ein guter Weg ist oder nicht.

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

28

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 Analyse (und kann von Dingen wie dem Gebietsschema abhängen, da es beispielsweise verschiedene Dezimaloperatoren usw. gibt).
Die eigentliche Validierung, wie in "Sind die Daten gut geformt?", Sollte also vom Controller durchgeführt werden. Allerdings ist die Überprüfung, wie in "Sind die Daten sinnvoll?" sollte innerhalb des Modells durchgeführt werden.

Um dies anhand eines Beispiels zu verdeutlichen:
Angenommen, Ihre Anwendung ermöglicht es Ihnen, einige Entitäten mit einem Datum hinzuzufü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. Bei einer HTML-Seite 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. Zum Beispiel kann es sinnvoll sein, Daten in der Vergangenheit oder Daten, die an Feiertagen/Sonntagen usw. liegen, 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 hatten 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 von IContstraint handeln, die wie folgt definiert ist:

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

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

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 verstehe auch nicht, was 'Age' Darstellen soll, um ehrlich zu sein. Ist es der tatsächliche Name der Immobilie? Angenommen, es gibt standardmäßig eine Konvention, könnte der Parameter einfach bis zum Ende der Funktion gehen und optional sein. Wenn nicht festgelegt, wird standardmäßig to_camel_case des DB-Spaltennamens verwendet.

Der Beispielaufruf würde also so 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, die so kompliziert sein können, wie Sie möchten. Damit eine Zeichenfolge mit einem regulären Ausdruck übereinstimmt. Für einen Termin, der mindestens 7 Tage im Voraus liegt. Und so weiter.

Dritte Antwort: Jede Modellentität sollte eine Methode wie Result checkValue(string property, mixed value) haben. Der Controller sollte es vor Einstellungsdaten nennen. Das Result sollte alle Informationen darüber enthalten, ob die Prüfung fehlgeschlagen ist, und in diesem Fall Gründe angeben, damit der Controller diese entsprechend an die Ansicht weitergeben kann.
Wenn ein falscher Wert an das Modell übergeben wird, sollte das Modell einfach eine Ausnahme auslösen.

31
back2dos

Ich stimme "back2dos" nicht vollständig zu: Ich empfehle, immer eine separate Formular-/Validierungsschicht zu verwenden, mit der der Controller Eingabedaten validieren kann, bevor sie an das Modell gesendet werden.

Aus theoretischer Sicht arbeitet die Modellvalidierung mit vertrauenswürdigen Daten (interner Systemstatus) und sollte idealerweise zu jedem Zeitpunkt wiederholbar sein, während die Eingabevalidierung explizit einmal mit Daten arbeitet, die aus nicht vertrauenswürdigen Quellen stammen (abhängig vom Anwendungsfall und den Benutzerrechten).

Diese Trennung ermöglicht es, wiederverwendbare Modelle, Controller und Formulare zu erstellen, die durch Abhängigkeitsinjektion lose gekoppelt werden können. Stellen Sie sich die Eingabevalidierung als Whitelist-Validierung vor („bekanntes Gut akzeptieren“) und die Modellvalidierung als Blacklist-Validierung („Bekanntes schlechtes ablehnen“). Die Whitelist-Validierung ist sicherer, während die Blacklist-Validierung verhindert, dass Ihre Modellebene zu stark auf ganz bestimmte Anwendungsfälle beschränkt wird.

Ungültige Modelldaten sollten immer dazu führen, dass eine Ausnahme ausgelöst wird (andernfalls kann die Anwendung ohne Kenntnis des Fehlers weiter ausgeführt werden), während ungültige Eingabewerte aus externen Quellen nicht unerwartet, sondern häufig vorkommen (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/

7
lastzero

Ja, das Modell sollte eine Validierung durchführen. 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.

3
Falcon

Gute Frage!

Was wäre, wenn Sie im Hinblick auf die Entwicklung des World Wide Web auch Folgendes fragen würden?.

"Wenn eine fehlerhafte Benutzereingabe von einer Benutzeroberfläche an einen Controller übermittelt wird, sollte der Controller die Ansicht in einer Art zyklischer Schleife aktualisieren und Befehle und Eingabedaten vor der Verarbeitung zur Genauigkeit zwingen? Wie? ? Wie wird die Ansicht unter normalen Bedingungen aktualisiert? Ist eine Ansicht eng an ein Modell gekoppelt? Ist die Validierung der Benutzereingaben die Kerngeschäftslogik des Modells, oder Ist dies vorläufig und sollte daher innerhalb des Controllers erfolgen (da Benutzereingabedaten Teil der Anforderung sind)?

(Kann und sollte tatsächlich eine Verzögerung die Instanziierung eines Modells verzögern, bis eine gute Eingabe erzielt wird?)

Meiner Meinung nach sollten Modelle einen reinen und makellosen Umstand (so weit wie möglich) bewältigen, der nicht durch eine grundlegende Validierung der HTTP-Anforderungseingabe belastet wird, die vor der Modellinstanziierung erfolgen sollte (und definitiv bevor das Modell Eingabedaten erhält). Da die Verwaltung von Statusdaten (persistent oder anderweitig) und API-Beziehungen die Welt des Modells ist, sollte die grundlegende Überprüfung der HTTP-Anforderungseingabe im Controller erfolgen.

Zusammenfassen.

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

2) Ein Modell kann viele Quellen für Eingabedaten haben: eine HTTP-Anforderung, 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 Validierung der HTTP-Anforderungseingabe als Teil der Geschäftsanforderungen für das Programm. Fall abgeschlossen.

3) Es ist jedoch kurzsichtig, die Kosten für die Instanziierung vieler Objekte zu tragen, wenn die HTTP-Anforderungseingabe nicht gut ist! Sie können feststellen, ob ** HTTP-Anforderungseingabe ** gut ist ( das mit der Anforderung eingegangen ist), indem Sie sie validieren, bevor Sie das Modell und alle seine Komplexitäten instanziieren (ja, vielleicht sogar 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) Korrigieren Sie HTML-Steuerelemente (haben Sie die richtigen?).

e) Eingabecodierung (ist normalerweise die Codierung UTF-8?).

f) Maximale Eingabegröße (ist eine der Eingaben außerhalb der Grenzen?).

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

Was ich hier beschrieben habe, trifft auf die Absicht der Anfrage, die über den Controller eingeht. Wenn Sie die Überprüfung von intent weglassen, ist Ihre Anwendung anfälliger. Absicht kann nur gut (nach Ihren Grundregeln spielen) oder schlecht (außerhalb Ihrer Grundregeln) sein.

Absicht für eine HTTP-Anfrage ist ein Alles-oder-Nichts-Vorschlag. Alles geht vorbei oder die Anfrage ist ungültig. Sie müssen nichts an das Modell senden.

Diese grundlegende Ebene der HTTP-Anforderung Absicht hat nichts mit regelmäßigen Benutzereingabefehlern und Validierung zu tun. In meinen Anwendungen muss eine HTTP-Anfrage auf die oben genannten fünf Arten gültig sein, damit ich sie erfüllen kann. In einer Tiefenverteidigung Art zu sprechen, erhalten Sie niemals eine Validierung der Benutzereingaben auf der Serverseite, wenn any diese fünf Dinge scheitern.

Ja, dies bedeutet, dass auch die Dateieingabe Ihren Front-End-Versuchen entsprechen muss, die maximal akzeptierte Dateigröße zu überprüfen und dem Benutzer mitzuteilen. Nur HTML? Kein JavaScript? Gut, aber der Benutzer muss über die Folgen des Hochladens zu großer Dateien informiert werden (hauptsächlich, dass alle Formulardaten verloren gehen und aus dem System geworfen werden).

4) Bedeutet dies, dass HTTP-Anforderungseingabedaten 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 zu beenden. Sie zahlen mehr Rechenressourcen, wenn Sie darauf warten, es später zu stoppen.

5) Wenn die HTTP-Anforderungseingabe fehlerhaft ist, die gesamte Anforderung ist fehlerhaft. So sehe ich das. Die Definition einer guten HTTP-Anforderungseingabe wird aus den Geschäftsanforderungen des Modells abgeleitet, es muss jedoch ein Punkt der Ressourcenabgrenzung vorhanden sein. Wie lange werden Sie eine schlechte Anfrage leben lassen, bevor Sie sie töten und sagen: "Oh, hey, egal. Schlechte Anfrage."

Das Urteil ist nicht einfach, dass der Benutzer einen vernünftigen Eingabefehler gemacht hat, sondern dass eine HTTP-Anfrage so außerhalb der Grenzen liegt, dass sie als bösartig deklariert und sofort gestoppt werden muss.

6) Für mein Geld ist die HTTP-Anfrage (METHODE, URL/Route und Daten) entweder ALLES gut oder NICHTS anderes kann fortgesetzt werden. Ein robustes Modell muss sich bereits mit Validierungsaufgaben befassen, aber ein guter Ressourcenhirte sagt: "Mein Weg oder der hohe Weg. Komm richtig oder komm überhaupt nicht."

Es ist jedoch Ihr Programm. "Es gibt mehr als einen Weg, dies zu tun." Einige Wege kosten mehr Zeit und Geld als andere. Das spätere Überprüfen von HTTP-Anforderungsdaten (im Modell) sollte über die Lebensdauer einer Anwendung mehr kosten (insbesondere beim Skalieren oder Verkleinern).

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

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

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

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

Sie haben keine Garantie für JavaScript im Frontend. Dies bedeutet, dass Sie keine Möglichkeit haben, eine asynchrone Aktualisierung der Benutzeroberfläche Ihrer Anwendung mit Fehlerstatus zu gewährleisten. Eine echte progressive Verbesserung würde auch den synchronen Anwendungsfall abdecken.

Die Berücksichtigung des synchronen Anwendungsfalls ist eine Kunst, die immer mehr verloren geht, weil einige Leute nicht die Zeit und den Aufwand durchlaufen möchten, den Status aller ihrer UI-Tricks zu verfolgen (Steuerelemente ein-/ausblenden, Steuerelemente deaktivieren/aktivieren) , Fehleranzeigen, Fehlermeldungen) im Back-End (normalerweise durch Verfolgen des Status in Arrays).

Update : Im Diagramm sage ich, dass das View auf das Model verweisen sollte. Nein. Sie sollten Daten von View an Model übergeben, um eine lose Kopplung zu gewährleisten. (enter image description here

2