Formulare

Form

n2n bietet eine umfassende Api, um Formulare schnell und effizient zusammenzustellen.

  1. Dispatchable
  2. Validierung
  3. Dispatch-Methoden
  4. Mapping

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.

Wie du eigene Eigenschafts-Validatoren definieren kannst, erfährst du im Kapitel Eigene Validatoren.

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.

« Lookupable Eigenschafts-Typen »

Kommentare

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

Fragen