phpvalidationzend-framework2zend-inputfiltermezzio

Zend Input Filter "either or" scenarios is not possible?


I'm on "zendframework/zend-inputfilter" v2.8.1. Quite surprisingly for me - I wasn't able to find the configuration or setup option for next scenario:

We have 2 fields, and either one or another needs to be populated.

For example: we have 2 fields: "year" and "age". I want to create a validation config where either "year" or "age" needs to be set.

So my valid payload should look like this

{
    "year": 2000
}

Or

{
    "age": 18
}

Or

{
    "year": 2000,
    "age": 18
}

This should be invalid:

{}

Unfortunately it seems other questions like this (Conditionally required in Zend Framework's 2 InputFilter) are either outdated or not accurate. For example if I try to make the field "year" optional, for payloads

{
    "age": 18
}

and

{}

it's getting skipped because of this code in \Zend\InputFilter\BaseInputFilter::validateInputs

        // If input is optional (not required), and value is not set, then ignore.
        if (! array_key_exists($name, $data)
            && ! $input->isRequired()
        ) {
            continue;
        }

If I make it required, I'm hitting condition in \Zend\InputFilter\Input::isValid

    if (! $hasValue && $required) {
        if ($this->errorMessage === null) {
            $this->errorMessage = $this->prepareRequiredValidationFailureMessage();
        }
        return false;
    }

I'm hesitant to override the BaseInputFilter or Input classes, but from what I can see right now that seems like my only option. However maybe I'm missing something. I'll appreciate any advice, thanks!


Solution

  • I believe what you need is a custom validator.

    You can do something like.

    class AgeYearValidator extends Zend\Validator\AbstractValidator
    {
        const AGE = 'age';
        const YEAR  = 'year';
        const EMPTY  = 'empty';
    
        protected $messageTemplates = array(
            self::AGE => "Age is invalid. It must be between 1 to 110.",
            self::YEAR  => "Year is invalid. It must between 1900 to 2050.",
            self::EMPTY  => "Age and Year input fields are both empty."
        );
    
        public function isValid($value, $context = null)
        {
            $isValid = true;
            $year = null;
            $age = null;
    
            if(isset($context['year'])){
                $year = (int)$context['year'];
            }
            if(isset($context['age'])){
                $age = (int)$context['age'];
            }
    
            if(is_null($year) && is_null($age)) {
                $this->error(self::EMPTY);
                $isValid = false;
            } else {
                $isAgeValid = false;
                if($age > 0 && $age < 110) {
                    $isAgeValid = true;
                }
                $isYearValid = false;
                if($year > 1900 && $year < 2050) {
                    $isYearValid = true;
                }
    
                if($isAgeValid || $isYearValid) {
                    $isValid = true;
                } else if (!$isAgeValid) {
                    $this->error(self::AGE);
                    $isValid = false;
                } else if (!isYearValid) {
                    $this->error(self::YEAR);
                    $isValid = false;
                }
            }
    
            return $isValid;
        }
    }
    

    I have used something similar before and it works nicely. The code itself is self-explanatory. You can find further info on Zend Validators. I can see they have some missing info on zend doco which I have added in my code for it to work.