Date field validation

I have setup a callback validator for a Date field which means it will have an error if another field in the $context has a specific value. I have setup the validation so it works as expected for this error but doing this also triggers the The input does not appear to be a valid date.

This is the form element

 $this->add([
            'name' => 'DatePosted',
            'type' => Date::class,
            'options' => [
                'label' => 'Posted Date',
            ],
            'attributes' => [
                'placeholder' => 'Date Posted',
            ],
        ]);

This is the inputFilterSpecification for that field:

'DatePosted' => [
                'required' => false,
                'allow_empty' => true,
                'continue_if_empty' => true,
                'validators' => [
                    [
                        'name' => Callback::class,
                        'options' => [
                            'messages' => [
                                Callback::INVALID_VALUE => 'Posted Date is required when Confirming.'
                            ],
                            'callback' => function ($value, $context = []) {
                                $valid = true;

                                if ($context['Status'] == Status::COLLECTED && $value == '') {
                                    $valid = false;
                                }

                                return $valid;
                            }
                        ]
                    ]
                ]

Is it possible I can have the validation fire and the field only be validated if it is populated?

Hi @mattG10,

If you’re looking for a solution to get the work done, then I think a check would work. But, if you’re looking for a solutions based on listeners then you’ve to wait for the right answer. Thanks!

DatePosted' => [
                'required' => false,
                'allow_empty' => true,
                'continue_if_empty' => true,
                'validators' => [
                    [
                        'name' => Callback::class,
                        'options' => [
                            'messages' => [
                                Callback::INVALID_VALUE => 'Posted Date is required when Confirming.'
                            ],
                            'callback' => function ($value, $context = []) {
                                $valid = true;

                                if( empty( $context['Status'] ) ){
                                     $valid = true;
                                }else if ($context['Status'] == Status::COLLECTED && $value == '') {
                                    $valid = false;
                                }

                                return $valid;
                            }
                        ]
                    ]
                ]

Thanks for the reply but this validator is working correctly without your change. The problem is there is another validator triggering because of the continue_if_empty option, which I need for the callback to be triggered.

Digging into the code more it seems to be the DateStep validator is what is being triggered.

Hi @mattG10,

Some other validators causing the issue. Then explain it a bit more to @froschdesign he will give you the answer you’re looking for. Thanks!

Yeah the standard Date element validators are being triggered because I’m always validating the field so my callback runs.

I’ll see what @froschdesign has to say :slight_smile:

Are you sure your form value for your date element is empty?

Instead of just validating this field, you should apply some filters to it. Just extend your input filter specification to the following.

'date_posted' => [
    'required' => false,
    'allow_empty' => true,
    'confinue_if_empty' => true,
    'filters' => [
        [ 'name' => \Laminas\Filter\StripTags::class ],
        [ 'name' => \Laminas\Filter\StringTrim::class ],
        [
            'name' => \Laminas\Filter\ToNull::class,
            'options' => [
                'type' => \Laminas\Filter\ToNull::TYPE_STRING,
            ],
        ],
    ],
    'validators' => [
        ...
    ],
],

These filters set the value to null, after the value has been trimmed and stripped. Without those filters your element value can be everything. For type safety and easy value checking just filter it to null, if nothing else was typed.

The continue_if_empty setting forces the input filter / validator, to continue if the element value is empty. So every validator in the filter chain will be executed. The date validator will be appended automatically for date elements. So the error you 're experiencing is logically right, because null or an empty string is no valid date.

Keep in mind, that additional date validators like the DateStepValidator is just added to the validator chain, if your date input element got a step attribute. Same goes for all other additional element attributes like min and max.

There is another option you can use. Just try to add a 'break_chain_on_failure' => true, to your callback validator. This results into a break of the validator chain, when the callback validator result is invalid. Another point could be optional input filters.

Thanks Ezkimo for your reply.

If I apply the filters to the element and submit my form with no value in the date_posted field then I get validation for the trim and that the input is not a valid date.

Invalid type given. String, integer, array or DateTime expected
The input does not appear to be a valid date

Debugging the validator the value is now null but when the Date validator isValid function is called because the value is false after is converted by the convertToDateTime call the system sets the error.

    /**
     * Returns true if $value is a DateTimeInterface instance or can be converted into one.
     *
     * @param  string|numeric|array|DateTimeInterface $value
     * @return bool
     */
    public function isValid($value)
    {
        $this->setValue($value);

        $date = $this->convertToDateTime($value);
        if (! $date) {
            $this->error(self::INVALID_DATE);
            return false;
        }

        if ($this->isStrict() && $date->format($this->getFormat()) !== $value) {
            $this->error(self::FALSEFORMAT);
            return false;
        }

        return true;
    }

Just to check I am not barking up the wrong tree here I should be able to have this field which is not required but can be validated by a callback. It will also be validated by the standard validators if the user enters a value?

So I thought I’d try and tackle this a different way and the Status field I’d instead check the other elements as they would be in the $context array but even then I don’t see how I can set them as required because I am only validating the Status field at this point.

Has anyone else tried to add conditional validation within a fieldset using a callback validator or do I need to do this outside the fieldset? This is also in a collection, not sure if that makes a difference.

Hi @mattG10, have you ever thought of using the input field instead of the date field?