Filtering input and similar

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

Post Reply
ManUnderground
Forum Newbie
Posts: 4
Joined: Mon Jan 21, 2008 8:47 pm

Filtering input and similar

Post 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!
Attachments
laundry.tar
Source code to critique.
(20 KiB) Downloaded 197 times
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Re: Filtering input and similar

Post by matthijs »

Could you also post the code here to look at? I'd prefer that over downloading something.
ManUnderground
Forum Newbie
Posts: 4
Joined: Mon Jan 21, 2008 8:47 pm

Re: Filtering input and similar

Post 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;
    }
}
?>
 
Post Reply