Eigene Validatoren

Form

In diesem Artikel wird erklärt, wie du eigene Validatoren erstellen kannst. Du solltest hierzu bereits die Artikel Formulare und Eigenschafts-Typen gelesen haben.

  1. Allgemeins
  2. Allgemeiner Validator
  3. Eigenschafts-Validator
  4. Wiederverwendbarkeit

Allgemeins

Um dem Entwickler die Autovervollständigung zu erleichtern, sollten die Namen aller Validatoren-Klassen mit dem Prefix "Val" beginnen.

Eigenschafts-Validatoren ignorieren null-Werte. Ist die Eigenschaft obligatorisch, soll zusätzlich ValNotEmpty oder ValIsset angewendet werden.

Bevor du mit diesem Artikel fortfährst, solltest du ein geringes Wissen über den Typ ManagedProperty haben. Lies hierzu das Kapitel Eigene Eigenschaften.

 

Allgemeiner Validator

Für einen Validator, der allgemein auf ein Formular angewendet werden soll, erstellst du eine Klasse, die n2n\dispatch\map\val\Validator implementiert. Das folgende Beispiel zeigt einen Validator, der prüft, ob die Eigenschaft $foo grösser als $bar ist:

class ValFooBar implements Validator {
    
    public function validate(MappingResult $mappingResult, N2nContext $n2nContext) {
        if ($mappingResult->foo > $mappingResult->bar) return;
            
        $mappingResult->getBindingErrors()->addError('foo', 'Foo must be greater than Bar.');
    }
}

validate() übergibt dir das MappingResult des aktuellen Form Models, über welches du auf die zu validierenden Werte zugreifen kannst. Zudem liefert dir MappingResult::getBindingErrors() die BindingErrors, über welche du allfällige Validierungs-Fehler registrieren kannst.

Allgemeine Validatoren lassen sich über BindingDefinition::valo() registrieren.

    private function _validation(BindingDefinition $bd) {
        $bd->valo(new ValFooBar());
    }

Eigenschafts-Validator

Eigenschafts-Validatoren implementieren n2n\dispatch\map\val\PropertyValidator und können über BindingDefinition::val() auf beliebige Eigenschaften angewendet werden.

Zum Beispiel können wir einen Validator ValEquals erstellen, der überprüfen soll, ob die Werte aller Eigenschaften, auf die er angewendet wird, gleich sind. Um die Entwicklung eines Validators zu vereinfachen, empfiehlt es sich n2n\dispatch\map\val\impl\PropertyValidatorAdapter zu erweitern.

class ValEquals extends PropertyValidatorAdapter {
    
    public function __construct() {
        $this->restrictType(array('n2n\dispatch\property\impl\ScalarProperty'), false);
    }
    
    public function validate(MappingResult $mappingResult, N2nContext $n2nContext) {
        $firstPropertyName = null;
        $firstMapValue = null;
        foreach ($this->getManagedProperties() as $managedProperty) {
            $propertyName = $managedProperty->getName();
            if ($firstPropertyName === null) {
                $firstPropertyName = $propertyName;
                $firstMapValue = $mappingResult->__get($propertyName);
                continue;
            }
            
            if ($firstMapValue === $mappingResult->__get($propertyName)) {
                continue;
            }
            
            $mappingResult->getBindingErrors()->addError($propertyName,
                    $mappingResult->getLabel($propertyName) . ' does not equal '
                    . $mappingResult->getLabel($firstPropertyName));
        }        
    }
}

PropertyValidatorAdapter::restrictType() gibt dir die Möglichkeit deinen Validator auf bestimmte Eigenschafts-Typen einzuschränken. Der erste Parameter erwartet ein array von Namen der kompatiblen Eigenschafts-Typen. Über den zweiten Parameter bestimmst du, ob der Validator auf Array-Eigenschaften angewendet werden darf. In diesem Beispiel erlaubt ValEquals nur Eigenschaften vom Typ  n2n\dispatch\property\impl\ScalarProperty, die keine Arrays sind. Wendet der Entwickler ValEquals auf einen anderen Eigenschafts-Typ an, wird eine entsprechende Exception geworfen.

