Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How use Callback validator when field is conditionally required? #2

Open
weierophinney opened this issue Dec 31, 2019 · 5 comments
Open
Labels
Question Further information is requested

Comments

@weierophinney
Copy link
Member

I have two fields and one of them is required if the value of the other is equal to one condition.

How can I do this?

The "campaignFranchise" field is only required when the value of the "campaign" is equal to "franchise".

            4 => [
                'required' => true,
                'validators' => [
                    0 => [
                        'name' => NotEmpty::class,
                        'options' => [...],
                    ],
                    1 => [
                        'name' => StringLength::class,
                        'options' => [...],
                    ],
                ],
                'filters' => [...],
                'name' => 'campaign',
            ],
            5 => [
                'required' => true,
                'validators' => [
                    0 => [
                        'name' => NotEmpty::class,
                        'options' => [
                            'translatorenabled' => true,
                        ],
                        'break_chain_on_failure' => true,
                    ],
                    1 => [
                        'name' => Callback::class,
                        'options' => [
                            'callback' => [
                                CampaignFranchise::class,
                                'isValid',
                            ],
                        ],
                    ],
                    2 => [
                        'name' => StringLength::class,
                        'options' => [...],
                    ],
                ],
                'filters' => [...],
                'name' => 'campaignFranchise',
            ],

I try use "continueIfEmpty" and "allowEmpty" but how I don't have value and is required, the validation return false.
https://github.com/zendframework/zend-inputfilter/blob/master/src/Input.php#L410-415

If the field is required false, the validation does not trigger.
https://github.com/zendframework/zend-inputfilter/blob/master/src/BaseInputFilter.php#L251-L256

And if I use the callback validator in "campaign" field, the message error result display in wrong field.


Originally posted by @vincequeiroz at zendframework/zend-inputfilter#146

@weierophinney weierophinney added the Question Further information is requested label Dec 31, 2019
@weierophinney
Copy link
Member Author

hmm there is no feature for this I guess but you can use validationGroup and override isValid method of inputFilter something like this should work(not tested):

public function isValid($context = null)
{
    if ($this->getRawValue('campaign') !== 'franchise') {
        $validationGroup = array_keys($this->getInputs());
        unset($validationGroup['campaignFranchise']);
        $this->setValidationGroup($validationGroup);
    }
    return parent::isValid($context);
}

Originally posted by @svycka at zendframework/zend-inputfilter#146 (comment)

@weierophinney
Copy link
Member Author

Example for you.

Use-case, an Entity can have child Entity Coordinates. Coordinates exists out of both latitude and longitude. Coordinates can only exist if both are present and must not be created if only one property is set.

class CoordinatesFieldsetInputFilter extends AbstractDoctrineFieldsetInputFilter
{
    public function init()
    {
        parent::init();

        $this->add([
            'name' => 'latitude',
            'required' => true,
            'allow_empty' => true,
            'filters' => [
                ['name' => StringTrim::class],
                ['name' => StripTags::class],
                [
                    'name' => ToNull::class,
                    'options' => [
                        'type' => ToNull::TYPE_STRING,
                    ],
                ],
                [
                    'name' => CallbackFilter::class,
                    'options' => [
                        'callback' => function ($value) {
                            $float = $value;

                            if ($value) {
                                $float = str_replace(',', '.', $value);
                            }

                            return $float;
                        },
                    ],
                ],
            ],
            'validators' => [
                [
                    'name' => StringLength::class,
                    'options' => [
                        'min' => 2,
                        'max' => 255,
                    ],
                ],
                [
                    'name' => CallbackValidator::class,
                    'options' => [
                        'callback' => function($value, $context) {
                            //If longitude has a value, mark required
                            if(empty($context['longitude']) && strlen($value) > 0) {
                                $validatorChain = $this->getInputs()['longitude']->getValidatorChain();

                                $validatorChain->attach(new NotEmpty(['type' => NotEmpty::NULL]));
                                $this->getInputs()['longitude']->setValidatorChain($validatorChain);

                                return false;
                            }

                            return true;
                        },
                        'messages' => [
                            'callbackValue' => _('Longitude is required when setting Latitude. Give both or neither.'),
                        ],
                    ],
                ],
            ],
        ]);

        $this->add([
            'name' => 'longitude',
            'required' => true,
            'allow_empty' => true,
            'filters' => [
                ['name' => StringTrim::class],
                ['name' => StripTags::class],
                [
                    'name' => ToNull::class,
                    'options' => [
                        'type' => ToNull::TYPE_STRING,
                    ],
                ],
                [
                    'name' => CallbackFilter::class,
                    'options' => [
                        'callback' => function ($value) {
                            $float = $value;

                            if ($value) {
                                $float = str_replace(',', '.', $value);
                            }

                            return $float;
                        },
                    ],
                ],
            ],
            'validators' => [
                [
                    'name' => StringLength::class,
                    'options' => [
                        'min' => 2,
                        'max' => 255,
                    ],
                ],
                [
                    'name' => CallbackValidator::class,
                    'options' => [
                        'callback' => function($value, $context) {
                            //If longitude has a value, mark required
                            if(empty($context['latitude']) && strlen($value) > 0) {
                                $validatorChain = $this->getInputs()['latitude']->getValidatorChain();
                                $validatorChain->attach(new NotEmpty(true));
                                $this->getInputs()['latitude']->setValidatorChain($validatorChain);

                                return false;
                            }

                            return true;
                        },
                        'messages' => [
                            'callbackValue' => _('Latitude is required when setting Longitude. Give both or neither.'),
                        ],
                    ],
                ],
            ],
        ]);
    }
}

Originally posted by @rkeet at zendframework/zend-inputfilter#146 (comment)

@fabioginzel
Copy link

+1

@devkokov
Copy link

+100

@g10-fred
Copy link

g10-fred commented Sep 2, 2022

Can this be done when using a InputFilterSpecification provided by a Fieldset?

class MyFieldset extends Fieldset implements InputFilterProviderInterface

If I add a callback validator to my InputFilterSpecification I can't find a way to access the ValidatorChain, the "$this->getInputs()" function is undefined and I can't see an alternative.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants