Was haben Kommentare zum Artikel über Habré und zusätzliche Optionen beim Autokauf gemeinsam?

Unter dem Gesichtspunkt der Datenmodellierung sind beide "verschachtelte" Entitäten, die unabhängig vom übergeordneten Objekt keine unabhängige Bedeutung haben.
In Yii (
PHP-Framework ) gibt es Gii - einen integrierten Codegenerator, mit dem Sie mithilfe weniger Datenklicks grundlegende CRUD-Schnittstellen mithilfe eines Datenmodells erstellen können. Dies beschleunigt die Entwicklung erheblich, gilt jedoch nur für unabhängige Entitäten wie den Artikel oder die Maschine in den obigen Beispielen.
Es wäre großartig, etwas Ähnliches für "verschachtelte" Datenobjekte generieren zu können, oder? Jetzt - Sie können, willkommen bei kat für Details.
Für die Ungeduldigsten am Ende des Artikels werden Anweisungen für einen schnellen Start gegeben.
Und für diejenigen, die an dem Artikel interessiert sind, werden Aspekte von einer Geschäftsanwendung bis zu einem internen Gerät berücksichtigt:
- Business Case: Posting nach Thema
- Die Liste der Hauptthemen
- Liste verwandter Beiträge
- Unter der Haube: ein Gii-Generator basierend auf CRUD
- Gii Generator Vorlage
- Widget-Basisklasse
- Integrierte Fassadensteuerung
- Schnellstart
- Über Support und Entwicklung
Business Case: Posting nach Thema
Vielleicht Kommentare zu einem Habr und einem schlechten Beispiel seitdem sind oft nützlicher als der Artikel selbst, aber in jedem Fall gibt es bei der Entwicklung einer Anwendung häufig Situationen, in denen ein bestimmtes Objekt des Datenmodells für den Benutzer als unabhängige Einheit von geringem Interesse ist.
Stellen Sie sich eine vereinfachte Geschäftsaufgabe vor: Erstellen einer Website zum Veröffentlichen von Nachrichten, die nach verschiedenen Themen gruppiert sind.
Die Site sollte die folgenden Schnittstellen haben:
- Die Hauptseite - sollte in Zukunft verschiedene Widgets unterstützen, aber in der aktuellen Implementierungsphase gibt es nur eines: eine Liste von Themen, die nach bestimmten Kriterien gefiltert werden.
- Vollständige Liste der Themen - eine vollständige Liste der Themen in Tabellenform;
- Themenseite - Informationen zum Thema und eine Liste der darin veröffentlichten Beiträge.
Ziemlich normal, oder?
Schauen wir uns das Datenmodell an:

