How to make this Filter run after this Validator
Asked Answered
B

4

6

I have an element. I want to add a custom validator and custom filter to it. The validator makes sure the input is one of several permitted values, then the filter adds some custom values to the input. This means I have to validate the original input first before running the filter. I do it in this order

$element = new Zend_Form_Element_Text('element');
$element->addValidator('PermittedValue', false);
$element->addFilter('TotalHyphen', false);
$this->addElement($element);

but this order isn't being respected. The filter runs first and changes the data, then the validator runs on the filtered data which means it always fails even for valid input. It seems from documentation that this is intentional

Note: Validation Operates On Filtered Values Zend_Form_Element::isValid() filters values through the provided filter chain prior to validation. See the Filters section for more information.

How can I specify the order in which validators and filters run?

Billmyre answered 28/1, 2011 at 0:25 Comment(0)
H
7

Sure seems like creating a custom element that supports post-validation filtering would be the way to go. How about this:

/**
 * An element that supports post-validation filtering
 */
class My_Form_Element_PostValidateFilterable extends Zend_Form_Element_Text
{
    protected $_postValidateFilters = array();

    public function setPostValidateFilters(array $filters)
    {
        $this->_postValidateFilters = $filters;
        return $this;
    }

    public function getPostValidateFilters()
    {
        return $this->_postValidateFilters;
    }

    public function isValid($value, $context = null)
    {
        $isValid = parent::isValid($value, $context);
        if ($isValid){
            foreach ($this->getPostValidateFilters() as $filter){
                $value = $filter->filter($value);
            }
            $this->setValue($value);
        }
        return $isValid;
    }
}

Usage would be something like this:

$elt = $form->addElement('PostValidateFilterable', 'myElement', array(
     'label' => 'MyLabel',
     'filters' => array(
         'StringTrim',
         // etc
     ),
     'validators' => array(
         'NotEmpty',
         // etc 
     ),
     // here comes the good stuff
     'postValidateFilters' => array(
         new My_Filter_RunAfterValidateOne(),
         new My_Filter_RunAfterValidateTwo(),
     ),
));

This keeps the validation and filtering in the form - keeping the controller thin.

Not tested, just a stab in the dark. And surely you could fatten/modify the API to add/remove filters by key, etc.

Whaddya think?

Hammock answered 28/1, 2011 at 13:57 Comment(2)
+1. Looks very nice. I also did not know that setPostValidateFilters() method will be called automatically (yes?) for key 'postValidateFilters'.Trieste
@Marcin: I think that's the way the options work. If there is a method that "matches" the option name, then that method gets called during setOptions().Hammock
W
4

Maybe don't add the filter at all. Validate the content first in the controller, and then use the filter separately:

$request = $this->getRequest();
if ($request->isPost() && $form->isValid($request->getParams())) {
    $filter = new Filter_Whatever();
    $val = $filter->filter($request->getParam('element'));
    ... //call your model or whatever
}

I've never done this, but I suppose this (or something similar) might work.

Waterworks answered 28/1, 2011 at 0:38 Comment(2)
+1 It's a possible solution, but not sure I like it. I'll do it if all else fails, but I'd prefer to keep the controller thin and keep the filtering logic with the form element where it belongs.Billmyre
Sure, I'd prefer to keep it in the form code as well, but I can't think of any other way of influencing the order in which validators and filters are called. I sometimes add stuff to the controller as well that should ideally reside in the form, for instance, when a set of fields are required if and only if something else is checked/filled. You can't reliably check that on the client side or prior to the user filling the form, so doing some extra form magic in the controller is sometimes necessary, like it or not :(.Waterworks
E
1

Good point ! ,

AFAIK filters should or must run before validating the input : from ZF docs

It's often useful and/or necessary to perform some normalization on input prior to validation. For example, you may want to strip out all HTML, but run your validations on what remains to ensure the submission is valid. Or you may want to trim empty space surrounding input so that a StringLength validator will use the correct length of the input without counting leading or trailing whitespace characters.

but if and only if you are in case which can't solve mingos's answer must be the help

Ecbolic answered 28/1, 2011 at 0:42 Comment(1)
I think it's a design flaw. Some filters are very complex and they make modifications which could make the input invalid. So filters should be divided into 2 types. The normalization filters you mention like trim that need to run before validation, and other filters that make more complex modifications. The developer can then choose when adding the filter when to trigger this filter.Billmyre
T
1

What you want to achieve is to change default behavior of how text element is being processed. Thus, I think you could create your own element (e.g. My_Form_Element_Text) that extends Zend_Form_Element_Text and overload its isValid() method.

Specifically you could just change second line in the orginal isValid() method, from $value = $this->getValue(); into $value = $this->getUnfilteredValue();. This way your validation will be performed using unfiltered values.

Trieste answered 28/1, 2011 at 3:16 Comment(2)
This is an interesting idea. The only problem I'm seeing with it is that I automatically forfeit all the other filters. If I do the modification on the unfiltered value, then I lose the Trim filter for example. For this reason, so far mingo's answer probably gives me the desired effect though I don't like it being in the controller. It would have been ideal if the developer could specify the exact order he wants for validators and filters.Billmyre
@jblue. So you want to have only one filter applied after validation, and the rest in a normal way, yes? This of course still could be done by making your custom text element.Trieste

© 2022 - 2024 — McMap. All rights reserved.