zend-frameworkzend-validatezend-filter

How to filter / validate for a boolean value?


i was just reading a great presentation about Quality Assurance for PHP Projects by Michelangelo van Dam aka DragonBe. In his presentation he uses Zend-Framework as an example, which i am familiar with on a basic level.

Since i want to improve myself, im starting with unit testing, though i feel the problem is on ZFs side.

The Problem:

With given code-artifacts whenever i assign a boolean value of false the Zend_Filter or Zend_Validator changes this into null. I can't figure out why this is the case.

Please see update on bottom for newer code and test

First the Tests

public function goodData() {
  return array(
    array('DU-IT', 'Sample Description', true, false),
    array('Mercüß', 'bla"ß"trager', false, true),
    array('Mia123', '728 Tage im Jahr', false, false)
  );
}

/**
 * @dataProvider goodData()
 */
public function testModelAcceptsValidData($name, $description, $flag_active, $flag_deleted)
{
  $data = array(
    'id'            => 0,
    'name'          => $name,
    'description'   => $description,
    'flag_active'   => $flag_active,
    'flag_deleted'  => $flag_deleted
  );

  try {
    $this->_model->populate($data);
  } catch (Zend_Exception $e) {
    $this->fail('Unexpected Exception: '.$e->getMessage());
  }

  $this->assertSame($data, $this->_model->toArray());
}

The Model

public function __construct($props = null)
{
  // Set Filters
  $this->_filters = array(
    'id'            => array('Int'),
    'name'          => array('StringTrim', 'StripTags'),
    'description'   => array('StringTrim', 'StripTags'),
    'flag_active'   => array(new Zend_Filter_Boolean()),
    'flag_deleted'  => array(new Zend_Filter_Boolean())
  );

  // Set Validators
  $this->_validators = array(
    'id'            => array('Int'),
    'name'          => array(new Zend_Validate_StringLength(array('min'=>4, 'max'=>50))),
    'description'   => array(new Zend_Validate_StringLength(array('max'=>5000))),
    'flag_active'   => array(new Zend_Validate_InArray(array(true, false))),
    'flag_deleted'  => array(new Zend_Validate_InArray(array(true, false)))
  );

  // Set Properties
  if (!is_null($props)) {
    $this->populate($props);
  }
}

public function setFlagActive($arg)
{
  $input = new Zend_Filter_Input($this->_filters, $this->_validators);
  $input->setData(array('flag_active'=>$arg));
  if (!$input->isValid('flag_active')) {
    throw new Zend_Exception('Invalid FLAG_ACTIVE provided'. gettype($input->flag_active));
  }
  $this->_flag_active = (bool) $input->flag_active;
  return $this;
}

As far as the model is concerned, i tried leaving the validator for flag_active and flag_deleted empty, but this did not change my results. The error message of phpunit remains the same:

Unexpected Exception: Invalid FLAG_ACTIVE providedNULL

Whereas NULL is the type of the variable, WHENEVER i pass false as the argument for the flag via the data-provider goodData() (same happens with badData, too).

My guess is this has something to do with Zend_Filter, but i just can't figure this out. And hints are greatly appreciated!

UPDATE Since Drew gave a little hint i've tested a little further, yet the problem remains. I have pasted the updated Class and unit test onto pastebin for better readability.

PHPUnit gives out the following error: You must give a non-empty value for field 'flag_active' same for flag_deleted. A second error on half the tests is <null> does not match expected type "boolean". I'm stuck. No matter if i use filters or an inArrayValidator(array(0=>true,1=>false)) it doesn't change a thing :\


Solution

  • If you examine the contents of $input->getMessages(), it should provide some explanation as to why that particular value is coming through as invalid.

    When you use Zend_Filter_Input, any value that did not pass validation will not be magically accessible by its name (e.g. $input->flag_active). The value will always be NULL if it didn't pass the validation step.

    Certain filters can almost guarantee validation in some cases (i.e. an Int filter with an Int validator) because the filter will filter the data and return a valid value for the validator. Passing hello through the Int filter returns 0 which may validate with a simple Int validator even though the original input data is not valid.

    The reason you are most likely getting a null value within your exception is because the value is not available since it didn't validate; therefore check the input messages for any hints as to why.

    EDIT:

    I think the problem you are having is that Zend_Filter_Input is not allowing empty values by default. Therefore if the value coming into the filter is false or NULL, it is causing an error with the filter.

    Check out this code:

        <?php
    
        // set up a boolean validator and inarray validator like you have
        $filters    = array('test' => array(new Zend_Filter_Boolean()));
        $validators = array('test' => array(new Zend_Validate_InArray(array(true, false))));
    
        // set up the input filter with default options
        $input      = new Zend_Filter_Input($filters, $validators);
    
        // our "input" value which is (bool)false
        $test       = false;
    
        $input->setData(array('test' => $test));
    
        // run through input filter using default values
        // should throw error about a non-empty value
        if (!$input->isValid('test')) {
            echo "Test is not valid.  <br />Original Value: ";
            var_dump($test);
            echo "<br />";
            print_r($input->getMessages());
        } else {
            echo "Test is valid.<br />Original Value: ";
            var_dump($test);
            echo "<br />Value now: ";
            var_dump($input->test);
        }
    
        // now allowing empty values...
        echo "<hr />\n";
    
        // tell Zend_Filter_Input to allow empty values on input
        $options = array(Zend_Filter_Input::ALLOW_EMPTY => true);
    
        // set up new input filter with option
        $input   = new Zend_Filter_Input($filters, $validators, null, $options);
    
        $input->setData(array('test' => $test));
    
        // run through input filter allowing empty values
        // this should NOT throw an error
        if (!$input->isValid('test')) {
            echo "Test is not valid.  <br />Original Value: ";
            var_dump($test);
            echo "<br />";
            print_r($input->getMessages());
        } else {
            echo "Test is valid.<br />Original Value: ";
            var_dump($test);
            echo "<br />Value now: ";
            var_dump($input->test);
        }
    

    So the problem seems to be that Zend_Filter_Input doesn't like empty values unless you specify an option. Therefore sending a valid (false) value into the filter causes an error unless you set Zend_Filter_Input::ALLOW_EMPTY to true.