Mehrseitige Formulare

Sunday, 21 December 2008, 17:07 von Blackflash

Mehrseitige Formulare werden in vielen komplexen Webanwendungen benötigt und werden häufig als statische Verkettung von verschiedenen Controllern implementiert. Es gibt allerdings eine einfache Alternative unter Nutzung von Entwurfsmustern, namentlich Zuständigkeitskette (engl. Chain of Responsibility) und Datentransferobjekt.

Mithilfe der Zuständigkeitskette komponieren wir mehrere eigenständige Formulare zu einer Komposition von Formularen, dem mehrseitigen Formular. Bsp.: Formular A beinhaltet Nutzerdaten, Formular B Kontodaten und Formular C optionale Angaben. Aus diesen Formularen lässt sich die Komposition ABC bilden, die Nutzerdaten, Kontodaten und optionale Angaben auf drei verschiedenen Seiten abfragt. Um eine solche Komposition zu realisieren, muss die Zuständigkeitskette folgende Methoden anbieten:

  • accept: Gibt true zurück, wenn das Formular noch nicht (korrekt) abgearbeitet wurde, d.h. es wird zur Bearbeitung akzeptiert.
  • process: Arbeitet das Formular ab (Validierung der Eingaben, Schreiben der Daten ins Transfer-Objekt, ...) und gibt true zurück, wenn die Eingaben korrekt sind.
  • dispatch: Arbeitet die Komposition ab, d.h. für das erste Element $form der Komposition, für das $form->accept(...) == true gilt, wird $form->process(...) aufgerufen. Die Methode gibt true zurück, wenn die gesamte Komposition korrekt abgearbeitet wurde.
  • attach: (Direktes) Anhängen eines Formulars an ein anderes Formular.

Ferner ist ein hinreiches Kriterium notwendig, um festzustellen, ob ein Formular (korrekt) abgearbeitet wurde. Hierfür wird ein Transfer-Objekt benutzt, das die eingegeben Formulardaten speichert, Zugriff auf jene anbietet und evtl. Zugriff auf den Workflow bietet, wie z.B. das Bestimmen, welches Template angezeigt werden soll. Beim Abfertigen der Komposition wird also jedem Formular das Transfer-Objekt an die Methode accept übergeben, bis ein Formular $form gefunden wurde, für das $form->accept($transfer) == true gilt. Danach wird $form->process($transfer) und im Erfolgsfall $form->dispatch($transfer) ausgeführt, damit das nächste Formular zum Abarbeiten angeboten wird. Um eine Endlosschleife zu verhindern, muss muss für alle Objekte $form folgende Eigenschaft gelten: $form->process($transfer) == !$form->accept($transfer). Wesentlich ist dabei die Reihenfolge der Auswertung (von links nach rechts). Wenn ein Formular korrekt ausgewertet wurde, dann darf das Formular das Transfer-Objekt nicht mehr akzeptieren. Andererseits muss das Formular das Transfer-Objekt akzeptieren, wenn eine fehlerhafte Eingabe gemacht wurde.
Nun zu einer Eigenschaft der Methode dispatch: dispatch($transfer) == true dann und nur dann, wenn alle Formulare korrekt abgearbeitet wurden. Es stellt sich heraus, dass man nur wissen muss, ob das letzte Element der Komposition korrekt abgearbeitet wurde. Die Begründung: Um zum letzten Formular $lastForm der Komposition zu gelangen, muss für jedes vorangegangene Formular $form die Eigenschaft $form->accept($transfer) == false gelten, womit $form->process($transfer) == true ist. Also wurde die Komposition genau dann korrekt ausgeführt, wenn $lastForm->process($transfer) == true gilt. Eine schöne Eigenschaft der dispatch-Methode ist, dass sie bei jedem dispatch-Vorgang alle Formulare überprüft, womit man Probleme inkonsistenter Daten vorbeugt. Beispiel: Möchte man sich ein Konto mit dem Namen JohnDoe registrieren, dann wird beim Absenden jedes Formulars kontrolliert, ob ein Konto mit dem Namen bereits existiert. Wurde in der Zwischenzeit bereits ein Konto mit dem Namen registriert, wird man über einen Fehler im assoziierten Formular informiert, während die restlichen Eingaben unverändert bleiben.

Die zugehörige Implementierung ist minimalistisch gehalten, da die Details von der Umgebung (z.B. Framework, Template-Engine) abhängen. Nachfolgend die Implementierung für ein Formular:

abstract class Form { private $next; abstract protected function accept ($transfer); abstract protected function process ($transfer); public function attach (Form $form) { $form->next = $this->next; $this->next = $form; return $this; } public function dispatch ($transfer) { if($this->accept($transfer)) { return ($this->process($transfer) ? $this->dispatch($transfer) : false); } if($this->next != null) { return $this->next->dispatch($transfer); } return true; } }

Eine Implementierung des Transfer-Objekts werde ich nicht anfertigen, da man hier eine große Auswahl hat. Z.B. könnte man $_SESSION oder das Transfer-Objekt von Biphrost benutzen. Wesentlich ist jedoch, dass man Daten im Transfer-Objekt hinterlegen und lesen kann. Ob diese nur die Daten für das mehrseitige Formular enthält oder auch Anweisungen für die Applikationslogik (Welches Template soll angezeigt werden? Welche Fehler traten auf?) hängt von der jeweiligen Implementierung ab. Nachfolgend noch ein beispielhafter Probelauf:

// $a, $b und $c sind Formulare (s.o.) $comp = $a->attach($b->attach($c)); if(!$comp->dispatch($transfer)) { $transfer->tpl->errors = $transfer->errors; $transfer->tpl->render(); } else { $tpl = new Template('success.phtml'); writeToDatabase($transfer->form); $tpl->render(); }

Mehr als zwei Klassen (und etwaige Unterklassen) benötigt man also nicht um elegante mehrseitige Formulare zu erstellen. Diese Lösung hat einige bedeutende Eigenschaften:

  • Formulare lassen sich wiederverwenden.
  • Die Lösung lässt sich erweitern, sodass man sich Formularkompositionen zusammenklicken kann.
  • Die Formularlogik wird gekapselt.
  • Formulare und Kompositionen lassen sich leichter ersetzen.
  • Es macht mehr Spaß als alles statisch zu erstellen.

Und je nach Implementierung lassen sich dieser Lösung weitere Vorteile abgewinnen. Für Anregungen stehe ich gerne zur Verfügung.

Kommentare


Kommentiere!

Your Name:


Your Email:


Your URL:


Spam Prevention:
Enter the text above into the box below.
If you are unable to read it, refresh the page.


Your Comment: