Announcement

Collapse
No announcement yet.

Smoothing Input Validation

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Smoothing Input Validation

    Edit:
    Wow, been a long while since I remembered this. I would like to clarify a couple of points. First of all this was an experimental idea and ultimately I found it impractical. The expectation was to create a fluid syntax for input validation which some of you may recognize as a tedious and grueling task. The result was something even less practical and its future would have resulted in PHP's syntax being reimplemented. In short: write it out. Not only is it easier to understand and maintain, its really not much extra work. I type 75WPM and I am told that I am only average, so what's the extra time to you?


    With that out of the way (^), please feel free to reason an opinion for yourself on the ideas presented here.

    Validating input is always a cumbersome task. However, it is a requirement in any online application that people can interact with. We are all human and often make mistakes. There are also the people who simply want to be malicious. In either case input validation saves both us and users with the proper intent.

    You probably do your input validation with a series of if-else blocks, or similar. The problem is that code bloats as the number of inputs increases. Soon you have unwieldy code to maintain filled with series of nested if-else blocks. So the question is, how can we validate input with the least amount of code?

    The answer is through chaining validation functions. Let's take a standard input validation:

    PHP Code:
    <?php

    $errors 
    = array();

    if (isset(
    $_POST['age'])) {
        
    $age $_POST['age'];
        if (
    ctype_digit($age)) {
            if (
    $age 10) {
                
    $errors['age'] = 'You are not old enough';
            }
            elseif (
    $age 120) {
                
    $errors['age'] = 'You cannot possibly be alive';
            }
        }
        else {
            
    $errors['age'] = 'That is not a valid age';
        }
    }
    else {
        
    $errors['age'] = 'Please fill out your age';
    }

    ?>
    That's a lot for a single input, isn't it? What if you were asking for their age, gender, and location? Your code length would triple! Using the OOP powers invested in PHP 5 we can certainly do better.

    PHP Code:
    <?php
    class ValSubm {

        protected
        
    $isset,
        
    $stop,
        
    $val,
        
    $valid;

        public function 
    __construct($post_key false) {

            
    $this->valid true;
            
    $this->stop false;

            if (
    $post_key !== false) {
                if (isset(
    $_POST[$post_key])) {
                    
    $this->isset true;
                    
    $this->val $_POST[$post_key];
                }
                else {
                    
    $this->isset false;
                    
    $this->valid false;
                }
            }

        }

        public function 
    val($new_post_key) {

            
    $this->__construct($new_post_key);
            return 
    $this;

        }

        public function 
    notSet($def_value$stop true) {

            if (!
    $this->isset) {
                
    $this->val $def_value;
                
    $this->valid true;
                
    $this->stop $stop;
            }

            return 
    $this;

        }

        public function 
    notValid($def_value$stop true) {

            if (!
    $this->valid) {
                
    $this->val $def_value;
                
    $this->valid true;
                
    $this->stop $stop;
            }

            return 
    $this;

        }

            public function 
    getString() {
            if (
    $this->stop) {
                return 
    $this->val;
            }
            return 
    $this->valid !== false ? (string) $this->val false;
        }

        public function 
    getInt() {
            if (
    $this->stop) {
                return 
    $this->val;
            }
            return 
    $this->valid !== false ? (int) $this->val false;
        }

        public function 
    getFloat() {
            if (
    $this->stop) {
                return 
    $this->val;
            }
            return 
    $this->valid !== false ? (float) $this->val false;
        }

        public function 
    getBool() {
            if (
    $this->stop) {
                return 
    $this->val;
            }
            return 
    $this->valid !== false ? (bool) $this->val false;
        }

        public function 
    getRaw() {
            if (
    $this->stop) {
                return 
    $this->val;
            }
            return 
    $this->valid !== false $this->val false;
        }

        public function 
    __call($func$args) {

            if (!
    $this->valid || $this->stop) {
                return 
    $this;
            }

            
    $cb_return call_user_func_array($funcarray_merge(array($this->val), $args));

            if (!
    is_bool($cb_return)) {
                
    $this->val $cb_return;
            }
            elseif (
    $cb_return === false) {
                
    $this->valid false;
            }

            return 
    $this;

        }

    }
    ?>
    Whew, that looks like way more code than before! Well, it is, but this is reusable code for any type of input validation. This is code you do not have to maintain either, in fact you shouldn't ever have to look at it.

    So how does this class assist with your validation needs? Let's rewrite our test case using the ValSubm (validate submission) class.

    PHP Code:
    <?php
    $age 
    = new ValSubm('age');
    $age $age->ctype_digit()->within(1119)->notValid(false)->getInt();
    ?>
    When we create the ValSubm object we specify the key in the $_POST array we will be validating. In this case we are validating $_POST['age'], so we pass 'age'.

    There are four types of functions that we have to consider in these chains.

    1) Validation functions
    2) Sanitizing functions
    3) Special condition functions
    4) Return-value functions (these are ALWAYS at the end of the chain)

    First lets look at validation functions. We can use any function that will accept a string as its first parameter (the string is the user's input) and return a boolean value as a validation function. In our test case we used both a built-in function, ctype_digit(), and our own function within() (in_array(range(1, 119)) would have also worked). If the return value is TRUE the validation (and sanitizing) process continues. If the return value is FALSE, all further validation (and sanitizing) processes are bypassed. In the case A()->B()->C(), if A() returns false B() and C() are never called. If C() returns false it has no affect on A() and B(), because they have already been called. Whereas if B() returns false C() is bypassed but A() will still have been called. Make sure to write your chains in a logical order.

    Secondly lets take a look at sanitizing functions. They must accept a string as its first parameter. These are not shown in our test case, and because of that we will create another one just for this example.
    PHP Code:
    $comment $comment->htmlspecialchars()->getString(); 
    htmlspecialchars does not TRUE or FALSE, it returns a string. In fact, any return value that is not TRUE or FALSE in our chain will tell ValSubm that we want to change the value we are validating (and sanitizing) to what our function returned. So if $_POST['comment'] was
    Code:
    <title>HTML Inject</title>
    $comment will come out as
    Code:
    &lt;title&gt;HTML Inject&lt;/title&gt;
    Other sanitizing could be addslashes(), or mysql_real_escape_string() for example. It is still perfectly valid to chain a validation function after a sanitizing function. Just remember that the new value returned by the sanitizing function will now be used as the first paramter passed to following functions.

    Thirdly we can change the way our chain works by the use of methods defined in the ValSubm class. These methods are notSet() and notValid(), which behave similarly. In our example (using $age) you can see the usage of notValid(). notValid() takes affect only if the input is currently considered invalid (a validation function returned false, OR the key did not exist in the $_POST array). The first parameter notValid takes is a new value that will be used for further evaluation. It also takes an optional second parameter that when true (default) will stop the chain and return the value we set as-is. If false it will continue evaluation based on the new value you set. The other special function, notSet(), only takes affect if the key did not exist in the $_POST array. It works exactly like notValid() as far as parameters. The first parameter is the new value, and the second parameter is the stop toggle. Keep in mind that notValid() and notSet() are the only special functions, you cannot use user-defined ones as ValSubm will think they are either validation functions or sanitizing functions.

    Finally, we must have a way to return the value. ValSubm provides a number of ways to do this. getString(), getInt(), getFloat(), getBool(), and getRaw(). Each one coerces the final value into the respective data type and returns it. getRaw() is the exception, it returns the final value as-is. Keep in mind that if the chain execution was stopped by notSet() or notValid() that all returning functions will work like getRaw(), returning your value as-is (which will be the value you set as the first param in notSet() or notValid()).

    ...

    I have been writing this documentation for a LONG time now. Please let me know if you find anything confusing, or if you find any limitations/problems with the code... I will adjust ValSubm and this documentation accordingly.

    Currently there isn't a fast way to handle error messages... although I plan to add in this ability soon. Additionally I plan to allow you to specify which parameter the value will be passed to. This way you don't have to make wrapper functions for things like preg_match() or str_replace().

    Happy validating!

  • #2
    Well there is an update.

    lease let me know if you find anything confusing, or if you find any limitations/problems with the code... I will adjust ValSubm and this documentation accordingly.

    Comment

    Working...
    X