Custom validator(chain) get multiple form inputs into validator

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 :blush:)
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…

1 Like

Is there a way to get it inside my filter/validator chain?

public function getInputFilterWerkervaring(): InputFilter
{

    $inputFilter = new InputFilter();
    
    //Bedrijf spaties verwijderen
        $input = new Input('bedrijfsnaam');
        $filterChain = new FilterChain();
        $filterChain->attach(new StringTrim(' '));
        $input->setFilterChain($filterChain);
    //Bedrijf valideren
        $validatorChain = new ValidatorChain();
        $validatorChain->attach(new StringLength(['min' => 1, 'max' => 128]));
        $input->setValidatorChain($validatorChain);
        $inputFilter->add($input);

    //Vestiging spaties verwijderen
        $input = new Input('locatie');
        $filterChain = new FilterChain();
        $filterChain->attach(new StringTrim(' '));
        $input->setFilterChain($filterChain);
    //Vestiging valideren
        $validatorChain = new ValidatorChain();
        $validatorChain->attach(new StringLength(['min' => 1, 'max' => 64]));
        $input->setValidatorChain($validatorChain);
        $inputFilter->add($input);
    //Functie spaties verwijderen
        $input = new Input('functie');
        $filterChain = new FilterChain();
        $filterChain->attach(new StringTrim(' '));
        $input->setFilterChain($filterChain);
    //Functie valideren
        $validatorChain = new ValidatorChain();
        $validatorChain->attach(new StringLength(['min' => 1, 'max' => 128]));
        $input->setValidatorChain($validatorChain);
        $inputFilter->add($input);
    //Startjaar Valideren
        $startjaar = new Input('startjaar');
        $validatorChain = new ValidatorChain();
        $validatorChain->attach(new NumberComparison(['min' => 1950, 'max' => 2025]), true);
        $startjaar->setValidatorChain($validatorChain);
        $inputFilter->add($startjaar);
    //Eindjaar 
        $eindjaar = new Input('eindjaar');
        $validatorChain = new ValidatorChain();
        $validatorChain->attach(new NumberComparison(['min' => 1950, 'max' => 2025]), true);
        $eindjaar->setValidatorChain($validatorChain);
        $inputFilter->add($eindjaar);
        
        return $inputFilter;
}

$inputFilter->setData($_POST) did the trick, after that i can use the value’s