PropertyValidatorAdapter::getManagedProperties() gibt uns ein Array vonManagedProperty-Objekten zurück, die alle Eigenschaften repräsentieren, auf welche der Validator angewendet werden soll.

Möchten wir nun prüfen, ob die Eigenschaften $foo und $bar identisch sind, müssen wir ValEquals wie folgt anwenden:

    private function _validation(BindingDefinition $bd) {
        $bd->val(array('additionalQuestions', 'mainQuestion'), new ValEquals());
    }

SinglePropertyValidator

Erweitere n2n\dispatch\map\val\SinglePropertyValidator, um Eigenschaften unabhängig voneinander zu validieren. SinglePropertyValidator erweitert PropertyValidatorAdapter und lässt dich die Methode validateProperty($mapValue) implementieren, die für jede Eigenschaft ausgeführt wird und deren Wert als Parameter übergibt. Nutze SinglePropertyValidator::failed() oder SinglePropertyValidator::failedCode(), um Fehler zu registrieren und SinglePropertyValidator::getLabel(), um das Label der aktuell zu validierenden Eigenschaft zu erhalten. failed() erlaubt als Fehlermeldung einen string oder ein Objekt vom Typ n2n\l10n\Message (auch n2n\l10n\MessageCode). Informationen über weitere Hilfs-Methoden findest du in der PHPDoc.

class ValArraySize extends SinglePropertyValidator {
    private $minSize;
    private $maxSize;
    
    public function __construct($minSize, $maxSize) {
        $this->minSize = (int) $minSize;
        $this->maxSize = (int) $maxSize;
        $this->restrictType(null, true);
    }

    protected function validateProperty($mapValue) {
        $size = count($mapValue);
        
        if ($size < $this->minSize) {
            $this->failed('At least ' . $this->minSize . ' of '
                    . $this->getLabel() . ' are required.');
        }
        
        if ($size > $this->maxSize) {
            $this->failed('At least ' . $this->minSize . ' of '    . $this->getLabel()
                    . ' are required.');
        }
    }
}

ValArraySize prüft die Grösse eines Arrays. Mit $this->restrictType(null, true); erreichen wir, dass dieser Validator nur auf Array-Eigenschaften angewendet werden kann.

Dieser Validator könnte zum Beispiel folgendermassen angewendet werden:

    private function _validation(BindingDefinition $bd) {
        $bd->val(array('foos', 'bars'), new ValArraySize(1, 5));
    }

In diesem Beispiel überprüfen wir, ob die zwei Eigenschaften $foos und $bars beide Arrays mit mindestens einem und maximal 5 Feldern als Wert haben.

SimplePropertyValidator

Erweitere n2n\dispatch\map\val\SimplePropertyValidator, wenn es für deinen Validator zu vernachlässigen ist, ob er auf eine Array-Eigenschaft angewendet wird oder nicht.SimplePropertyValidator lässt dich die Methode validateValue() implementieren. Ähnlich wie beim SinglePropertyValidator wird diese Methode für jede Eigenschaft aufgerufen. Bei Array-Eigenschaften wird sie allerdings für jedes Array-Feld separat mit dessen Wert als Parameter aufgerufen. SimplePropertyValidator erweitert SinglePropertyValidator, so sind dieselben Hilfs-Methoden verfügbar.

class ValSuffix extends SimplePropertyValidator {
    private $suffix;
    
    public function __construct($suffix) {
        $this->suffix = $suffix;
        $this->restrictType(array('n2n\dispatch\property\SimpleProperty'));
    }
    
