Skip to main content

Custom Validators

This article explains how you can create your own validators. For this you should already have read the articles Forms and Property Types.

General​

To make auto-completion easier for the developer, the names of all validator classes should start with the prefix "Val".

Property validators ignore null values. If the property is mandatory, ValNotEmpty or ValIsset should additionally be applied.

Before you continue with this article, you should have a basic knowledge of the type ManagedProperty. For this, read the chapter Custom Properties.

General Validator​

For a validator that should be applied to a form in general, you create a class that implements n2n\dispatch\map\val\Validator. The following example shows a validator that checks whether the property $foo is greater than $bar:

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() passes you the MappingResult of the current form model, via which you can access the values to validate. Furthermore MappingResult::getBindingErrors() provides you with the BindingErrors, via which you can register any validation errors.

General validators can be registered via BindingDefinition::valo().

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

Property Validator​

Property validators implement n2n\dispatch\map\val\PropertyValidator and can be applied to any properties via BindingDefinition::val().

For example, we can create a validator ValEquals which should check whether the values of all properties it is applied to are equal. To simplify the development of a validator, it is advisable to extend n2n\dispatch\map\val\impl\PropertyValidatorAdapter.

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() gives you the possibility to restrict your validator to certain property types. The first parameter expects an array of names of the compatible property types. Via the second parameter you determine whether the validator may be applied to array properties. In this example ValEquals only allows properties of type n2n\dispatch\property\impl\ScalarProperty that are not arrays. If the developer applies ValEquals to another property type, a corresponding Exception is thrown.

PropertyValidatorAdapter::getManagedProperties() returns an array of ManagedProperty objects, which represent all properties to which the validator should be applied.

If we now want to check whether the properties $foo and $bar are identical, we have to apply ValEquals as follows:

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

SinglePropertyValidator​

Extend n2n\dispatch\map\val\SinglePropertyValidator to validate properties independently of each other. SinglePropertyValidator extends PropertyValidatorAdapter and lets you implement the method validateProperty($mapValue), which is executed for every property and passes its value as parameter. Use SinglePropertyValidator::failed() or SinglePropertyValidator::failedCode() to register errors and SinglePropertyValidator::getLabel() to get the label of the property that is currently being validated. failed() allows a string or an object of type n2n\l10n\Message (also n2n\l10n\MessageCode) as error message. You can find information about further helper methods in the 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 checks the size of an array. With $this->restrictType(null, true); we achieve that this validator can only be applied to array properties.

This validator could for example be applied as follows:

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

In this example we check whether the two properties $foos and $bars both have arrays with at least one and at most 5 fields as value.

SimplePropertyValidator​

Extend n2n\dispatch\map\val\SimplePropertyValidator if it is negligible for your validator whether it is applied to an array property or not. SimplePropertyValidator lets you implement the method validateValue(). Similar to the SinglePropertyValidator this method is called for every property. For array properties, however, it is called separately for every array field with its value as parameter. SimplePropertyValidator extends SinglePropertyValidator, so the same helper methods are available.

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 checks whether the values of all properties the validator is applied to have the respective suffix. If the property is an array, it is checked whether the values of all array fields have this suffix. We restrict this validator to property types that implement n2n\dispatch\property\SimpleProperty. Therefore we have to use SimpleProperty::convertMapValueToScalar() to convert the property value into a scalar value.

In the following example we assume that $additionalQuestions is an array property and $mainQuestion is not.

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

In this validation example we check whether the property $mainQuestion and all array fields of the property $additionalQuestion have a value with the suffix "?".

Reusability​

To increase the reusability of validators, the developer should have the possibility to optionally pass his own error message. If he does not, a default error message must be used in case of an error. For this you should fall back on a [link to text-collection docs]text code[/link to text-collection docs]. This text code can be overwritten when output in a view by the [link to View Text-Collection Info]current text file[/link to View Text-Collection Info]. To prevent this from happening unintentionally, the text code should contain the name (with namespace) of the validator.

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

You can pass the text code of the default error message as well as any arguments to SinglePropertyValidator::failed() as second and third parameter. failed() additionally adds the field "field" to the arguments of the text code, which contains the label of the current property. This also happens for passed objects of MessageCode. As fourth parameter the namespace of the module is expected, in which the text file should be searched. In the text file atusch\lang\en.ini the following entry could for example exist:

atusch.form.ValSuffix = "{field} requires suffix '{suffix}'.";