Property Types
In forms you can also work with other types, such as
arrayorDateTime, and even nest forms into each other. To do this you have to mark the corresponding properties with annotations or a suitable setter method.
Array
To include properties of type array with scalar fields in forms, you can annotate them with n2n\dispatch\annotations\AnnoDispScalarArray. If you prefer to work with an ArrayObject, you have to determine this via the first parameter of AnnoDispScalarArray. Alternatively, you can declare the visibility of the property as protected and add a suitable setter method which expects a parameter of type array or ArrayObject.
class ExampleForm implements Dispatchable {
private static function _annos(AnnoInit $ai) {
$ai->p('foos', new AnnoDispScalarArray());
}
public $foos;
public array $bars;
public \ArrayObject $bazes;
public function getBazOptions() {
return array('item1' => 'Item 1', 'item2' => 'Item 2');
}
private function _validation(BindingDefinition $bd) {
$bd->val('foos', new ValArraySize(1, null, 4));
$bd->val('bazes', new ValArrayValues(array_keys($this->getBazOptions())));
}
}
In this example we also added a method getBazOptions(), which returns all values that are allowed in the array fields of $bazes. We can use these both in the view and when validating. The following validators are suitable for array properties:
n2n\dispatch\map\val\impl\ValArraySizen2n\dispatch\map\val\impl\ValArrayKeysn2n\dispatch\map\val\impl\ValArrayValues
In the view you can now bind form elements to array fields of these properties. The following example shows you how:
<?php $formHtml->open($exampleForm) ?>
<?php $formHtml->messageList() ?>
<div>
<?php $formHtml->label('foos')?>
<ul>
<li><?php $formHtml->input('foos[key1]') ?></li>
<li><?php $formHtml->input('foos[key2]') ?></li>
</ul>
</div>
<div>
<?php $formHtml->label('bars') ?>
<ul>
<li><?php $formHtml->input('bars[]') ?></li>
<li><?php $formHtml->input('bars[]') ?></li>
</ul>
</div>
<div>
<?php $formHtml->label('bazes') ?>
<ul>
<?php foreach ($exampleForm->getBazOptions() as $value => $label): ?>
<li><?php $formHtml->inputCheckbox('bazes[]', $value, null, $label) ?></li>
<?php endforeach ?>
</ul>
</div>
<input type="submit" />
<?php $formHtml->close() ?>
You can also have the array index determined automatically by leaving the square brackets empty. The first input element generated with $formHtml->input('bars[]') therefore refers to the array field bars[0] and the second to bars[1]. If you leave the square brackets empty with $formHtml->inputCheckbox(), however, the checkbox is checked as soon as $value occurs in any array field of the current value. $formHtml->inputRadio() does not allow empty square brackets.
Attention
Make sure that you validate array properties completely. Through simple manipulations the user could for example add further array fields (e.g.
foos[key3]).
Initialized Array Properties
$formHtml->meta()->arrayProps() analyzes the current value of the respective property and executes the passed closure for every array field.
<ul>
<?php $formHtml->meta()->arrayProps('foos', function () use ($formHtml, $html) { ?>
<li><?php $formHtml->input() ?></li>
<?php }) ?>
<li><?php $formHtml->input('foos[]') ?></li>
</ul>
In this example we create an input element for every array field of the current value of $foos. At the end of the list we append an additional input element with which the list can be extended.
If you now initialize ExampleForm::$foos for example with the array array('something', 'somekey' => 'something else'), three input fields are generated in the example above.
Attention
Since the new value is only written to
ExampleForm::$foosafter a successful validation, the current value that is to be displayed in the view can differ from the value of the property. If for example the user fills in all three input elements for$foosand the validation fails, the current value now consists of an array with three fields, whileExampleForm::$foosstill contains the original value. Therefore you cannot simply iterate over the value ofExampleForm::$foos, but have to fall back on$this->meta()->arrayProps(). Alternatively you can also access the current value via$this->meta()->getValue().
DateTime
To mark a property as a DateTime type, you can annotate it with n2n\dispatch\annotations\AnnoDispDateTime. Alternatively, you can declare the visibility of the property as protected and add a suitable setter method which expects a parameter of type DateTime.
class ExampleForm implements Dispatchable {
private static function _annos(AnnoInit $ai) {
$ai->p('foo', new AnnoDispDateTime());
}
public $foo;
public \DateTime $bar;
private function _validation(BindingDefinition $bd) {
$bd->val('bar', new ValNotEmpty());
}
}
Via $formHtml->input() you can now create form elements that are bound to DateTime properties:
<?php $formHtml->open($exampleForm) ?>
<?php $formHtml->messageList() ?>
<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() ?>
If you want to use properties that contain DateTime arrays in forms, you can mark them with the annotation n2n\dispatch\annotations\AnnoDispDateTimeArray.
Formats
The default format for date and time in input elements is determined by the "L10n configuration". Via annotations you can also override these formats:
$ai->p('foo', new AnnoDispDateTime(DateTimeFormat::STYLE_LONG, DateTimeFormat::STYLE_MEDIUM));
$ai->p('bar', new AnnoDispDateTime(DateTimeFormat::STYLE_MEDIUM, DateTimeFormat::STYLE_NONE));
$ai->p('baz', new AnnoDispDateTime(), new AnnoIcuFormat('yyyy-mm-dd hh:mm:ss'));
You can learn more about the styles in the chapter "Internationalization / Localization]". Via n2n\dispatch\annotation\AnnoIcuFormat you can even define an Icu pattern.
Suitable Validators
n2n\dispatch\map\val\impl\ValDateTime
File
To mark a property as a n2n\io\fs\File type, you can annotate it with n2n\dispatch\annotations\AnnoDispFile. Alternatively, you can declare the visibility of the property as protected and add a suitable setter method which expects a parameter of type n2n\io\fs\File.
class ExampleForm implements Dispatchable {
private static function _annos(AnnoInit $ai) {
$ai->p('foo', new AnnoDispFile());
}
public $foo;
public File $bar;
private function _validation(BindingDefinition $bd) {
$bd->val('bar', new ValNotEmpty());
}
}
Use $formHtml->inputFileWithLabel() in the view to create the corresponding file input elements together with a label.
<div>
<?php $formHtml->label('foo')?>
<div><?php $formHtml->inputFileWithLabel('foo') ?></div>
</div>
The label displays the current value if the property $foo has already been initialized with a File or if a file was uploaded during the last submit, but the validation failed. In the second case the label also ensures that the file does not have to be uploaded again for the next submit. Furthermore the label generates a link with which the user can delete the current value / the current file. This has the effect that on a successful submit null is written to the property. Via $formHtml->inputFile() and $formHtml->inputFileLabel() these two elements can also be generated separately.
If you want to use properties that contain File arrays in forms, you can mark them with the annotation n2n\dispatch\annotations\AnnoDispFileArray.
Suitable Validators
n2n\dispatch\map\val\impl\ValImageFilen2n\dispatch\map\val\impl\ValFileExtensionsn2n\dispatch\map\val\impl\ValImageResourceMemory
Object
To mark a property as an object type, you can annotate it with n2n\dispatch\annotations\AnnoDispObject. Alternatively, you can declare the visibility of the property as protected and add a suitable setter method which expects a parameter of a type that implements n2n\dispatch\Dispatchable. This way form models can be nested into each other.
class ExampleForm implements Dispatchable {
private static function _annos(AnnoInit $ai) {
$ai->p('shippingAddressForm', new AnnoDispObject(function () { return new AddressForm(); }));
}
public AddressForm $addressForm;
public $shippingAddressForm;
public function __construct() {
$this->addressForm = new AddressForm();
}
private function _validation(BindingDefinition $bd) { }
}
In this example we use the Dispatchable AddressForm for the billing as well as the shipping address. This way we don't have to record the properties of the addresses twice. The billing address is mandatory and must always be present, which is why we initialize this property ($addressForm) in the constructor. Since the shipping address is voluntary, we do not initialize this property ($shippingAddressForm). An AddressForm for this property should only be created when it is needed.
For optional object properties you have to define a closure via the annotation AnnoDispObject which creates and returns the corresponding value for this property. This closure is magic and can additionally pass you the current form model as a parameter. In the example above this would be possible with the closure signature function (ExampleForm $exampleForm) { }.
AddressForm could look like this:
class AddressForm implements Dispatchable {
public $name;
public $street;
public $zip;
public $location;
private function _validation(BindingDefinition $bd) {
$bd->val(array('name', 'street', 'zip', 'location'), new ValNotEmpty());
}
}
In the view you can now bind form elements to these properties in the usual way:
<?php $formHtml->open($exampleForm) ?>
<?php $formHtml->messageList() ?>
<div>
<div>
<?php $formHtml->label('addressForm.name')?>
<div><?php $formHtml->input('addressForm.name') ?></div>
</div>
// other addressForm properties
</div>
<div>
<?php $formHtml->optionalObjectCheckbox('shippingAddressForm', null, 'Shipping Address') ?>
<div>
<?php $formHtml->label('shippingAddressForm.name') ?>
<div><?php $formHtml->input('shippingAddressForm.name') ?></div>
</div>
// other deliveryAddressForm properties
</div>
<input type="submit" />
<?php $formHtml->close() ?>
Object Arrays
The annotation n2n\dispatch\annotations\AddressForm also allows you to use properties with object arrays in forms:
class ExampleForm implements Dispatchable {
private static function _annos(AnnoInit $ai) {
$ai->p('addressForms', new AnnoDispObjectArray(function () { return new AddressForm(); }));
}
private $addressForms;
public function __construct() {
$this->addressForms = array();
}
public function getAddressForms() {
return $this->addressForms;
}
public function setAddressForms(array $addressForms) {
$this->addressForms = $addressForms;
}
private function _validation(BindingDefinition $bd) { }
}
If the array should be able to have a dynamic number of elements, you have to define a closure via AnnoDispObjectArray which can create the values for array fields. This closure is magic and can additionally pass you the current form model and the array index as parameters. In the example above this would be possible with the closure signature function (ExampleForm $exampleForm, $key) { }.
Also here use $formHtml->meta()->arrayProps() to iterate over the current value of $addressForms and create the form elements:
<?php $formHtml->open($exampleForm) ?>
<?php $formHtml->messageList() ?>
<?php $formHtml->meta()->arrayProps('addressForms', function () use ($formHtml) { ?>
<div>
<?php $formHtml->optionalObjectCheckbox() ?>
<div>
<?php $formHtml->label('name') ?>
<div><?php $formHtml->input('name') ?></div>
</div>
// other addressForm properties
</div>
<?php }, 5, 5) ?>
<input type="submit" />
<?php $formHtml->close() ?>
In this example we limit the maximum number of AddressForm fields to 5 via the last two parameters of arrayProps().
Custom Properties
For every form model a DispatchModel is created, in which every property is represented by a n2n\dispatch\property\ManagedProperty. For every property type there is an implementation of this interface.
n2n already provides the following property types:
n2n\dispatch\property\impl\ScalarPropertyn2n\dispatch\property\impl\DateTimePropertyn2n\dispatch\property\impl\FilePropertyn2n\dispatch\property\impl\ObjectProperty.
You can extend n2n with your own property types. To do this, create a class for your property type that implements n2n\dispatch\property\ManagedProperty or directly extends n2n\dispatch\property\ManagedPropertyAdapter. If you want your property type to be compatible with $formHtml->input(), the class must additionally implement n2n\dispatch\property\SimpleProperty. Otherwise you can create your own HTML builder and instantiate it directly in the view.
Furthermore you need a class that implements n2n\dispatch\property\ManagedPropertyProvider. You have to register this in the app.ini under the group [http]:
[http]
dispatch.property_providers[] = "n2n\atusch\model\CustomPropertyProvider"
Further information can be found in the PHPDoc.