ZF-8446: Zend_Filter_Input: Custom NotEmpty error message "inherited" by other NotEmpty validators

Description

Setting a custom error message for a NotEmpty validator in Zend_Filter_Input results in that custom error message being used for all following NotEmpty validators (if those validators don't themselves have custom error messages).

h3. Using Zend_Validate_NotEmpty


<?php

require 'Zend/Debug.php';
require 'Zend/Filter/Input.php';
require 'Zend/Validate/NotEmpty.php';

$data = array(
    'field1' => '',
    'field2' => ''
);

$validators = array(
    'field1' => array(
        new Zend_Validate_NotEmpty(),
        Zend_Filter_Input::MESSAGES => array(
            array(
                Zend_Validate_NotEmpty::IS_EMPTY => '\'field1\' is required'
            )
        )
    ),

    'field2' => array(
        new Zend_Validate_NotEmpty()
    )
);

$input = new Zend_Filter_Input( null, $validators, $data );

if( !$input->isValid() ) {
    Zend_Debug::dump( $input->getMessages() );
}

array(2) {
  ["field1"] => array(1) {
    ["isEmpty"] => string(20) "'field1' is required"
  }
  ["field2"] => array(1) {
    ["isEmpty"] => string(36) "Value is required and can't be empty"
  }
}

array(2) {
  ["field1"] => array(1) {
    ["isEmpty"] => array(1) {
      ["isEmpty"] => string(20) "'field1' is required"
    }
  }
  ["field2"] => array(1) {
    ["isEmpty"] => array(1) {
      ["isEmpty"] => string(20) "'field1' is required"
    }
  }
}

(The nested "isEmpty" arrays seem to be another issue with the customization of the NotEmpty error messages. Possibly related.)

This issue exists even when removing the NotEmpty validator from 'field2', falling back to Zend_Filter_Input's "built-in" NotEmpty validator.

Custom error messages for other validator types don't seem to suffer from this problem.

h3. Using Zend_Validate_EmailAddress


<?php

require 'Zend/Debug.php';
require 'Zend/Filter/Input.php';
require 'Zend/Validate/EmailAddress.php';

$data = array(
    'field1' => 'foo',
    'field2' => 'bar'
);

$validators = array(
    'field1' => array(
        new Zend_Validate_EmailAddress(),
        Zend_Filter_Input::MESSAGES => array(
            array(
                Zend_Validate_EmailAddress::INVALID_FORMAT => 'Improperly formatted email address \'%value%\' in \'field1\''
            )
        )
    ),

    'field2' => array(
        new Zend_Validate_EmailAddress()
    )
);

$input = new Zend_Filter_Input( null, $validators, $data );

if( !$input->isValid() ) {
    Zend_Debug::dump( $input->getMessages() );
}

array(2) {
  ["field1"] => array(1) {
    ["emailAddressInvalidFormat"] => string(52) "Improperly formatted email address 'foo' in 'field1'"
  }
  ["field2"] => array(1) {
    ["emailAddressInvalidFormat"] => string(74) "'bar' is not a valid email address in the basic format local-part@hostname"
  }
}

Comments

Hmm... part of the issue here seems to be the fact that Zend_Filter_Input inserts its internal NotEmpty validator at the front of the validator chain and sets it to break the chain on failure.


921: $validatorChain = new Zend_Validate();
922: $validatorChain->addValidator($notEmptyValidator, true /* Always break on failure */);
923: $validatorChain->addValidator($validatorRule[self::VALIDATOR_CHAIN]);

A quick fix is to not break the chain on failure:


921: $validatorChain = new Zend_Validate();
922: $validatorChain->addValidator($notEmptyValidator);
923: $validatorChain->addValidator($validatorRule[self::VALIDATOR_CHAIN]);

The unfortunate side-effect of this is that, with the sample code in my OP, two NotEmpty validators are run on each value. It appears to work correctly because the "isEmpty" key gets reassigned by the second NotEmpty validator, which will be the developer-assigned one.

This also doesn't actually solve the "inheritance" issue for fields that rely on the internal NotEmpty validator.


<?php

require 'Zend/Debug.php';
require 'Zend/Filter/Input.php';
require 'Zend/Validate/NotEmpty.php';

$data = array(
    'field1' => '',
    'field2' => '',
    'field3' => ''
);

$validators = array(
    'field1' => array(
        new Zend_Validate_NotEmpty(),
        Zend_Filter_Input::MESSAGES => array(
            array(
                Zend_Validate_NotEmpty::IS_EMPTY => '\'field1\' is required'
            )
        )
    ),

    'field2' => array(
    ),

    'field3' => array(
    )
);

$input = new Zend_Filter_Input( null, $validators, $data );

if( !$input->isValid() ) {
    Zend_Debug::dump( $input->getMessages() );
}

array(2) {
  ["field1"] => array(1) {
    ["isEmpty"] => string(20) "'field1' is required"
  }
  ["field2"] => array(1) {
    ["isEmpty"] => string(50) "You must give a non-empty value for field 'field2'"
  }
  ["field3"] => array(1) {
    ["isEmpty"] => string(50) "You must give a non-empty value for field 'field3'"
  }
}

array(2) {
  ["field1"] => array(1) {
    ["isEmpty"] => string(20) "'field1' is required"
  }
  ["field2"] => array(1) {
    ["isEmpty"] => array(1) {
      ["isEmpty"] => string(20) "'field1' is required"
    }
  }
  ["field3"] => array(1) {
    ["isEmpty"] => array(1) {
      ["isEmpty"] => string(20) "'field1' is required"
    }
  }
}

The reason for this appears to be the following code:


780: if ($validator instanceof Zend_Validate_NotEmpty) {
781:     $this->_defaults[self::NOT_EMPTY_MESSAGE] = $value;
782: }

This simply takes the custom message of each developer-assigned NotEmpty validator and makes it the default empty message for Zend_Filter_Input. When a field has no NotEmpty validator of its own, and is empty, the internal NotEmpty validator ends up returning the custom message of the last, developer-assigned NotEmpty validator.

(Additionally, given the first sample code in my OP, $value will be an array here. This explains the nested "isEmpty" arrays I pointed out in the output in the OP.)

The fix here is to simply remove lines 780-782 (global defaults should never be transparently overwritten anyway). This fix, in conjunction with the minor fix to line 922, appears to fix the issues I've mentioned here. (NOTE: I've only done limited testing so far. This may create other issues I haven't come across yet. The potential double-run of NotEmpty validators I mentioned above is still an issue, though.)

I apologize for talking to myself here, but...

On second thought, my suggested change to line 922 in my previous comment completely undoes the entire reason for having the ALLOW_EMPTY option, since the entire validator chain will now run regardless of the emptiness of the value.

Hmm... I'll wait for additional comments.

I linked this issue, there is a patch available in the linked issue.

The patch is in the related issue. So far I found three issues reporting the same problem with the inherited error messages.

Fix is in svn now.

I can confirm that the patch attached to ZF-11142 does indeed fix the inherited error message issue. The nested error message issue is still present, but since that now appears to be unrelated[1], I think this issue can be considered closed. I don't seem to have permission to update the status myself, though, so someone else will have to do the honors.

Thanks, Bart!

-- [1] If a separate issue doesn't already exist for this, I'll go ahead and create one.

Closed by permission of the reporter.

Reopening to attach patches for the nested error message.

As usual, I will leave this issue open for a week for the community and the component owner to review the patches.

If no one has complained after a week, I will commit the patch for the component and the patch for the UnitTest.

Added the patches.

Changed to minor, because these patches are only to fix the nested error messages. The other issue, with the inherited error messages, is already fixed in svn.

The fix for the nested error messages is in svn now.

Fixed in ZF2 with GH-268