Auch keine Überraschungen. Zwei Klassen von Modellen enthalten unsere Geschäftslogik:
- Themenklasse - Daten zum Thema, Validierung, eine Liste der darin enthaltenen Beiträge sowie eine separate Methode, die eine Liste der Themen zurückgibt, die nach den Kriterien für das Widget auf der Hauptseite gefiltert sind.
- Die Post- Klasse besteht nur aus Daten und Validierung.
Die Anwendung wird von zwei Controllern bedient:
- SiteController - Standardseiten (über uns, Kontakte usw.), Autorisierung (nicht nach technischen Spezifikationen erforderlich, aber wir wissen etwas) und Index - die Hauptseite. Weil Wir erwarten in Zukunft viele verschiedene Widgets. Es ist sinnvoll, die Hauptseite in diesem Controller zu belassen und nicht auf ein bestimmtes Modell zu übertragen.
- TopicController ist eine Standardaktion: Auflisten , Erstellen, Bearbeiten, Anzeigen und Löschen von Themen.
Möglicherweise können Sie auch einen
PostController generieren - zu Verwaltungszwecken und / oder zum Kopieren und Einfügen von Codeteilen in benutzerdefinierte Widgets. Lassen Sie dies jedoch außerhalb des Geltungsbereichs dieses Artikels.
Bisher kann der größte Teil des Codes mit gii generiert werden, was die Entwicklung beschleunigt und Risiken reduziert (weniger manueller Code = weniger Fehlerwahrscheinlichkeit).
Zwei Fragen bleiben offen:
- Wie kann ich eine gefilterte Themenliste auf der Hauptseite anzeigen?
- Wie zeige ich eine Liste der Beiträge nach Thema an?
Wenn Sie sie mit einem automatischen Generator lösen können, ist dies eine solide Leistung.
Die Liste der Hauptthemen
Die Hauptseite, die von der Site- / Indexadresse bereitgestellt wird, sollte eine Liste von Themen enthalten, die nach einem vorgegebenen Kriterium gefiltert werden. Filterkriterien haben wir als Teil der Geschäftslogik in das Modell aufgenommen.
Für die Anzeige gibt es mehrere Implementierungsoptionen.
Das erste, schmutzige und schnelle, ist, alles direkt in der Ansichtsdatei (
views / site / index.php ) zu erledigen:
- ActiveDataProvider erstellen;
- Füllen Sie es mit Daten aus dem Themenmodell .
- Anzeige mit einem Standard- ListView / GridView- Widget, wobei die erforderlichen Felder manuell angegeben werden.
Sie können ein wenig weiter gehen und alles in eine separate Ansichtsdatei packen, z. B.
views / site / _topic-list-widget.php , und das Rendern aus der
Hauptdatei aufrufen. Dies gibt ein wenig mehr Verwaltbarkeit und Erweiterbarkeit, aber es sieht immer noch ziemlich schmutzig aus.
Die meisten von uns erstellen wahrscheinlich ein separates Widget gemäß allen Regeln in einem separaten Namespace (
App \ Widgets oder
App \ Komponenten für die
Basisvorlage - abhängig von der verwendeten Version), in dem die Erstellung von
ActiveDataProvider nach Modell zusammengefasst und in einem separaten angezeigt wird Einreichung. Sie müssen dieses Widget nur noch von der Hauptseite aus aufrufen. Diese Lösung ist unter dem Gesichtspunkt der Klassenzerlegung, Verwaltbarkeit und Erweiterbarkeit des Codes am korrektesten.
Aber scheint der Code dieses Widgets den Code von
TopicController in Bezug auf die Behandlung von
actionIndex () sehr oft zu wiederholen? Und es ist so ärgerlich, diesen Code manuell zu schreiben.
Es wäre viel besser, diesen Code automatisch zu generieren und dann einfach das fertige Widget aufzurufen:
<?= \app\widgets\TopicControllerWidget::widget([ 'action' => 'index', 'params' => [ 'query' => app\models\Topic::findBySomeSpecificCriteria() ], ]) ?>
Liste verwandter Beiträge
Die Seite zum Anzeigen des von der
Themen- / Ansichtsadresse bereitgestellten
Themas sollte Informationen zum Thema selbst und eine Liste der darin veröffentlichten Nachrichten enthalten. Wir erhalten die Liste der Nachrichten für das Thema im Modell automatisch, wenn wir die Beziehungen zwischen den Tabellen korrekt konfiguriert haben, sodass nur die Anzeigefrage übrig bleibt.
In Analogie zur gefilterten Themenliste haben wir fast die gleichen Optionen.
Die erste besteht darin, alles im Code der Ansichtsdatei zu tun, um das Thema anzuzeigen (
views / topic / view.php ):
- ActiveDataProvider erstellen;
- Füllen Sie es mit Daten aus dem Modell $ model-> getPosts () ;
- Anzeige mit einem Standard- ListView / GridView- Widget, wobei die erforderlichen Felder manuell angegeben werden.
Die zweite besteht darin, diesen Code in eine separate Präsentationsdatei zu isolieren:
views / topic / _posts-list-widget.php , nur um kein Dorn im Auge zu sein - die Wiederverwendung irgendwo schlägt immer noch fehl.
Das dritte ist ein vollwertiges Widget, das den Code des bedingten
PostControllers im
actionIndex () - Teil weitgehend dupliziert, jedoch manuell geschrieben.
Oder generieren Sie den Code automatisch und rufen Sie das fertige Widget auf:
<?= app\widgets\PostControllerWidget::widget([ 'action' => 'index', 'params' => [ 'query' => $model->getPosts(), ], ]) ?>
Unter der Haube: ein Gii-Generator basierend auf CRUD
Die Geschäftsaufgabe wird definiert, die Anforderungen für das generierte Widget werden umrissen, wir werden herausfinden, wie genau wir es generieren werden. Gii hat bereits einen Generator für den CRUD-Controller. Für ein CRUD-Widget müssen wir einen neuen Generator erstellen, der auf dem vorhandenen basiert.
Ein paar Links zur Dokumentation vor dem Start - es ist auch nützlich, wenn Sie Ihre eigene Erweiterung schreiben möchten:
Offensichtlich sind alle Funktionen in der Yii-Erweiterung enthalten, die über Composer installiert wird und in den Herstellerordner Ihres Projekts gelangt.
Die Erweiterung besteht aus drei Teilen:
- Das Templates / Crud-Verzeichnis, das die Gii-Generator-Vorlage enthält.
- Controller.php- Datei - integrierter Fassadencontroller für Widget-Aufrufe;
- Die Datei Widget.php ist die Basisklasse für alle generierten Widgets.

