Formulare für Dateneingaben
Benutzereingaben sind wichtige Bestandteile von Applikationen. Für Benutzereingaben brauchen wir Formulare. In n2n sind für die Erstellung und Verarbeitung von Formularen Form Models zuständig.
Das Form Model
Formulardaten werden in n2n von Form-Models verwaltet.
Erstelle das Form-Model app\qs\model\BlogCommentForm.php
mit folgendem Inhalt:
<?php namespace qs\model; use n2n\web\dispatch\Dispatchable; use qs\bo\BlogArticle; use n2n\web\dispatch\map\bind\BindingDefinition; use n2n\impl\web\dispatch\map\val\ValNotEmpty; use n2n\impl\web\dispatch\map\val\ValEmail; use n2n\impl\web\dispatch\map\val\ValImageFile; use n2n\io\managed\File; use n2n\web\dispatch\map\MappingResult; class BlogCommentForm implements Dispatchable { private $blogArticle; protected $email; protected $image; protected $content; public function __construct(BlogArticle $blogArticle) { $this->blogArticle = $blogArticle; } public function getBlogArticle() { return $this->blogArticle; } public function getEmail() { return $this->email; } public function setEmail(string $email) { $this->email = $email; } public function getImage() { return $this->image; } public function setImage(File $image = null) { $this->image = $image; } public function getContent() { return $this->content; } public function setContent(string $content) { $this->content = $content; } private function _mapping(MappingResult $mr) { $mr->setLabel('email', 'E-Mail'); $mr->setLabel('content', 'Dein Kommentar'); $mr->setLabel('image', 'Dein Bild'); } private function _validation(BindingDefinition $bd) { $bd->val(array('content', 'email'), new ValNotEmpty()); $bd->val('email', new ValEmail()); $bd->val('image', new ValImageFile(true)); } public function save(BlogDao $blogDao) { $blogDao->saveComment($this->blogArticle, $this->email, $this->content, $this->image); } }
Ein Form Model implementiert das Interface Dispatchable
. Formularfelder müssen entweder als protected
oder public
gekennzeichnet werden. Wenn sie protected
sind, braucht es zwingend Getter- und Setter-Methoden, damit die Felder ausgelesen, respektive überschrieben werden können.
Da ein Kommentar immer zu einem BlogArticle
gehört, erstellen wir einen Konstruktor, welcher zwingend die Übergabe des zugehörigen BlogArticle
erwartet. $blogArticle
speichern wir in der entsprechenden Eigenschaft.
Formular Mapping
Ein Form Model kann die private Methode _mapping()
enthalten. Im Mapping können den Eigenschaften Labels zugeteilt werden ($mr->setLabel()
). Diese Labels werden dann in der Formular View oder bei Fehlermeldung verwendet.
Formular Validierung
Ein Form-Model kann die private Methode _validation()
enthalten. Diese wird vor der Dispatch-Methode verarbeitet. Nur wenn die Validierung nicht fehl schlägt, wird die Dispatch-Methode ausgeführt. Die BindingDefinition
wird der _validation()
-Methode via Dependency Injection übergeben. Darauf werden dann die benötigten Validatoren registriert:
ValNotEmpty
: stellt sicher, dass die Felder (Content, Email) nicht leer sind.ValEmail
: überprüft, ob das Feld eine gültige E-Mail Adresse ist.ValImageFile
: überprüft, ob das File ein gültiges Bild ist.
Es ist möglich, den Validatoren eigene Fehlermeldungen zu übergeben. Für die in n2n definierten Validatoren sind aber bereits Fehlermeldungen hinterlegt. Beachte, dass die Fehlermeldungen in der im app.ini
hinterlegten Sprache erscheinen.
Dispatch Methode
In unserem Falle ist save()
die Dispatch Methode. Hier übergeben wir das BlogDao
und speichern den neuen Kommentar, indem wir auf die Eigenschaften des Form Models (also die eingegebenen Formularwerte) zurückgreifen.
Einbindung im Controller
Jetzt bauen wir das Form Model in unseren Controller ein. Dazu erstellen wir in BlogController
eine neue Action Methode, welche ein Kommentarformular zur Verfügung stellt:
public function doComment(int $blogId) { // Artikel holen über ID $blogArticle = $this->blogDao->getBlogArticleById($blogId); // Prüfen, ob Artikel gefunden if ($blogArticle === null) { throw new PageNotFoundException('invalid id: ' . $blogId); } $this->beginTransaction(); $commentForm = new BlogCommentForm($blogArticle); if ($this->dispatch($commentForm, 'save')) { $this->commit(); $this->redirectToController(array('thanks', $blogId)); return; } $this->commit(); $this->forward('..\view\comment.html', array('commentForm' => $commentForm)); }
Als Parameter erwarten wir die $blogId
des Artikels, für welchen der Kommentar erstellt wird. Als erstes überprüft die Methode, ob unter der angegebenen ID auch ein $blogArticle
gefunden wird.
Ist dies der Fall, starten wir eine Transaktion ($this->beginTransaction()
) und erstellen das Form Model (BlogCommentForm
). Der Controller versucht, dieses zu dispatchen ($this->dispatch()
). Als Parameter geben wir der Dispatch-Methode das Form Model ($commentForm
) und den Namen der Dispatch Methode ('save'
) mit. Der Dispatch ist erfolgreich, wenn Formulardaten überhaupt vorliegen und die Validierung nicht fehlschlägt. Im Erfolgsfall wird die Transaktion abgeschlossen ($this->commit()
), womit der neue Kommentar gespeichert wird. Danach leitet der Controller auf die Dankeseite weiter ($this->redirectToController()
).
$this->redirectToController()
ausschauen, damit direkt auf die Detail Seite des Artikels weitergeleitet wird?Ist der Dispatch nicht erfolgreich, können wir die Transaktion trotzdem committen. In diesem Falle wäre $this->commit()
nicht unbedingt notwendig, da am Ende einer Controller-Methode automatisch offene Transaktion abgeschlossen werden. Danach rufen wir die Kommentar-View auf, welcher wir unser Form Model ($commentForm
) übergeben.
Damit wir überhaupt an die Kommentar View weiterleiten können, müssen wir diese natürlich erstellen.
Formular View
Erstelle für unser Formular die View app\qs\view\comment.html.php
mit folgedem Inhalt:
<?php use n2n\impl\web\ui\view\html\HtmlView; use qs\model\BlogCommentForm; $view = HtmlView::view($this); $formHtml = HtmlView::formHtml($view); $commentForm = $view->getParam('commentForm'); $view->assert($commentForm instanceof BlogCommentForm); $view->useTemplate('boilerplate.html', array('title' => 'Dein Kommentar')); ?> <h1>Kommentieren</h1> <?php $formHtml->open($commentForm) ?> <?php $formHtml->messageList() ?> <div> <?php $formHtml->label('email') ?><br /> <?php $formHtml->input('email', array('maxlength' => 120)) ?> </div> <div> <?php $formHtml->label('content') ?><br /> <?php $formHtml->textarea('content', array('rows' => 5, 'cols' => 30)) ?> </div> <div> <?php $formHtml->label('image')?><br /> <?php $formHtml->inputFileWithLabel('image') ?> </div> <?php $formHtml->buttonSubmit('save', 'Kommentar speichern')?> <?php $formHtml->close() ?>
Jede HTML View stellt auch einen FormHtmlBuilder
$formHtml
zur Verfügung. Diesen brauchen wir, um die Formular Tags zu erstellen. Ausserdem laden wir das $commentForm
, welches über die Parameter der View übergeben wurde.
Ein Formular muss geöffnet ($form->open()
) und geschlossen ($form->close()
) werden. Beim Öffnen müssen wir immer das Form Model ($commentForm
) mitgeben. So weiss der FormHtmlBuilder
, welche Properties ($content
, $email
, $image
) zur Verfügung stehen.
Mit $form->messageList()
werden sämtliche Fehlermeldungen ausgegeben, welche die Validation ergeben hat.
Danach werden die verschiedenen Felder und Ihre Labels ausgegeben. Die korrekten Labels kennt $commentForm
bereits, weil wir diese unter _mapping()
bereits definiert haben. Wir könnten die Labels aber auch überschreiben, indem wir $html->label()
den zweiten Parameter $label
mitgeben.
Beim Submit ist relevant, dass die Dispatch Methode (in unserem Falle 'save'
) angegeben wird.
Finale Arbeiten
Momentan ist die soeben erstellte View noch nicht mit dem Rest unseres Modules verlinkt. Dies kannst du ändern, indem du in app\qs\view\detail.html.php
folgenden Code anfügst:
<h2>Kommentare</h2> <p><?php $html->linkToController(array('comment', $blogArticle->getId()), 'Kommentar verfassen') ?></p> <?php foreach ($blogComments as $blogComment): ?> <h3><?php $html->linkEmail($blogComment->getEmail()) ?></h3> <p><?php $html->escBr($blogComment->getContent()) ?></p> <p><strong>Datum:</strong> <?php $html->l10nDateTime($blogComment->getCreated(), DateTimeFormat::STYLE_LONG) ?></p> <?php if (null !== ($image = $blogComment->getImage())): ?> <?php $html->image($image, ThSt::prop(200, 300))?> <?php endif ?> <hr /> <?php endforeach ?>
Zuerst haben wir den Link zum Kommentar Formular hinzugefügt ($html->linkToController()
). Beachte, dass wir hier, wie im Controller vorgesehen, die ID des Artikels mitgeben. Somit ist unsere Formular View verlinkt. Jetzt kannst du die Kommentare ausgeben. Dazu kannst du mit einem foreach
über alle Kommentare iterieren.
Spezielle Funktionen des HtmlBuilder
Für die Ausgabe von E-Mail Links bietet der HtmlBuilder
eine spezielle Methode ($html->linkEmail()
) an.
Damit womöglich vorhandene Zeilenschläge in den Kommentaren auch als solche ausgegeben werden, verwenden wir bei der Ausgabe der Eigenschaft content
statt $html->out()
die Methode$html->escBr()
. Diese Methode ersetzt Zeilenschläge mit <br />
.
Ebenfalls eine Besonderheit ist die Ausgabe des Erstellungsdatums. Der HtmlBuilder bietet dafür diverse Lokalisierungsmethoden (diese starten alle mit l10n). In unserem Falle nutzen wir $html->l10nDateTime()
und geben das Erstellungsdatum sowie das gewünschte Zeitformat mit. Sämtliche Lokalisierungsmethoden berücksichtigen das aktuelle Locale. Da wir in unserem Modul keine Locales setzen, wird das Standardlocale aus dem app.ini
genommen.
Anzeige von Bildern
Bevor wir ein Bild ausgeben, prüfen wir mit if (null !== $image = $blogComment->getImage())
ob überhaupt eines vorhanden ist . Auch Bilder können wir über den HtmlBuilder einfach ausgeben ($html->image()
). Beachte, dass wir dem Bild eine ThumbStrategy
mitgeben können. Eine ThumbStrategy
bestimmt, wie das vorliegende Bild auf das mitgegebene Format (Breite, Höhe) verkleinert/vergrössert/zugeschnitten werden soll. Momentan kennt ThSt folgende Methoden:
prop
: proportionales Vergrössern/Verkleinerncrop
: mittiges, formatgenaues ZuschneidencropTop
: formatgenaues Zuschneiden, oben zentriert ausgerichtet
Diesen Methoden wird jeweils die Breite, die Höhe und die Angabe mitgegeben, ob das Bild vergrössert werden darf (Vergrösserung führt zu Qualitätsverlust des Bildes).
Bestätigungs-View
Anpassen müssen wir im BlogController noch den Aufruf der Danke-View:
public function doThanks(int $blogId) { // Artikel holen über ID $blogArticle = $this->blogDao->getBlogArticleById($blogId); // prüfen, ob Artikel gefunden if ($blogArticle === null) { throw new PageNotFoundException('invalid id: ' . $blogId); } $this->forward('\qs\view\thanks.html', array('blogArticle' => $blogArticle)); }
Wir erwarten jetzt auch in dieser Methode eine $blogId
. Damit laden wir $blogArticle
und übergeben ihn der View, damit wir dort zurück auf den relevanten Artikel linken können.
Unsere View (app\qs\view\thanks.html.php
) müssen wir jetzt auch noch etwas anpassen:
<?php use n2n\impl\web\ui\view\html\HtmlView; use n2n\web\ui\view\View; use qs\bo\BlogArticle; $view = HtmlView::view($this); $html = HtmlView::html($view); $blogArticle = $view->getParam('blogArticle'); $view->assert($blogArticle instanceof BlogArticle); $view->useTemplate('boilerplate.html', array('title' => 'Danke')); ?> <h1>Kommentar erfasst</h1> <p>Danke für deinen Kommentar</p> <?php $html->linkToController($blogArticle->getUrlPart(), 'zurück zum Artikel') ?>
Hier gibt es aber nichts, was du nicht schon kennst.
Jetzt ist es an der Zeit, dass du das Modul mal so richtig testest und ein paar Kommentare erfasst! Wir wünschen dir viel Spass!
qs4
festgehalten. Du kannst den Code mit http://localhost/[pfad-zum-root-directory]/blog4
aufrufen.Fazit
Damit ist der Quickstart abgeschlossen. Wir haben dir die wichtigsten Funktionen von n2n demonstriert. Wir hoffen, dass wir dich überzeugen konnten, dass n2n ein mächtiges und flexibles Framework ist. Es eignet sich sowohl für einfache als auch komplexe Internet-Projekte. Das beste hast du aber noch gar nicht gesehen. Es ist die nahtlose Integration von Rocket, unserem CMS.
Der nächste logische Schritt ist darum, mit dem Quickstart von Rocket weiter zu machen!