Eigenschafts-Typen

ORM

Du kannst in Formularen auch mit weiteren Typen, wie zum Beispiel array oder DateTime arbeiten und sogar Formulare ineinander verschachteln. Hierzu musst du die entsprechenden Eigenschaften mit Annotationen oder einer passenden Setter-Methode markieren.

  1. Array
  2. DateTime
  3. File
  4. Objekt
  5. Eigene Eigenschaften

Array

Um in Formularen Eigenschaften vom Typ array mit skalaren Feldern einzubinden, kannst du sie mit n2n\dispatch\annotations\AnnoDispScalarArray annotieren. Arbeitest du lieber mit einem ArrayObject, musst du dies über den ersten Parameter von AnnoDispScalarArray bestimmen. Alternativ kannst du für die Eigenschaft die Sichtbarkeit protected deklarieren und eine entsprechende Setter-Methode hinzufügen, welche einen Parameter vom Typ array oder ArrayObject erwartet.

class ExampleForm implements Dispatchable {
    private static function _annos(AnnoInit $ai) {
        $ai->p('foos', new AnnoDispScalarArray());
    }
    
    public $foos;
    protected $bars;
    protected $bazes;
    
    public function setBars(array $bars) {
        $this->bars = $bars;
    }
    
    public function getBars() {
        return $this->bars;
    }
    
    public function setBazes(\ArrayObject $bazes) {
        $this->bazes = $bazes;
    }
    
