Hello Laminas Friends,
I want to extend my validatorchain with some custum validator(s). Writing the validators is not a problem, but how to get the inputs into the validatorchain?
I want to compare 2 month/year inputs (and I know I can also do that with datecomparison if I use a date field, but for some reasons I need to do it like this )
I wrote a simple class (see below) to invoke it in the validatorchain, but how to get 2 inputs in the object in the vallidatorchain. I assume somewhere where the stars are, I want to compare “startjaar” en “eindjaar” inputs in the validator below.
final class DatumInterval extends AbstractValidator
{
public const ERR_DATE = 'date';
protected array $messageTemplates = [
self::ERR_DATE => "'%value1%' is bigger than '%value2%'",
];
public function isValid(mixed $value1,mixed $value2): bool
{
//$this->setValue($value1);
if ($value1 > $value2) {
$this->error(self::ERR_DATE);
return false;
}
return true;
}
}
public function getInputFilterWerkervaring(): InputFilter
{
$inputFilter = new InputFilter();
……
//Startjaar Valideren
$input = new Input('startjaar');
$validatorChain = new ValidatorChain();
$validatorChain->attach(new NumberComparison(['min' => 1950, 'max' => 2025]));
$input->setValidatorChain($validatorChain);
$inputFilter->add($input);
//Eindjaar
$input = new Input('eindjaar');
$validatorChain = new ValidatorChain();
$validatorChain->attach(new DatumInterval(******));
$input->setValidatorChain($validatorChain);
$inputFilter->add($input);
return $inputFilter;
}
As soon as you are validating multiple inputs, you should be using an input filter.
Within InputFilter, you can retrieve the wider context and use that for further validation.
use Laminas\Filter\StringTrim;
use Laminas\Filter\ToNull;
use Laminas\InputFilter\Input;
use Laminas\InputFilter\InputFilter;
use Laminas\Validator\Callback;
use Laminas\Validator\Date;
use Laminas\Validator\NotEmpty;
final class MyInputFilter extends InputFilter {
#[Override]
public function init(): void
{
$this->add([
'name' => 'input1',
'filters' => [
['name' => StringTrim::class],
['name' => ToNull::class],
],
'validators' => [
['name' => NotEmpty::class],
[
'name' => Date::class,
'break_chain_on_failure' => true,
],
],
]);
$this->add([
'name' => 'input2',
'filters' => [
['name' => StringTrim::class],
['name' => ToNull::class],
],
'validators' => [
['name' => NotEmpty::class],
[
'name' => Date::class,
'break_chain_on_failure' => true,
],
],
]);
// At this point we have 2 fields that will both be valid dates
$input1 = $this->get('input1');
assert($input1 instanceof Input);
$input2 = $this->get('input2');
assert($input2 instanceof Input);
$input2->getValidatorChain()->attach(new Callback([
'callback' => function () use ($input1, $input2): bool {
$date1 = DateTimeImmutable::createFromFormat('!Y-m-d', (string) $input1->getValue());
// Careful, $date1 could very well be invalid here
$date2 = DateTimeImmutable::createFromFormat('!Y-m-d', (string) $input2->getValue());
// $date2 is guaranteed to be valid at this point
if ($date2 <= $date1) {
/** @var Callback $this */
$this->setMessage('Date 2 must be after date 1');
return false;
}
return true;
},
'bind' => true, // Sets $this to be the Callback instance inside the closure.
]));
}
}
The example above is for Dates as opposed to Intervals, but the process is the same - ensure each single value is valid in its basic form, then use additional callback validators when you need to enforce certain relationships between values.
Note the break_chain_on_failure - This means that the callback will not run on the input until it is basically valid…