Formulare für Dateneingaben

Quickstart

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.

  1. Das Form Model
  2. Einbindung im Controller
  3. Formular View
  4. Finale Arbeiten
  5. Fazit

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.

Es ist auch möglich, Eingabewerte im Mapping automatisch anzupassen oder gar zu korrigieren. Zum Beispiel kannst du www.hnm.ch automatisch zu http://www.hnm.ch korrigieren. Im Quickstart gehen wir aber nicht darauf ein. 

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:

  1. ValNotEmpty: stellt sicher, dass die Felder (Content, Email) nicht leer sind.
  2. ValEmail: überprüft, ob das Feld eine gültige E-Mail Adresse ist.
  3. 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.

n2n unterstützt viele Validatoren. Du kannst aber auch eigene Validatoren erstellen.
Bei User Generated Content (UGC) ist es äusserst wichtig, dass sämtliche Eingaben genau validiert werden. Besonders wichtig ist die Validierung, wenn Files hochgeladen werden können.

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()).

Wie müsste $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.

n2n arbeitet mit Transaktionen. Im Quickstart gehen wir nicht genauer auf diese ein.

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/Verkleinern
  • crop: mittiges, formatgenaues Zuschneiden
  • cropTop: 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).

n2n belässt die hochgeladenen Bilder unverändert und fertigt sich für die einzelnen Formate Kopien an.

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!

Der Endstand des Moduls ist in qs4 festgehalten. Du kannst den Code mit http://localhost/[pfad-zum-root-directory]/blog4 aufrufen.
Das Modul zum herunterladen

Hier der fixfertige Code des Moduls. Bitte vergiss nicht, dass das Modul auch die app.ini Einträge braucht, um lauffähig zu sein.

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!

« MVC zusammensetzen Entwicklung »

Kommentare

Du musst eingeloggt sein, damit du Beiträge erstellen kannst.

Fragen