Page 1 of 1

Filtering input and similar

Posted: Tue Apr 15, 2008 8:33 pm
by ManUnderground
Personally, I am tired of validating forms and manipulating the data so that it's ready to be inserted into a database or whatever. In addition, I was finding that I had to cast POST data into ints to use with Creole, which was just one more annoying little thing. Finally, I found it really annoying to communicate errors to the user because my error discovery and error messages were in different files.

So, I wrote an example XML file describing what I wanted. I've copied it below:

Code: Select all

 
<sample>
    <field name="title">
        <filter type="String" emsg="Must specify a title." />
        <filter type="Callback" emsg="Title must be unique.">
            <arg name="callback" value="Movie::isUnique" />
        </filter>
    </field>
    <field name="rating">
        <filter type="Int" emsg="Must specify a rating between 1 and 5.">
            <arg name="min" value="1" />
            <arg name="max" value="5" />
        </filter>
        <transform type="Int" />
    </field>
</sample>
 
Any comments on the XML are appreciated. A nice observation is that you could use this same XML to describe how to manipulate data on its way out of the application. For instance, to transform timestamps to strings before passing the data to some template.

I'd love some advice with the code I've written to turn this XML file into something that does work. It's all rough (and poorly named) but I think it's worth looking at. What's most important in my mind is that it be easy for myself or others to write new filters/transforms in the future.

I'd also love some suggestions on how to modify the current code to allow for aggregate operations. For instance, checking that two fields in the collection matched (say password and confirmed password).

The code is attached. Thanks!

Re: Filtering input and similar

Posted: Wed Apr 16, 2008 12:49 am
by matthijs
Could you also post the code here to look at? I'd prefer that over downloading something.

Re: Filtering input and similar

Posted: Wed Apr 16, 2008 10:41 am
by ManUnderground
Alright, there are a number of files, but most of them are pretty unimportant. Here are the ones which will give you a feel for what I'm trying to do:

Code: Select all

 
<?php
/**
 * Apply a set of defined rules to fields in an array.
 *
 * Define filters and transforms to apply to a field in metadata and then 
 * process a set of fields according to those rules. Return the verified 
 * and transformed data afterwards alongside any error messages.
 *
 * @author Alexander Schearer <aschearer@gmail.com>
 */
class Laundry_SpinCycle {
 
    /**
     * @var array 'field' => array(MetaCycle ... )
     */
    private $_cycles;
 
    /**
     * Construct a new spin cycle based on the given metadata.
     *
     * @param String $xml
     */
    function __construct($xml) {
        $p = Laundry_Paser_Factory::getInstanceFor('xml');
        $this->_cycles = $p->parse($xml); // this is a hack
    }
 
    /**
     * Run through the rules defined in the metadata and execute them. {{{
     *
     * Try to execute each rule defined in the metadata. If a rule fails 
     * then record its error message in the $err array. If a rule's field 
     * is missing then record an error without a message. Place data into 
     * the $out array after it has been processed.
     *
     * @param array $fields
     * @param array $out
     * @param array $err
     */
    function spin(array $fields, array & $out, array & $err) {
        foreach ($this->_cycles as $field => $cycles) {
            if (isset($fields[$field])) {
                $this->cycle($fields[$field], $cycles, $out, $err);
            } else {
                $err[$field] = TRUE;
            }
        }
    }
    /* }}} */
 
    /**
     * Apply all of the rules to the given field. {{{
     *
     * If a rule fails then record its error message and exit.
     *
     * @param unknown $field
     * @param array $cycles
     * @param array $out
     * @param array $err
     * @return boolean
     */
    private function cycle($field, $cycles, & $out, & $err) {
        foreach ($cycles as $cycle) {
            $c = new $cycle->type(); // total PHP hack
            if (!$c->actOn($field, $cycle->args)) {
                $err[$field] = $cycle->emsg;
                return FALSE;
            }
        }
        $out[$field] = $field;
        return TRUE;
    }
    /* }}} */
}
?>
 
This one is actually not included in the tar. But I think it's a good example of a filter.

Code: Select all

 
<?php
 
class Laundry_Filters_DateFilter {
 
    const VALID_DATE= '#^(0[1-9]|1[012])[-/.](0[1-9]|[12][0-9]|3[01])[-/.](19|20)\d\d$#';
 
    /**
     * Check that the field is a date or matches the passed pattern. 
     * Optionally make sure that it falls within the given time frame.
     */
    static function actOn($value, array $args) {
        $valid = preg_match(self::VALID_DATE, $value);
        // check whether the date comes 
        if ($valid && isset($args['before'])) {
            $date = strtotime($value);
            $before = strtotime($args['before']);
            $valid &= $before > $date;
        }
        if ($valid && isset($args['after'])) {
            $date = strtotime($value);
            $after = strtotime($args['after']);
            $valid &= $date > $after;
        }
        return $valid;
    }
}
?>