Skip to main content

Forms

n2n provides a comprehensive API to assemble forms quickly and efficiently.

Dispatchable​

Classes that implement the interface n2n\dispatch\Dispatchable form the model of a form.

<?php
namespace atusch\form;

use n2n\dispatch\Dispatchable;

class ExampleForm implements Dispatchable {
public $foo = 'default value';
public $bar;

private function _validation() {}
}

Properties that are protected or public are automatically recognized as "managed", which allows you to bind form fields to them. To do this, we create a new instance of ExampleForm in any controller and pass it to the view atusch\view\form.html:

class ExampleController extends ControllerAdapter {
public function index() {
$exampleForm = new ExampleForm();

$this->forward('view\form.html', array('exampleForm' => $exampleForm));
}
}

Via the n2n\dispatch\ui\FormHtmlBuilder (accessible in HTML views via $formHtml) you can now assemble your form in your view:

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

With $formHtml->open() you open the form and generate the <form> tag. The form model is expected as first parameter, which in this example is our ExampleForm. Between $formHtml->open() and $formHtml->close() you can now create form elements such as labels ($formHtml->label()) or input fields ($formHtml->input()). In this view we have for example created an input field with $formHtml->input('foo') that is bound to the property ExampleForm::$foo. This means that the value of the HTML attribute "value" corresponds to the current value of ExampleForm::$foo and that the value that the user enters into this input field is also written into this property when the form is submitted.

As a last step you have to instruct the controller to write the submitted data into the ExampleForm when the form is sent. This is done via the dispatch() method:

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() writes the submitted form data to the properties of the form model and returns true in case of success. It returns false if the form was not submitted or if the validation failed.

Validation​

The submitted form data can be validated in the magic method _validation(). As a tool you can have the n2n\dispatch\map\bind\BindingDefinition passed to you, via which you can register validators. The properties of the form model (ExampleForm) are only written when the validation was successful.

class ExampleForm implements Dispatchable {
public $foo = 'default value';
public $bar;

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))));
}
}

Via BindingDefinition::val() you can register validators. BindingDefinition::val() expects the name of the property or the names of several properties as an array as its first parameter. As further parameters you can pass any number of validators that should be applied to those properties.

If you want to fall back on text blocks, you can do this via n2n\l10n\MessageCode. Assuming this form was defined in a module with the namespace atusch, this block is looked up in atusch\lang\{locale_id}.ini. You can find more information in the chapter Internationalization / Localization.

In the view you can output error messages via $formHtml->messageList() or $formHtml->message():

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

Use $formHtml->meta()->hasErrors() to find out whether the validation failed for certain properties.

Property Validators​

Property validators can be applied to properties via BindingDefinition::val(). They all provide a default error message if none is defined by the developer. Property validators ignore null values. For example, if you want to validate a mandatory property $email, you have to use the validators ValNotEmpty and ValEmail.

The following property validators are already provided by n2n (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

If the validation fails for one property, all further validators are skipped for this property.

Closure Validators​

Via BindingDefinition::closure() you can define magic closures. You can have the desired properties passed to you as parameters; for this the parameter names simply have to correspond to the names of the respective properties. Errors you find can be registered via the n2n\dispatch\map\bind\BindingErrors.

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.');
}
});
}
}

As soon as an error is registered for one property, all property and closure validators that include this property are skipped. In this example this also means that if the ValNotEmpty validator fails for password, the subsequent closure validator is ignored.

Validating Without Validators​

The submitted form data is not yet written at the time of the validation. However, you can access this data via the n2n\dispatch\map\MappingResult. If you also have the BindingErrors passed to you, you can perform the validation directly in _validation().

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 Methods​

You can generate submit buttons that are bound to a method (e.g. via $formHtml->inputSubmit() or $formHtml->buttonSubmit()). As a result, the corresponding method is executed when the form was submitted. But this only happens if the validation was successful and only after the properties of the form model have been written.

<?php $formHtml->open($exampleForm) ?>
<?php $formHtml->messageList() ?>

...
<div>
<?php $formHtml->inputSubmit('method1', 'Invoke method1()') ?>
<?php $formHtml->buttonSubmit('method2', 'Invoke method2()') ?>
</div>
<?php $formHtml->close() ?>

All public methods can be used as dispatch methods. In the following example these are method1() and 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 methods are magic. In this example we use method2() to create a fictional article with the submitted values of ExampleForm::$foo and ExampleForm::$bar via a fictional ExampleDao. The id of the article is returned.

For every submit button you need a corresponding call of ControllerAdapter::dispatch() in the controller:

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() returns true in case of success, or the return value generated by the dispatch method, if it generates one. In this example, depending on which submit button was clicked, ExampleForm::method1() or ExampleForm::method2() is executed and, in case of success, redirected to a corresponding thank-you page.

Mapping​

The magic method _mapping() can be implemented for every form model (Dispatchable) and is called before an n2n\dispatch\map\MappingResult is created. This happens before submitted form data is processed, but also when a form is only displayed. A MappingResult represents the current state of a form model. It contains for example the current values and errors of all properties. You can get more information about the n2n\dispatch\map\MappingDefinition.

Labels​

Via the MappingResult you can overwrite the labels of the individual properties. These labels are used both in the view (e.g. as default label for $formHtml->label()) and for the automatically generated error messages of the validators. This way you can define the labels of the properties in a central place and are spared from having to write your own error messages.

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
}
}

The labels in this example are used for the default error messages of ValNotEmpty and ValEmail as well as for labels that you generate in the view with <?php $formHtml->label('name') ?> or <?php $formHtml->label('email') ?>.

Manipulating Values​

When the MappingResult is passed to you, it is still empty at this point. The values are only determined later by analyzing submitted form data or reading the properties of the form model. You can determine whether form data was submitted via MappingDefinition::isDispatched(). You can access this form data with MappingDefinition::getDispatchedValue().

However, values that you write to the MappingResult remain unchanged. This allows you for example to manipulate submitted form data before it is validated and written to the properties:

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 = 'https://' . $url;
}
}
}

private function _validation(BindingDefinition $bd) {
$bd->val('url', new ValUrl(array('http', 'https')));
}
}

In this example, if the user enters a URL without a scheme, we add "http://" to this value. So "www.example.com" would be converted to "http://www.example.com".

Ignoring Properties​

With MappingDefinition::ignore() you can ensure that submitted form data is ignored for individual properties. This means that these properties are not changed and all validators that relate to these properties are ignored.

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 this example the property $authCode is only needed for unsubscribing. If subscribe() is executed, we use $md->ignore('authCode') to ensure that this property remains unchanged and that both validators that relate to this property (the ValNotEmpty and Closure validators) are ignored.