    public function getBazes() {
        return $this->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 diesem Beispiel haben wir noch eine Methode getBazOptions() hinzugefügt, die uns alle Werte zurückgibt, die in den Array-Feldern von $bazes erlaubt sind. Diese können wir sowohl in der View als auch beim Validieren nutzen. Für Array-Eigenschaften sind folgende Validatoren geeignet:

  • n2n\dispatch\map\val\impl\ValArraySize
  • n2n\dispatch\map\val\impl\ValArrayKeys
  • n2n\dispatch\map\val\impl\ValArrayValues

In der View kannst du nun Formular-Elemente an Array-Felder dieser Eigenschaften binden. Das folgende Beispiel zeigt dir wie:

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

Du kannst den Array-Index auch automatisch bestimmen lassen, indem du die eckigen Klammern leer lässt. Das erste Input-Element, das mit $formHtml->input('bars[]') generiert wird, bezieht sich also auf das Array-Feld bars[0] und das zweite auf bars[1]. Lässt du aber bei $formHtml->inputCheckbox() die eckigen Klammern leer, ist die Checkbox ausgewählt, sobald $value in einem beliebigen Array-Feld des aktuellen Wertes vorkommt. $formHtml->inputRadio() erlaubt keine leeren eckigen Klammern.

Achte darauf, dass du Array-Eigenschaften vollständig validierst. Der Benutzer könnte durch einfache Manipulationen zum Beispiel weitere Array-Felder (z. B. foos[key3]) hinzufügen.

Initialisierte Array-Eigenschaften

$formHtml->meta()->arrayProps() analysiert den aktuellen Wert der jeweiligen Eigenschaft und führt für jedes Array-Feld die übergebene Closure aus.

        <ul>
            <?php $formHtml->meta()->arrayProps('foos', function () use ($formHtml, $html) { ?>
                <li><?php $formHtml->input() ?></li>
            <?php }) ?>
            <li><?php $formHtml->input('foos[]') ?></li>
        </ul>

In diesem Beispiel erstellen wir für jedes Array-Feld des aktuellen Wertes von $foos ein Input-Element. Am Schluss der Liste hängen wir ein zusätzliches Input-Element an, mit dem die Liste erweitert werden kann.

Initialisierst du ExampleForm::$foos nun zum Beispiel mit einem Array array('something', 'somekey' => 'something else'), werden im oben stehenden Beispiel drei Input-Felder generiert.

Da der neue Wert erst nach erfolgreicher Validierung auf ExampleForm::$foos geschrieben wird, kann sich der aktuelle Wert, der in der View dargestellt werden soll, vom Wert der Eigenschaft unterscheiden. Füllt der Benutzer zum Beispiel alle drei Input-Elemente für $foos aus und die Validierung schlägt fehl, besteht der aktuelle Wert nun aus einem Array mit drei Feldern, während ExampleForm::$foos immer noch den ursprünglichen Wert enthält. Deshalb kannst du nicht einfach über den Wert von ExampleForm::$foos iterieren, sondern musst auf  $this->meta()->arrayProps() zurückgreifen. Alternativ kannst du auch über $this->meta()->getValue() auf den aktuellen Wert zugreifen.

DateTime

Um eine Eigenschaft als DateTime-Type zu markieren, kannst du sie mit n2n\dispatch\annotations\AnnoDispDateTime annotieren. Alternativ kannst du für die Eigenschaft die Sichtbarkeit protected deklarieren und eine entsprechende Setter-Methode hinzufügen, welche einen Parameter vom Typ DateTime erwartet.

class ExampleForm implements Dispatchable {
    private static function _annos(AnnoInit $ai) {
        $ai->p('foo', new AnnoDispDateTime());
    }
    
    public $foo;
    protected $bar;
        
    public function getBar() {
        return $this->bar;
    }
    
    public function setBar(\DateTime $bar) {
        $this->bar = $bar;
    }
    
    private function _validation(BindingDefinition $bd) {
        $bd->val('bar', new ValNotEmpty());
    }
}

setBar(\DateTime $bar) erlaubt keine null-Werte. Ist die Eigenschaft optional, muss auch die Setter-Methode angepasst werden: setBar(\DateTime $bar = null)

Über $formHtml->input() kannst du nun Form-Elemente erstellen, die an DateTime-Eigenschaften gebunden sind:

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

Möchtest du Eigenschaften, die DateTime-Arrays enthalten, in Formularen nutzen, kannst du sie mit der Annotation n2n\dispatch\annotations\AnnoDispDateTimeArray markieren.

Formate

Das Standard-Format für Datum und Zeit in Input-Elementen wird von der L10n-Konfiguration bestimmt. Über Annotationen kannst du diese Formate aber auch überschreiben:

        $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'));

Mehr über die Styles erfährst du im Kapitel Internationalisierung/Lokalisierung. Über n2n\dispatch\annotation\AnnoIcuFormat kannst du sogar ein Icu-Pattern definieren.

Geeignete Validatoren

  • n2n\dispatch\map\val\impl\ValDateTime

File

Um eine Eigenschaft als n2n\io\fs\File-Type zu markieren, kannst du sie mit n2n\dispatch\annotations\AnnoDispFile annotieren. Alternativ kannst du für die Eigenschaft die Sichtbarkeit protected deklarieren und eine entsprechende Setter-Methode hinzufügen, welche einen Parameter vom Typ n2n\io\fs\File erwartet.

class ExampleForm implements Dispatchable {
    private static function _annos(AnnoInit $ai) {
        $ai->p('foo', new AnnoDispFile());
    }
    
    public $foo;
    protected $bar;
    
    public function getBar() {
        return $this->bar;
    }
    
    public function setBar(File $bar) {
        $this->bar = $bar;
    }
    
    private function _validation(BindingDefinition $bd) {
        $bd->val('bar', new ValNotEmpty());
    }
}

setBar(\File $bar) erlaubt keine null-Werte. Ist die Eigenschaft optional, muss auch die Setter-Methode angepasst werden: setBar(\File $bar = null)

Nutze in der View $formHtml->inputFileWithLabel(), um in der View entsprechende File-Input-Elemente zusammen mit einem Label zu erstellen.

    <div>
        <?php $formHtml->label('foo')?>
        <div><?php $formHtml->inputFileWithLabel('foo') ?></div>
    </div>

Das Label zeigt den aktuellen Wert an, falls die Eigenschaft $foo bereits mit einem File initialisiert wurde oder beim letzten Submit eine Datei hochgeladen wurde, die Validierung aber fehlschlug. Das Label sorgt im zweiten Fall auch dafür, dass die Datei für den nächsten Submit nicht erneut hochgeladen werden muss. Ausserdem generiert das Label einen Link, mit dem der Benutzer den aktuellen Wert / die aktuelle Datei löschen kann. Dies führt dazu, dass bei erfolgreichem Submit null auf die Eigenschaft geschrieben wird. Über $formHtml->inputFile() und $formHtml->inputFileLabel() lassen sich diese zwei Elemente auch getrennt voneinander generieren.

File-Objekte kannst du auch direkt in Entities verwenden.

Möchtest du Eigenschaften, die File-Arrays enthalten, in Formularen nutzen, kannst du sie mit der Annotation n2n\dispatch\annotations\AnnoDispFileArray markieren.

Geeignete Validatoren

  • n2n\dispatch\map\val\impl\ValImageFile
  • n2n\dispatch\map\val\impl\ValFileExtensions
  • n2n\dispatch\map\val\impl\ValImageResourceMemory

Objekt

Um eine Eigenschaft als Objekt-Typ zu markieren, kannst du sie mit n2n\dispatch\annotations\AnnoDispObject annotieren. Alternativ kannst du für die Eigenschaft die Sichtbarkeit protected deklarieren und eine entsprechende Setter-Methode hinzufügen, die einen Parameter eines Typs erwartet, der n2n\dispatch\Dispatchable implementiert. So lassen sich Form Models ineinander verschachteln.

class ExampleForm implements Dispatchable {
    private static function _annos(AnnoInit $ai) {
        $ai->p('shippingAddressForm', new AnnoDispObject(function () { return new AddressForm(); }));
    }
    
    protected $addressForm;
    public $shippingAddressForm;
    
    public function __construct() {
        $this->addressForm = new AddressForm();
    }
    
    public function getAddressForm() {
        return $this->addressForm;
    }
    
    public function setAddressForm(AddressForm $addressForm) {
        $this->addressForm = $addressForm;
    }
    
    private function _validation(BindingDefinition $bd) { }
}

In diesem Beispiel nutzen wir das Dispatchable AddressForm für die Rechnungs- sowie die Lieferadresse. So müssen wir die Eigenschaften der Adressen nicht doppelt erfassen. Die Rechnungsadresse ist obligatorisch und muss immer vorhanden sein, weshalb wir diese Eigenschaft ($addressForm) im Konstruktor initialisieren. Da die Lieferadresse freiwillig ist, initialisieren wir diese Eigenschaft ($shippingAddressForm) nicht. Es soll erst ein AddressForm für diese Eigenschaft erstellt werden, wenn dieses benötigt wird.

Für optionale Objekt-Eigenschaften musst du über die Annotation AnnoDispObject eine Closure definieren, die den entsprechenden Wert für diese Eigenschaft erstellt und zurückgibt. Diese Closure ist magisch und kann dir zusätzlich das aktuelle Form Model als Parameter übergeben. Im oben stehenden Beispiel wäre dies mit der Closure-Signatur function (ExampleForm $exampleForm) { } möglich.

AddressForm könnte folgendermassen aussehen:

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 der View kannst du nun auf gewohnte Art und Weise Form-Elemente an diese Eigenschaften binden:

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

Objekt-Arrays

Die Annotation n2n\dispatch\annotations\AddressForm ermöglicht es dir auch, Eigenschaften mit Objekt-Arrays in Formularen nutzen:

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

Soll das Array eine dynamische Anzahl Elemente haben können, musst du über AnnoDispObjectArray eine Closure definieren, die die Werte für Array-Felder erstellen kann. Diese Closure ist magisch und kann dir zusätzlich das aktuelle Form Model und den Array-Index als Parameter übergeben. Im oben stehenden Beispiel wäre dies mit der Closure-Signatur function (ExampleForm $exampleForm, $key) { } möglich.

Nutze auch hier $formHtml->meta()->arrayProps(), um über den aktuellen Wert von $addressForms zu iterieren und die Formular-Elemente zu erstellen:

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

Wir beschränken in diesem Beispiel über die zwei letzten Parameter von arrayProps() die maximale Anzahl der AddressForm-Felder auf 5.

Eigene Eigenschaften

Für jedes Form Model wird ein DispatchModel erstellt, in welchem jede Eigenschaft von einem n2n\dispatch\property\ManagedProperty representiert wird. Für jeden Eigenschafts-Typ gibt es eine  Implentierung dieses Interfaces.

Vom n2n werden bereits folgende Eingenschafts-Typen zur Verfügung gestellt:

  • n2n\dispatch\property\impl\ScalarProperty
  • n2n\dispatch\property\impl\DateTimeProperty
  • n2n\dispatch\property\impl\FileProperty
  • n2n\dispatch\property\impl\ObjectProperty.

Du kannst n2n mit eigenen Eigenschafts-Typen erweitern. Hierzu erstellst du für deinen Eigenschafts-Typ eine Klasse, die n2n\dispatch\property\ManagedProperty implementiert oder gleich n2n\dispatch\property\ManagedPropertyAdapter erweitert. Möchtest du, dass dein Eigenschafts-Typ mit $formHtml->input() kompatibel ist, muss die Klasse zusätzlich noch n2n\dispatch\property\SimpleProperty implementieren. Sonst kannst du deinen eigenen HTML-Builder erstellen und diesen direkt in der View instanzieren.

Weiter benötigst du eine Klasse, die n2n\dispatch\property\ManagedPropertyProvider implementiert. Diese musst du in der app.ini unter der Gruppe [http] registrieren:

[http]
dispatch.property_providers[] = "n2n\atusch\model\CustomPropertyProvider"

Weitere Informationen kannst du der PHPDoc entnehmen.

« Formulare Eigene Validatoren »

comments_title

post_login_to_create

questions_title