Formulare
n2n bietet eine umfassende Api, um Formulare schnell und effizient zusammenzustellen.
Dispatchable
Klassen, die das Interface n2n\dispatch\Dispatchable
implementieren, bilden das Model eines Formulars.
<?php namespace atusch\form; use n2n\dispatch\Dispatchable; class ExampleForm implements Dispatchable { protected $foo = 'default value'; public $bar; public function getFoo() { return $this->foo; } public function setFoo($foo) { $this->foo = $foo; } private function _validation() {} }
Eigenschaften, die protected
oder public
sind, werden automatisch als "managed" erkannt, was dir ermöglicht, Formular-Felder an sie zu binden. Dazu erstellen wir in einem beliebigen Controller eine neue Instanz von ExampleForm
und übergeben sie der View atusch\view\form.html
:
class ExampleController extends ControllerAdapter { public function index() { $exampleForm = new ExampleForm(); $this->forward('view\form.html', array('exampleForm' => $exampleForm)); } }
Über den n2n\dispatch\ui\FormHtmlBuilder
(in Html-Views zugreifbar über $formHtml
) kannst du in deiner View nun dein Formular zusammenstellen:
<?php use atusch\form\ExampleForm; $exampleForm = $view->getParam('exampleForm'); $view->assert($exampleForm instanceof ExampleForm); ?> <?php $formHtml->open($exampleForm) ?> <div> <?php $formHtml->label('foo')?> <div><?php $formHtml->input('foo') ?></div> </div> <div> <?php $formHtml->label('bar')?> <div><?php $formHtml->input('bar') ?></div> </div> <input type="submit" /> <?php $formHtml->close() ?>
Mit $formHtml->open()
eröffnest du das Formular und generierst den <form>
-Tag. Als ersten Parameter wird das Gerüst erwartet, was in diesem Beispiel unser ExampleForm
ist. Zwischen $formHtml->open()
und $formHtml->close()
kannst du nun Form-Elemente, wie Labels ($formHtml->label()
) oder Input-Felder ($formHtml->input()
) erstellen . In dieser View haben wir zum Beispiel mit $formHtml->input('foo')
ein Input-Feld erstellt, das an die Eigenschaft ExampleForm::$foo
gebunden ist. Dies bedeutet, dass der Wert des HTML-Attributs "value" dem aktuellen Wert von ExampleForm::$foo
entspricht und dass der Wert, der vom Benutzer in dieses Input-Feld eingetragen wird, beim Absenden des Formulars auch in diese Eigenschaft geschrieben wird.
Als Letztes musst du den Controller noch anweisen, dass er beim Absenden des Formulars die gesendeten Daten in das ExampleForm
schreiben soll. Dies geschieht über die dispatch()
-Methode:
class ExampleController extends ControllerAdapter { public function index() { $exampleForm = new ExampleForm(); if ($this->dispatch($exampleForm)) { echo 'form submitted; foo: ' . $exampleForm->getFoo() . ' bar: ' . $exampleForm->bar; } $this->forward('view\form.html', array('exampleForm' => $exampleForm)); } }
ControllerAdapter::dispatch()
schreibt die gesendeten Formular-Daten auf die Eigenschaften des Form Models und gibt im Erfolgsfall true
zurück. Es wird false
zurückgegeben, falls das Formular nicht gesendet wurde oder die Validierung fehlschlug.
Validierung
Die gesendeten Formular-Daten lassen sich in der magischen Methode _validation()
validieren. Als Werkzeug kannst du dir die n2n\dispatch\map\bind\BindingDefinition
übergeben lassen, über welche du Validatoren registrieren kannst. Die Eigenschaften des Form Models (ExampleForm
) werden erst geschrieben, wenn die Validierung erfolgreich war.
class ExampleForm implements Dispatchable { protected $foo = 'default value'; public $bar; public function getFoo() { return $this->foo; } public function setFoo($foo) { $this->foo = $foo; } private function _validation(BindingDefinition $bd) { $bd->val(array('foo', 'bar'), new ValNotEmpty()); $bd->val('foo', new ValNumeric('Foo must be numeric.')); $bd->val('bar', new ValEmail(), new ValMaxLength(128, new MessageCode('maxlength_err', array('max' => 128)))); } }
Über BindingDefinition::val()
kannst du Validatoren registrieren. Als ersten Parameter erwartet BindingDefinition::val()
den Namen der Eigenschaft oder die Namen von mehreren Eigenschaften als array
. Als weitere Parameter kannst du eine beliebige Anzahl von Validatoren übergeben, die auf jene Eigenschaften angewendet werden sollen.
Möchtest du auf Text-Bausteine zurückgreifen, kannst du dies über n2n\l10n\MessageCode
tun. Gehen wir davon aus, dass dieses Formular in einem Modul mit Namespace atusch
definiert wurde, wird dieser Baustein in atusch\lang\{locale_id}.ini
gesucht. Weitere Informationen erhältst du im Kapitel Internationalisierung / Lokalisierung.
In der View kannst du Fehlermeldungen über $formHtml->messageList()
oder $formHtml->message()
ausgeben:
<?php $formHtml->open($exampleForm) ?> <?php $formHtml->messageList() ?> <div> <?php $formHtml->label('foo')?> <div><?php $formHtml->input('foo') ?></div> </div> <div<?php $view->out($formHtml->meta()->hasErrors('bar') ? ' class="error"' : '') ?>> <?php $formHtml->label('bar', 'Custom Label')?> <div> <?php $formHtml->input('bar') ?> <span><?php $formHtml->message('bar') ?></span> </div> </div> <input type="submit"> <?php $formHtml->close() ?>
Nutze $formHtml->meta()->hasErrors()
, um herauszufinden, ob die Validierung für bestimmte Eigenschaften fehlgeschlagen ist.
Eigenschafts-Validatoren
Eigenschafts-Validatoren können über BindingDefinition::val()
auf Eigenschaften angewendet werden. Sie alle stellen eine Standard-Fehlermeldung zur Verfügung, falls vom Entwickler keine definiert wird. Eigenschafts-Validatoren ignorieren null
-Werte. Möchtest du zum Beispiel eine obligatorische Eigenschaft $email
validieren, musst du die Validatoren ValNotEmpty
und ValEmail
verwenden.
Folgende Eigenschafts-Validatoren stellt das n2n bereits zur Verfügung (Thomas, phpdoc):
n2n\dispatch\val\impl\ValIsset
n2n\dispatch\val\impl\ValNotEmpty
n2n\dispatch\val\impl\ValMaxLength
n2n\dispatch\val\impl\ValNumeric
n2n\dispatch\val\impl\ValEmail
n2n\dispatch\val\impl\ValUrl
n2n\dispatch\val\impl\ValEnum
n2n\dispatch\val\impl\ValFileExtensions
n2n\dispatch\val\impl\ValImageFile
n2n\dispatch\val\impl\ValImageResourceMemory
n2n\dispatch\val\impl\ValReflectionClass
n2n\dispatch\val\impl\ValArrayKeys
n2n\dispatch\val\impl\ValArraySize
Schlägt die Validierung für eine Eigenschaft fehl, werden für diese Eigenschaft alle weitere Validatoren übersprungen.
Closure Validatoren
Über BindingDefinition::closure()
kannst du magische Closures definieren. Du kannst dir die gewünschten Eigenschaften als Parameter übergeben lassen, dafür müssen die Parameter-Namen einfach den Namen der jeweiligen Eigenschaften entsprechen. Gefundene Fehler kannst du über die n2n\dispatch\map\bind\BindingErrors
registrieren.
class ExampleForm implements Dispatchable { public $password; public $passwordConfirmation; private function _validation(BindingDefinition $bd) { $bd->val('password', new ValNotEmpty()); $bd->closure(function ($password, $passwordConfirmation, BindingErrors $be) { if ($password != $passwordConfirmation) { $be->addError('passwordConfirmation', 'Passwords must be equal.'); } }); } }
Sobald für eine Eigenschaft ein Fehler registriert ist, werden alle Eigenschafts- und Closure-Validatoren, die diese Eigenschaft einschliessen, übersprungen. Dies hat in diesem Beispiel auch zur Folge, dass wenn der ValNotEmpty
-Validator für password
fehlschlägt, der nachfolgende Closure-Validator ignoriert wird.
Validieren ohne Validatoren
Die gesendeten Formular-Daten sind zum Zeitpunkt der Validierung noch nicht geschrieben. Auf diese Daten kannst du aber über das n2n\dispatch\map\MappingResult
zugreifen. Wenn du dir dann auch noch die BindingErrors
übergeben lässt, kannst du die Validierung direkt in _validation()
vornehmen.
class ExampleForm implements Dispatchable { public $password; public $passwordConfirmation; private function _validation(BindingDefinition $bd, MappingResult $mr, BindingErrors $be) { $bd->val('password', new ValNotEmpty()); if ($mr->password != $mr->passwordConfirmation) { $be->addError('passwordConfirmation', 'Passwords must be equal.'); } } }
Dispatch-Methoden
Du kannst Submit-Buttons generieren, die an eine Methode gebunden sind (z. B. über $formHtml->inputSubmit()
oder $formHtml->buttonSubmit()
). Dadurch wird die entsprechende Methode ausgeführt, wenn das Formular abgesendet wurde. Dies geschieht aber nur, falls die Validierung erfolgreich war und erst nachdem die Eigenschaften des Form Models geschrieben wurden.
<?php $formHtml->open($exampleForm) ?> <?php $formHtml->messageList() ?> ... <div> <?php $formHtml->inputSubmit('method1', 'Invoke method1()') ?> <?php $formHtml->buttonSubmit('method2', 'Invoke method2()') ?> </div> <?php $formHtml->close() ?>
Als Dispatch-Methoden können alle public
-Methoden verwendet werden. Im folgenden Beispiel sind dies method1()
und method2()
:
class ExampleForm implements Dispatchable { public $foo; public $bar; private function _validation(BindingDefinition $bd) { $bd->val(array('foo', 'bar'), new ValNotEmpty()); } public function method1() { // code } public function method2(ExampleDao $exampleDao) { $article = $exampleDao->createArtilce($this->foo, $this-bar); return $article->getId(); } }
Dispatch-Methoden sind magisch. In diesem Beispiel nutzen wir method2()
, um über ein fiktives ExampleDao
einen fiktiven Artikel mit den gesendeten Werten von ExampleForm::$foo
und ExampleForm::$bar
zu erstellen. Zurückgegeben wird die Id des Artikels.
Für jeden Submit-Button benötigst du im Controller einen entsprechenden Aufruf von ControllerAdapter::dispatch()
:
class ExampleController extends ControllerAdapter { public function index() { $exampleForm = new ExampleForm(); if ($this->dispatch($exampleForm, 'method1')) { $this->redirectToController(array('thx1')); return; } if (false !== ($articleId = $this->dispatch($exampleForm, 'method2'))) { $this->redirectToController(array('thx2', $articleId)); return; } $this->forward('view\form.html', array('exampleForm' => $exampleForm)); } public function doThx1() { echo 'thank you for invoking method1()'; } public function doThx2($articleId) { echo 'thank you for invoking method2() an creating article ' . $articleId; } }
ControllerAdapter::dispatch()
gibt im Erfolgsfall true
oder falls die Dispatch-Methode einen Rückgabewert generiert, diesen Rückgabewert zurück. In diesem Beispiel wird nun je nachdem, welcher Submit-Button geklickt wurde, ExampleForm::method1()
oder ExampleForm::method2()
ausgeführt und im Erfolgsfall auf eine entsprechende Danke-Seite weitergeleitet.
Mapping
Die magische Methode _mapping()
kann für jedes Form Model (Dispatchable
) implementiert werden und wird aufgerufen, bevor ein n2n\dispatch\map\MappingResult
erstellt wird. Dies geschieht bevor gesendete Formular-Daten verarbeitet werden, aber auch wenn ein Formular nur angezeigt wird. Ein MappingResult
repräsentiert den aktuellen Zustand eines Form Models. Es enthält zum Beispiel die aktuellen Werte und Fehler aller Eigenschaften. Weitere Informationen erhältst du über die n2n\dispatch\map\MappingDefinition
.
Labels
Du kannst über das MappingResult
die Labels der einzelnen Eigenschaften überschreiben. Diese Labels werden sowohl in der View (z. B. als Standard-Label für $formHtml->label()
) als auch für die automatisch generierten Fehlermeldungen der Validatoren genutzt. Auf diese Weise kannst du die Labels der Eigenschaften an einem zentralen Ort definieren und bleibst auch davor verschont, eigene Fehlermeldungen schreiben zu müssen.
class ExampleForm implements Dispatchable { public $name; public $email; private function _mapping(MappingResult $mr, DynamicTextCollection $dtc) { $mr->setLabel('name', $dtc->translate('name_label')); $mr->setLabel('email', 'E-Mail'); } private function _validation(BindingDefinition $bd) { $bd->val('name', new ValNotEmpty()); $bd->val('email', new ValNotEmpty(), new ValEmail()); } public function register() { // code } }
Die Labels in diesem Beispiel werden für die Standard-Fehlermeldungen von ValNotEmpty
und ValEmail
sowie für Labels, die du in der View mit <?php $formHtml->label('name') ?>
oder <?php $formHtml->label('email') ?>
generierst, verwendet.
Werte manipulieren
Wenn du dir das MappingResult
übergeben lässt, ist es zu diesem Zeitpunkt noch leer. Die Werte werden erst später ermittelt, indem gesendete Formular-Daten analysiert oder die Eigenschaften des Form Models gelesen werden. Ob Formular-Daten gesendet wurden, kannst du über MappingDefinition::isDispatched()
ermitteln. Auf diese Formular-Daten kannst du mit MappingDefinition::getDispatchedValue()
zugreifen.
Werte, die du auf das MappingResult
schreibst, bleiben allerdings unverändert. Damit kannst du zum Beispiel gesendete Formular-Daten manipulieren, bevor diese validiert und auf die Eigenschaften geschrieben werden:
class ExampleForm implements Dispatchable { public $url; private function _mapping(MappingResult $mr, MappingDefinition $md) { if ($md->isDispatched()) { $url = $md->getDispatchedValue('url'); if (!preg_match('#^([^:]+):\S+#', $url)) { $mr->url = 'http://' . $url; } } } private function _validation(BindingDefinition $bd) { $bd->val('url', new ValUrl(array('http', 'https'))); } }
Gibt der Benutzer in diesem Beispiel eine URL ohne Schema ein, ergänzen wir diesen Wert mit "http://". "www.example.com", würde also in "http://www.example.com" umgewandelt.
Eigenschaften ignorieren
Mit MappingDefinition::ignore()
erreichst du, dass gesendete Formular-Daten für einzelne Eigenschaften ignoriert werden. Das heisst, dass diese Eigenschaften nicht verändert und alle Validatoren ignoriert werden, die sich auf diese Eigenschaften beziehen.
class ExampleForm implements Dispatchable { private static function _annos(AnnoInit $ai) { $ai->m('subscribe', new AnnoDispMethod()); $ai->m('unsubscribe', new AnnoDispMethod()); } public $email; public $name; public $authCode; private function _mapping(MappingDefinition $md) { if ($md->getMethodName() == 'subscribe') { $md->ignore('authCode'); } } private function _validation(BindingDefinition $bd) { $bd->val('email', new ValNotEmpty(), new ValEmail()); $bd->val('name', new ValNotEmpty()); $bd->val('authCode', new ValNotEmpty()); $bd->closure(function ($authCode) { // validate authCode }); } public function subscribe() { // code } public function unsubscribe() { // code } }
In diesem Beispiel wird die Eigenschaft $authCode
nur beim Abmelden benötigt. Wird subscribe()
ausgeführt, erreichen wir mit $md->ignore('authCode')
, dass diese Eigenschaft unverändert bleibt und beide Validatoren, die sich auf diese Eigenschaft beziehen (ValNotEmpty
- und Closure
-Validator) ignoriert werden.