Gii Generator Vorlage
Die Erweiterung muss Code generieren, daher ist ihr zentraler Teil der Gii-Generator.
Zunächst wurde angenommen, dass es zur Implementierung der Erweiterung ausreichen würde, eine eigene Vorlage für den integrierten CRUD-Controller-Generator zu schreiben. Aus diesem Grund wird das Verzeichnis übrigens als Vorlagen und nicht als Generatoren bezeichnet. Es stellte sich jedoch heraus, dass der CRUD-Controller-Generator eine sehr intensive Validierung der Eingabedaten durchführt, die es nicht ermöglichte, viele Anforderungen zu implementieren, z. B. die Klasse für die Vererbung zu ändern. Daher enthält die Erweiterung einen vollwertigen Generator und nicht nur eine Vorlage.
Der Gii-Generator besteht aus folgenden Teilen (alle befinden sich im Verzeichnis templates / crud):
- Das Standardverzeichnis ist eine Vorlage, in der die ganze Magie passiert: Jede Datei in diesem Verzeichnis entspricht einer generierten Datei in Ihrem Projekt.
- Datei form.php - Wie Sie anhand des Namens erraten können, ist dies ein Formular zur Eingabe von Generierungsparametern (Klassennamen usw.).
- File Generator.php - Ein Generationsorchester, das Daten aus dem Formular empfängt, validiert und anschließend die Vorlagendateien nacheinander aufruft, um das Ergebnis zu erstellen.
Die
Dateien Generator.php und
form.php enthalten hauptsächlich kosmetische Änderungen gegenüber den Originaldateien des CRUD-Generators: Dateinamen, Validierung, Beschreibungen und Eingabeaufforderungstexte usw.
Vorlagendateien sind für die generierte Ansicht und den Widget-Code selbst verantwortlich. Zunächst ist die Datei
templates / crud / default / controller.php wichtig, die für die direkte Generierung der der Controller-Klasse entsprechenden Widget-Klasse aus dem ursprünglichen Generator verantwortlich ist.
Das Widget sollte dieselben Aktionen wie der CRUD-Controller haben, sie werden jedoch etwas anders generiert. Die folgenden Beispiele zeigen das Ergebnis der Generierung mit Kommentaren:
- actionIndex - Anstelle der bedingungslosen Ausgabe aller Modelle akzeptiert die Methode den Parameter $ query.
public function actionIndex($query) { $dataProvider = new ActiveDataProvider([ 'query' => $query, ]); return $this->render('index', [ 'dataProvider' => $dataProvider, ]); }
- actionCreate und actionUpdate - Im Erfolgsfall geben sie anstelle einer Umleitung einfach den Erfolgscode zurück. Die weitere Verarbeitung erfolgt durch die integrierte Fassadensteuerung.
public function actionCreate() { $model = new Post(); if ($model->load(Yii::$app->request->post()) && $model->save()) { return 'success'; } return $this->render('create', [ 'model' => $model, ]); }
- actionDelete - unterstützt die GET-Methode zum Anzeigen des Lösch-Widgets (standardmäßig - eine Schaltfläche) und POST zum Ausführen der Aktion. Bei Erfolg wird auch keine Umleitung durchgeführt, sondern ein Code zurückgegeben.
public function actionDelete($id) { $model = $this->findModel($id); if (Yii::$app->request->method == 'GET') { return $this->render('delete', [ 'model' => $model, ]); } else { $model->delete(); return 'success'; } }
Schließlich enthalten Ansichtsdateien die folgenden grundlegenden Änderungen:
- Alle Header wurden in h2 anstelle von h1 übersetzt.
- Der Code, der für die Anzeige des Titels der Seite und für Breadcrumbs verantwortlich ist, wurde entfernt. Das Widget sollte diese Dinge nicht beeinflussen.
- Das Erstellen und Bearbeiten von Modellen erfolgt über ein modales Fenster (integriertes modales Widget).
- Lösch-Widget-Vorlage hinzugefügt - mit einer großen roten Schaltfläche.
Widget-Basisklasse
Wenn der Generator seine Arbeit beendet hat, erstellt er eine Widget-Klasse im Anwendungsnamespace. Die Vererbungskette sieht folgendermaßen aus: Für die Anwendung generierte Widgets werden vom
Basiserweiterungs- Widget class
\ ianikanov \ wce \ Widget geerbt, das wiederum vom Basis-Yii-Widget class
\ yii \ base \ Widget geerbt wird .
Die Basisklasse des Erweiterungs-Widgets löst die folgenden Aufgaben:
- Definiert zwei Hauptfelder: $ action und $ params, über die die Steuerung aus der aufrufenden Ansicht an das Widget übertragen wird.
- Definiert eine Reihe von Standardparametern, die in der generierten Klasse überschrieben werden können, z. B. den Pfad zu den Ansichtsdateien des Widgets, den Namen und den Pfad zur Fassadensteuerung (siehe unten) und Fehlermeldungen.
- Definiert Standardparameter beim Rendern von Ansichten: render und renderFile;
- Bietet eine Ereignisinfrastruktur ähnlich der Controller-Infrastruktur, sodass Standardfilter wie AccessControl und VerbFilter funktionieren .
- Definiert eine Ausführungsmethode, die all dies zusammen sammelt.
Integrierte Fassadensteuerung
Es gibt keine Probleme bei der Anzeige dieser Daten - Widgets sind für diesen Zweck vorgesehen. Für die Bearbeitung benötigen Sie jedoch einen Controller. Generieren Sie für jedes Widget einen eindeutigen Controller - seine gesamte Essenz geht verloren. Die Verwendung einer Standard-CRUD ist nicht immer relevant, und ich möchte nicht auf den zusätzlichen Start von gii angewiesen sein. Daher wurde die Option mit einer universellen, integrierten Steuerungsfassade verwendet.
Dieser Controller ist in der Anwendungszuordnung über die Konfigurationsdatei registriert und enthält nur eine Methode - actionIndex, die die folgenden Aktionen ausführt:
- Akzeptiert eine Anfrage von einem Kunden;
- Überträgt die Kontrolle an die entsprechende Widget-Klasse.
- Behandelt Geschäftsfehler aufgrund des Widgets.
- Leitet zurück zur Hauptanwendung.
Vielleicht ist es wichtiger anzugeben, was dieser Controller NICHT tut:
- Die Zugriffsebenen werden nicht überprüft. Diese Logik gehört zu bestimmten Widgets.
- Es werden keine Eingabemanipulationen durchgeführt. Die Parameter werden unverändert an das Widget übergeben.
- Die Ausgabe wird nur manipuliert, um nach einem vordefinierten Erfolgscode zu suchen.
Mit diesem Ansatz können Sie die Vielseitigkeit der Fassade beibehalten und die Implementierung der Geschäftsanforderungen, einschließlich der Sicherheitsanforderungen, des Anwendungsanwendungscodes überlassen.
Schnellstart
Die geschäftliche Herausforderung ist klar, bereit zu beginnen? Die Verwendung der Erweiterung umfasst vier Schritte:
- Installation;
- Konfiguration;
- Generation;
- Anwendung.
Die Installation der Erweiterung erfolgt mit Composer:
php composer.phar require --prefer-dist ianikanov/yii2-wce "dev-master"
Als Nächstes müssen Sie mehrere Änderungen an der Anwendungskonfigurationsdatei vornehmen.
Fügen Sie zunächst einen Verweis auf den Gii-Generator hinzu:
if (YII_ENV_DEV) { $config['modules']['gii'] = [ 'class' => 'yii\gii\Module', 'allowedIPs' => ['127.0.0.1', '::1', '192.168.0.*', '192.168.178.20'], 'generators' => [
Zweitens fügen Sie der Karte den integrierten Fassadencontroller hinzu:
$config = [ ... 'controllerMap' => [ 'wce-embed' => '\ianikanov\wce\Controller', ], ... ];
Damit ist die Installation und Konfiguration abgeschlossen.
So generieren Sie ein Widget:
- Öffnen Sie gii;
- Wählen Sie "CRUD Controller Widget";
- Füllen Sie die Formularfelder aus.
- Code anzeigen und generieren.
Um das Widget verwenden zu können, muss es durch Angabe von Aktion und Parametern aufgerufen werden - fast so, wie der Controller aufgerufen wird.
Widget zum Anzeigen der Modellliste:
<?= app\widgets\PostControllerWidget::widget([ 'action' => 'index', 'params' => [ 'query' => $otherModel->getPosts(), ], ]) ?>
Widget zum Anzeigen eines Modells:
<?= app\widgets\PostControllerWidget::widget(['action' => 'view', 'params' => ['id' => $post_id]]) ?>
Widget zur Modellerstellung (Schaltfläche + Formular in Modal eingeschlossen):
<?= app\widgets\PostControllerWidget::widget(['action' => 'create']) ?>
Modellwechsel-Widget (Schaltfläche + Formular in Modal eingeschlossen):
<?= app\widgets\PostControllerWidget::widget(['action' => 'update', 'params'=>['id' => $post_id]]) ?>
Widget zum Entfernen von Modellen (Schaltfläche):
<?= app\widgets\PostControllerWidget::widget(['action' => 'delete', 'params'=>['id' => $post_id]]) ?>
Der Code des Widgets und aller Ansichten gehört zur Anwendung und kann leicht geändert werden - alles ist genau das gleiche wie beim Generieren des Controllers.
Über Support und Entwicklung
Ein paar Worte darüber, wie die Erweiterung unterstützt und weiterentwickelt wird. Ich habe die Hauptarbeit und einige meiner Nebenprojekte (Haustierprojekte). Diese Erweiterung ist also ein Nebenprojekt aus meinen Nebenprojekten, daher werde ich Verbesserungen nur für die Bedürfnisse meiner Projekte entwickeln.
In den besten Open Source-Traditionen ist der Code auf dem
Github verfügbar, und ich werde ihn bei der Behebung von Fehlern unterstützen. Ich werde auch versuchen, zeitnahe Überprüfungen
durchzuführen , wenn jemand eine Pull-Anfrage senden möchte. Wer also interessiert ist, macht mit.