MVC + Szenario gegen Fat Controller

MVC + -Szenario vs. Thick Controller


Moderne PHP-Frameworks (Symphony, Laravel, im Folgenden überall) zeigen überzeugend, dass die Implementierung des Model-View-Controller-Musters nicht so einfach ist. Aus irgendeinem Grund sind alle Implementierungen anfällig für fat controllers , die von allen, sowohl den Entwicklern als auch den Frameworks selbst, verurteilt werden.


Warum ist das so? Und gibt es eine Möglichkeit, damit umzugehen? Lass es uns richtig machen.


Terminologie


  • Modell - Modell (Shaper der angeforderten Daten)
  • Ansicht - Ansicht (Modelldatendekorateur)
  • Controller - Controller (Modellansichtskoordinator wie gewünscht)
  • Vorlage - Präsentationsvorlage
  • Rendern - Rendern (Bildung, Gestaltung des Präsentationsbildes)
  • Renderer - Renderer (Shaper, Designer des Präsentationsbildes)

Dicker Controller


Hier ist ein typischer Fat Controller:


 class UserController { /** *   *      ID */ public function actionUserHello($userId) { //         ( ) $user = UserModel::find($userId); //       -   $name = $user->firstName.' '.$user->lastName; //         $view = new View('hello', ['name' => $name]); //  ( )     return $view->render(); } } 

Was sehen wir? Wir sehen Vinaigrette! In der Steuerung ist alles, was möglich ist, gemischt - sowohl das Modell als auch die Präsentation und tatsächlich die Steuerung selbst!


Wir sehen die Namen des Modells und der Vorlage fest mit dem Controller verbunden. Dies ist kein Summen. Wir sehen Manipulationen mit den Modelldaten in der Steuerung - die Bildung eines vollständigen Namens aus dem Vor- und Nachnamen. Und das ist kein Summen.


Und noch etwas: Wir sehen dieses Beispiel nicht explizit, aber es ist implizit. Nämlich: Es gibt nur einen Weg zum Rendern (Bilderzeugung)! Nur eins: laut Vorlage in der PHP-Datei! Und wenn ich pdf will? Und wenn ich nicht in einer Datei, sondern in einer PHP-Zeile will? Ich hatte Designs mit aufwändigen Designs auf Hunderten von kleinen Vorlagen. Ich musste den Renderer für String-Vorlagen selbst herausplatzen lassen. Ich habe natürlich nicht überhitzt, aber die Sache ist im Prinzip.


Kurze Zusammenfassung:


Moderne Frameworks haben gemeinsame Mängel bei der Implementierung von MVC für alle:
  1. Enge Interpretation von MVC-Ansicht (Ansicht) nur als "Ansicht mit einer Vorlage in einer PHP-Datei" anstelle von "Ansicht mit einem beliebigen Renderer".
  2. Enge Interpretation des MVC-Modells nur als "Datenbankmodelldomäne" anstelle von "Beliebiger Datencompiler zur Präsentation".
  3. Sie provozieren die Verwendung der sogenannten "Thick Controller", die alle Logik gleichzeitig enthalten: Geschäft, Präsentation und Interaktion. Dies zerstört das Hauptziel von MVC - die Aufteilung der Verantwortlichkeiten zwischen den Komponenten der Triade.

Um diese Mängel zu beheben, wäre es schön, die Komponenten von MVC genauer zu betrachten.


View ist ein Renderer


Schauen Sie sich den ersten Nachteil an:


  1. Enge Interpretation von MVC-Ansicht (Ansicht) nur als "Ansicht mit einer Vorlage in einer PHP-Datei" anstelle von "Ansicht mit einem beliebigen Renderer".

Hier ist alles ganz einfach - die Lösung des Problems ist bereits in der Problemstellung selbst angegeben. Wir müssen nur sagen, dass jeder Renderer die Ansicht verwenden kann. Um dies zu implementieren, fügen Sie einfach die neue renderer Eigenschaft zur View-Klasse hinzu:


 class View { public $template, $data, $renderer; public function __costruct($template, $data, $renderer = NULL) {} } 

Daher haben wir eine neue renderer Eigenschaft für die Ansicht definiert. Im allgemeinsten Fall kann der Wert dieser Eigenschaft eine beliebige callable Funktion sein, die ein Bild der Daten erstellt, die mithilfe der übertragenen Vorlage an sie übertragen wurden.


Die meisten Anwendungen verwenden nur einen Renderer, und selbst wenn sie mehrere verwenden, wird einer von ihnen bevorzugt. Daher wird das renderer Argument als optional definiert, vorausgesetzt, es gibt einen Standard-Renderer.


Ist es einfach Einfach. Eigentlich nicht so einfach. Tatsache ist, dass die View in MVC nicht genau die View in den Frameworks ist. Die View im Framework kann nicht ohne Vorlage leben. Aber die View , die in MVC aus irgendeinem Grund nichts über dieselben Vorlagen weiß. Warum? Ja, denn für MVC View ist dies ein Konverter von Modelldaten in ein Bild und nicht nur eine Vorlagen-Engine. Wenn wir so etwas in den Request-Handler schreiben:


 $name = ' '; return "Hello, {$name}!"; 

oder sogar:


 $return json_encode($name); // Ajax response 

Dann definieren wir wirklich die View , die sich in MVC befindet, ohne eine View zu berühren, die sich in den Frameworks befindet!


Aber jetzt ist alles wirklich einfach: diese View , die in den Frameworks - dies ist eine Teilmenge dieser View , die sich in MVC befinden. Darüber hinaus ist eine sehr enge Teilmenge, nämlich es ist nur eine Template-Engine, die auf PHP-Dateien basiert.


Zusammenfassung: Es ist der , d.h. Jeder Dekorateur eines Datenbildes ist die View in MVC. Und diese View , die sich in den Frameworks befindet, ist nur eine Art .


Domänenmodell / Ansichtsmodell (ViewModel / DomainModel)


Schauen Sie sich nun den zweiten Nachteil an:


  1. Enge Interpretation des MVC-Modells nur als "Datenbankmodelldomäne" anstelle von "Beliebiger Datencompiler zur Präsentation".

Es ist für jeden offensichtlich, dass das MVC-Modell eine komplexe Sache ist, die aus anderen Teilen besteht. Die Community erklärt sich damit einverstanden, das Modell in zwei Komponenten zu zerlegen: ein Domänenmodell (DomainModel) und ein Präsentationsmodell (ViewModel).


Ein Domänenmodell ist das, was in Datenbanken gespeichert ist, d.h. normalisierte Modelldaten. Geben Sie "Vorname" und "Nachname" in verschiedene Felder ein. Die Frameworks sind mit diesem bestimmten Teil des Modells beschäftigt, einfach weil die Datenspeicherung ein eigenes Universum ist, das gut untersucht wurde.


Eine Anwendung benötigt jedoch eher aggregierte als normalisierte Daten. Domain-Daten müssen zu Bildern wie "Hallo, Ivan!" Oder "Lieber Ivan Petrov!" Oder sogar "Für Ivan a Petrov a !" Zusammengestellt werden. Diese konvertierten Daten werden auf ein anderes Modell bezogen - das Präsentationsmodell. Es ist also dieser Teil des Modells, der von modernen Frameworks immer noch ignoriert wird. Es wird ignoriert, da es keine Einigung darüber gibt, wie damit umgegangen werden soll. Und wenn Frameworks keine Lösung bieten, gehen Programmierer den einfachsten Weg - sie werfen das Ansichtsmodell in die Steuerung. Und sie bekommen die verhassten, aber unvermeidlichen Fat Controller!


Fazit: Um MVC zu implementieren, müssen Sie ein Ansichtsmodell implementieren. Es gibt keine anderen Optionen. Da die Darstellungen und ihre Daten beliebig sein können, geben wir an, dass wir ein Problem haben.


Szenario vs. Fettkontrolleure


Es gibt einen letzten Nachteil der Frameworks:


  1. Sie provozieren die Verwendung der sogenannten "Thick Controller", die alle Logik gleichzeitig enthalten: Geschäft, Präsentation und Interaktion. Dies zerstört das Hauptziel von MVC - die Aufteilung der Verantwortlichkeiten zwischen den Komponenten der Triade.

Hier kommen wir zu den Grundlagen von MVC. Lassen Sie uns klar sein. Daher übernimmt MVC die folgende Verteilung der Verantwortlichkeiten zwischen den Komponenten der Triade:


  • Die Steuerung ist die Interaktionslogik , d.h. Interaktionen sowohl mit der Außenwelt (Anfrage - Antwort) als auch mit der Innenwelt (Modell - Präsentation),
  • Das Modell ist Geschäftslogik , d.h. Generieren von Daten für eine bestimmte Anforderung,
  • Repräsentation ist die Logik der Repräsentation , d.h. Dekoration der vom Modell generierten Daten.

Mach weiter. Zwei Ebenen von Verantwortlichkeiten sind deutlich sichtbar:


  • Die Organisationsebene ist der Controller,
  • Die Führungsebene ist Modell und Repräsentation.

In einfachen Worten, der Controller steuert, Modell und Ansicht Pflug. Dies ist auf einfache Weise. Und wenn nicht auf einfache Weise, aber genauer? Wie genau lenkt der Controller? Und wie genau pflügen das Modell und der View?


Der Controller steuert wie folgt:


  • Erhält eine Anfrage von einer Bewerbung,
  • Legt fest, welches Modell und welche Ansicht für diese Anforderung verwendet werden soll.
  • Ruft das ausgewählte Modell auf und empfängt Daten von ihm.
  • Ruft die ausgewählte Ansicht mit vom Modell empfangenen Daten auf.
  • Gibt die von der Ansicht dekorierten Daten an die Anwendung zurück.

So etwas in der Art. Das Wesentliche an diesem Schema ist, dass sich das Modell und die Darstellung als Glieder in der Kette der Abfrageausführung herausstellen. Darüber hinaus durch aufeinanderfolgende Links: Zuerst konvertiert das Modell die Anforderung in einige Daten, dann werden diese Modelldaten von der Ansicht in eine Antwort konvertiert, die nach Bedarf für eine bestimmte Anforderung dekoriert ist. Wie eine humanoide Anfrage wird visuell mit Templatizatoren dekoriert, eine Android-Anfrage wird mit JSON-Encodern dekoriert.


Versuchen wir nun herauszufinden, wie genau die Darsteller pflügen - Modell und Präsentation. Wir haben oben gesagt, dass Konsens über die Zerlegung des Modells in zwei Unterkomponenten besteht: Domänenmodell und Präsentationsmodell. Dies bedeutet, dass es mehr Künstler geben kann - nicht zwei, sondern drei. Anstelle einer Ausführungskette


>>

Es kann durchaus eine Kette geben


>> anzeigen >>

Die Frage stellt sich: Warum nur zwei oder drei? Und wenn Sie mehr brauchen? Die natürliche Antwort ist, um Gottes willen, nimm so viel, wie du brauchst!


Andere nützliche Künstler sind sofort sichtbar: Validatoren, Redirectors, verschiedene Renderer und im Allgemeinen alles, was unvorhersehbar, aber erfreulich ist.


Fassen wir zusammen:


  • Die MVC ( - ) auf Führungsebene kann als Kette von Gliedern implementiert werden, wobei jedes Glied die Ausgabe des vorherigen Links in die Eingabe für das nächste konvertiert.
  • Die Eingabe des ersten Links ist die Anwendungsanforderung.
  • Die Ausgabe des letzten Links ist die Antwort der Anwendung auf die Anforderung.

Ich habe dieses Scenario , aber für die Glieder der Kette habe ich mich noch nicht für den Namen entschieden. Aktuelle Optionen sind eine Szene (als Teil eines Skripts), ein Filter (als Datenkonverter) und eine Skriptaktion. Im Allgemeinen ist der Name für den Link nicht so wichtig, es gibt eine wichtigere Sache.


Die Folgen des Auftretens des Szenarios sind erheblich. Das Szenario übernahm nämlich die Hauptverantwortung des Controllers - das für die Anforderung erforderliche Modell und die Präsentation zu bestimmen und diese zu starten. Somit hat der Controller nur zwei Verantwortlichkeiten: Interaktion mit der Außenwelt (Anfrage-Antwort) und Ausführen des Skripts. Dies ist insofern gut, als alle Komponenten der MVC-Triade nacheinander zerlegt werden und spezifischer und überschaubarer werden. Und in anderer Hinsicht immer noch gut - der MVCS-Controller wird zu einer rein internen unveränderlichen Klasse und kann daher auch im Prinzip nicht fett werden.


Die Verwendung von Szenarien führt zu einer anderen Variation des MVC-Musters. Ich habe diese Variation MVCS - Model-View-Controller-Scenario .


Und noch ein paar Zeilen zur MVC-Zerlegung. Moderne Frameworks, in denen alle typischen Funktionen bis an die Grenzen zerlegt werden, haben dem konzeptionellen MVC-Teil der Verantwortung für die Interaktion mit der Außenwelt ganz natürlich entzogen. Daher sind speziell geschulte Klassen wie HTTP und mit der Verarbeitung von Benutzeranfragen beschäftigt. Infolgedessen empfängt der Controller nicht die anfängliche Benutzeranforderung, sondern eine verfeinerte , und dies ermöglicht es, den Controller von den Besonderheiten der Anforderung zu isolieren. In ähnlicher Weise wird von den Besonderheiten der HTTP-Antwort isoliert, sodass das MVC-Modul seinen eigenen Antworttyp definieren kann. Darüber hinaus haben die Frameworks die beiden Komponenten von MVC - das Domänenmodell und die Präsentationsvorlage - vollständig implementiert. Dies haben wir jedoch bereits erörtert. Ich bin das alles auf die Tatsache zurückzuführen, dass die Verfeinerung und Konkretisierung von MVC fortlaufend und kontinuierlich ist, und dies ist ein Summen.


MVCS-Beispiel


Lassen Sie uns nun sehen, wie das Beispiel des Fat Cortroller am Anfang dieses Artikels in MVCS implementiert werden kann.


Wir beginnen mit der Erstellung eines MVCS-Controllers:


 $mvcs = new MvcsController(); 

Der MVCS-Controller empfängt eine Anforderung von einem externen Router. Lassen Sie den Router den URI der Form 'user / hello / XXX' in eine solche Aktion konvertieren und Parameter anfordern:


 $requestAction = 'user/hello'; //   $requestParams = ['XXX']; //   -   

In Anbetracht der Tatsache, dass der MVCS-Controller Skripts anstelle von URIs akzeptiert, müssen wir der Aktion der Anforderung ein Skript zuordnen. Dies geschieht am besten im MVCS-Container:


 //   MVCS  URI  $mvcs->set('scenarios', [ 'user/hello' => 'UserModel > UserViewModel > view, hello', ..., ]); 

Schauen wir uns dieses Szenario genauer an. Dies ist eine Kette von drei Datenkonvertern, die durch ein '>' getrennt sind:


  • 'UserModel' ist der Name des Domänenmodells 'User', die Eingabe des Modells sind die Anforderungsparameter, die Ausgabe sind die tatsächlichen Daten des Modells.
  • 'UserViewModel' ist der Name des Ansichtsmodells, das Domänendaten in Ansichtsdaten konvertiert.
  • 'view, hello' ist die Systemansicht 'template' für eine PHP-Vorlage namens 'hello'.

Jetzt müssen wir nur noch zwei am Skript beteiligte Transformatoren als Abschlussfunktion zum MVCS-Container hinzufügen:


 //   UserModel $mvcs->set('UserModel', function($id) { $users = [ 1 => ['first' => '', 'last' => ''], 2 => ['first' => '', 'last' => ''], ]; return isset($users[$id]) ? $users[$id] : NULL; }); //   UserViewModel $mvcs->set('UserViewModel', function($user) { //    PHP  : 'echo "Hello, $name!"'; return ['name' => $user['first'].' '.$user['last']]; }); 

Und das ist alles! Für jede Anforderung ist es erforderlich, das entsprechende Skript und alle seine Szenen zu bestimmen (mit Ausnahme von Systemszenen wie "Ansicht"). Und nichts mehr.


Und jetzt können wir MVCS auf verschiedene Anforderungen testen:


 //        $scenarios = $mvcs->get('scenarios'); $scenario = $scenarios[$requestAction]; //      ... //   'user/hello/1'  ' '   'hello' $requestParams = ['1']; $response = $mvcs->play($scenario, $requestParams); //   'user/hello/2'  ' '   'hello' $requestParams = ['2']; $response = $mvcs->play($scenario, $requestParams); 

Die PHP MVCS-Implementierung wird auf github.com gehostet.
Dieses Beispiel befindet sich im example MVCS-Verzeichnis.

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


All Articles