    public function validateValue($mapValue) {
        if ($mapValue === null) return;
        
        $managedProperty = $this->getManagedProperty();
        ArgumentUtils::assertTrue($managedProperty instanceof SimpleProperty);
        $scalarValue = $managedProperty->convertMapValueToScalar($mapValue, $this->getN2nContext());
        
        if (!StringUtils::endsWith($this->suffix, $scalarValue)) {
            $this->failed($this->getLabel() . ' must end with ' . $this->suffix);
        }
    }
}

ValSuffix prüft, ob die Werte aller Eigenschaften, auf die der Validator angewendet wird, den jeweiligen Suffix haben. Ist die Eigenschaft ein Array, so wird überprüft, ob die Werte aller Array-Felder diesen Suffix haben. Wir beschränken diesen Validator auf Eigenschafts-Typen, die n2n\dispatch\property\SimpleProperty implementieren. Somit müssen wir SimpleProperty::convertMapValueToScalar() verwenden, um den Eigenschafts-Wert in einen skalaren Wert umzuwandeln.

In folgendem Beispiel gehen wir davon aus, dass $additionalQuestions eine Array-Eigenschaft ist und $mainQuestion nicht.

    private function _validation(BindingDefinition $bd) {
        $bd->val(array('foo', 'bars'), new ValSuffix('?'));
    }

In diesem Validierungs-Beispiel üperprüfen wir, ob die Eigenschaft $mainQuestion und alle Array-Felder der Eigenschaft $additionalQuestion einen Wert mit Suffix "?" haben.

Wiederverwendbarkeit

Um die Wiederverwendbarkeit von Validatoren zu erhöhen, sollte der Entwickler die Möglichkeit haben, optional eine eigene Fehlermeldung zu übergeben. Sollte er dies nicht tun, muss im Fehlerfall eine Standard-Fehlermeldung verwendet werden. Hierzu solltest du auf einen [link zu text-collection doku]Text-Code[/[link zu text-collection doku] zurückgreifen. Dieser Text-Code kann bei der Ausgabe in einer View, von der [link zu View Text-Collection Info]aktuellen Text-Datei[/link zu View Text-Collection Info] überschrieben werden. Damit dies nicht unabsichtlich passiert, sollte der Text-Code den Namen (mit Namespace) des Validators enthalten.

class ValSuffix extends SimplePropertyValidator {
    const DEFAULT_ERROR_TEXT_CODE = 'atusch.form.ValSuffix';
    
    private $suffix;
    private $errorMessage;
    
    public function __construct($suffix, $errorMessage = null) {
        $this->suffix = $suffix;
        $this->errorMessage = $errorMessage;
        $this->restrictType(array('n2n\dispatch\property\SimpleProperty'));
    }
    
    public function validateValue($mapValue) {
        if ($mapValue === null) return;
        
        ArgumentUtils::assertTrue($managedProperty instanceof SimpleProperty);
        $scalarValue = $managedProperty->convertMapValueToScalar($mapValue, $this->getN2nContext());
        
        if (!StringUtils::endsWith($this->suffix, $scalarValue)) {
            $this->failed($this->errorMessage, self::DEFAULT_ERROR_TEXT_CODE, array('suffix' => $this->suffix), 'atusch');
        }
    }
}

Den Text-Code der Standard-Fehlermeldung sowie allfällige Argumente kannst du SinglePropertyValidator::failed() als zweiten und dritten Parameter übergeben. failed() fügt bei den Argumenten des Text-Codes zusätzlich das Feld "field" hinzu, welches das Label der aktuellen Eigenschaft enthält. Dies geschieht auch für übergebene Objekte von MessageCode. Als vierten Parameter wird der Namespace des Moduls erwartet, in welchem die Text-Datei gesucht werden soll. In der Text-Datei atusch\lang\en.ini könnte zum Beispiel Folgendes stehen:

atusch.form.ValSuffix = "{field} requires suffix '{suffix}'.";
« Eigenschafts-Typen Eigenschaften annotieren »

comments_title

post_login_to_create

questions_title