Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the cleanest way to handle validation errors with PHP?

I have looked for better ways to handle validation for as long as I've been developing web applications. Catching multiple validation errors is frequently necessary, so I wanted to know if there was a better way to do it than the following.

Right now I have an assert method in a framework I've developed myself. An example of the method is this:

assert(($foo == 1), 'Foo is not equal to 1');

If the condition in the first argument is false, the error message in the second argument is added to an $errors array (which is wrapped in a class (referenced by $eh below) that provides convenience functions such as hasErrors()).

This method works but is messy in practice. Consider this code:

public function submit($foo, $bar, $baz)
{
    assert(($foo == 1), 'Foo is not equal to 1');
    assert(($bar == 2), 'Bar is not equal to 2');

    if (!$eh->hasErrors())
    {
        assert(($baz == 3), 'Baz is not equal to 3');

        if (!$eh->hasErrors())
        {
            finallyDoSomething();
            return;
        }
    }

    outputErrors();
}

This is something fairly common. I want to check two conditions before moving on, and then if those pass, check a third condition before finally doing what I want to do. As you can see, most of the lines in this code are related to validation. In a real application, there will be more validation and possibly more nested if statements.

Does anyone have a better structure for handling validation than this? If there are frameworks that handle this more elegantly, what are they and how do they accomplish it? Multiple nested if statements seem like such a 'brute-force' solution to the problem.

Just a note, I understand it would probably be a good idea to wrap some common validation functions in a class so that I can check length, string format, etc., by calling those functions. What I am asking is a cleaner approach to the code structure, not how I am actually checking the errors.

Thank you!

like image 256
Logan Serman Avatar asked May 19 '12 02:05

Logan Serman


4 Answers

Please check Respect\Validation. It's a library built for that purpouse. It can handle multiple rules very easily and uses exceptions for errors. Here is a quick sample:

<?php

use Respect\Validation\Validator as v;

$usernameValidator = v::alnum()->noWhitespace()->length(1,15);

$valid = $usernameValidator->validate("alganet"); //$valid now == true
$valid = $usernameValidator->validate("ácido acético"); //$valid now == false

Now using exceptions:

try {
    $usernameValidator->assert("foo # bar");
} catch (Exception $e) {
    $errors = $e->findMessages('alnum', 'noWhitespace', 'length');
}

In the sample above, the $errors variable would be something like this:

array(
    "alnum" => '"foo # bar" must contain only letters and digits',
    "noWhitespace" => '"foo # bar" must not contain whitespace',
    "length" => null
)

I broke two previously declared rules using "foo # bar": It has whitespace and it has a non-alnum char. For every rule that doesn't pass, a message will be returned. Since "length" is OK, the error message is null.

The documentation includes several more samples including nested, hierarchical rules and better exception handling. Also has a extensive list of samples for all of the 30+ built in validators.

Hope that helps!

like image 181
alganet Avatar answered Oct 04 '22 13:10

alganet


How about throwing exceptions? you can catch exceptions explicity with try /catch blocks, and/or catch them using set_exception_handler()

there are a number of useful exception types defined in PHP, which you can use to your advantage if you need granularity in exeption handling. plus you can define custom exceptions.

http://php.net/manual/en/function.set-exception-handler.php http://www.php.net/manual/en/spl.exceptions.php

EDIT

To answer your question about how some other frameworks approach this problem - judicious use of exceptions seems pretty common. The useful thing about using them is, say you have a particular method that does a number of different validations that might possibly be erroneous - you can throw an appopriate exception in each case, but you don't have to handle the different possible exceptions in that method. instead, depending on how you structure your code, you can allow the exception to bubble up to a more centralised place in your code where you can catch it and handle it appropriately.

EDIT 2

To elaborate on my last comment about filter_input_array()

Based on a really simple example with POSTed user data. First create a definition:

$userFormDefinition = array(
    'email' => FILTER_VALIDATE_EMAIL,
    'age'   => FILTER_VALIDATE_INT,
    'name'  => array(
        'filter'  => FILTER_VALIDATE_REGEXP, 
        'options' => array('regexp' => '/^\w+$/')
    ),
);

Then using a generic validation class (class definition below):

$formValidator = new FormValidator();
$formValidator->validatePost($userFormDefinition);

if ($formValidator->isValid()) {

    // if valid, retrieve the array
    // and use the values how you wish

    $values = $formValidator->getValues();

    // for example, extract and populate
    // a User object, or whatever :)

    extract($values);

    $user = new User();
    $user->setName($name);
    $user->setEmail($email);
    $user->setAge($age);

    // etc.
}

A very basic (and untested) implementation of a FormValidator.

The basic use case is to call the appropriate method for the request method to filter against. This in turn checks the returned values and decides if the input is valid.

This could use a lot of love - especially the filterInput method, because you might have to do some testing to make sure you handle 'truthy' or 'falsy' values appropriately. I'm thinking checkbox type values. A straight up in_array check for false might not cut it as implemented here. But there are loads of flags that you can pass in with the definition.

I guess you could also check for missing inputs by comapring a count of the resulting $values array and the definition, to make sure they match. Additional inputs not in the definition are filtered out (you might want to check that but I'm reasonably sure about this off the top of my head).

<?php

class FormValidator
{   
    private $isValid = false;

    private $isBound = false;

    private $values  = array();

    public function validatePost(array $definition)
    {
        // additional REQUEST_METHOD checking here?
        $this->filter(INPUT_POST, $definition);
    }

    public function validateGet(array $definition)
    {
        // additional REQUEST_METHOD checking here?
        $this->filterInput(INPUT_GET, $definition);
    }

    protected function filterInput($type, $definition)
    {
        $this->isBound = true;

        $this->values = filter_input_array($type, $definition);

        // might have to do some reading on nulls vs false, 
        // and validating checkbox type values here... you can
        // set all sorts of flags so a better implementation
        // would probably be required here... :s

        if (is_array($this->values) && !in_array(false, $this->values))) {
            $this->isValid = true;
        }   
    }

    public function isValid()
    {
        if (!$this->isBound) {
            throw new Exception("you didn't validate yet!");
        }

        return $this->isValid;
    }

    public function getValues()
    {
        if (!$this->isBound) {
            throw new Exception("You didn't validate yet!");
        }

        return $this->values;
    }
}

Anyway, I would say refactor and test the bejayzis out of that class, (or even totally change it) but hopefully it outlines the basic idea: for each type of input, create a definition and then use a generic validation class to filter and ensure validity.

Hope this helps. filter_input and filter_input_array rock :)

like image 24
Darragh Enright Avatar answered Oct 04 '22 13:10

Darragh Enright


When you say "validation" - I am assuming that you are validating user input before you do an action. I often use this when submitting data with AJAX by jQuery or when I am responding from a web-service.

If so, you might want to look at my very simple validation class.

<?php

$validator = new Validator();

// Each validation rule is a property of the validator object.
$validator->username = function($value, $key, $self)
{
    if(preg_match('~\W~', $value))
    {
        return 'Your username must only contain letters and numbers';
    }
};

$validator->password = function($value, $key, $self)
{
    if(strlen($value) < 8)
    {
        return 'Your password must be at least 8 characters long';
    }
};

if( ! $validator($_POST))
{
    die(json_encode($validator->errors()));
}

// ... register user now

You can use it to validate any data - as long as it's in array form. Not just $_POST/$_GET arrays.

like image 35
Xeoncross Avatar answered Oct 04 '22 12:10

Xeoncross


We have created and used number of different frameworks. Form handling is usually an essential part of the creation of web applications. So, to answer your question about error handling, I would suggest looking at the question more widely.

Clearly, for anything to be validated, you need input data some sort of and definition of the input data. Next, are you having one form or do you plan to have centralized validation for more than one form. If, so, creating common validator object makes sense.

class validator {}

Okay, so, for validator to work nicely, it must know what to validate and how. So, here we step back to question of how you create forms - are those dynamic, based on Models, or are they plain html. If form is based on Model, it usually has all fields defined and usually most validation rules are present already on the model level. In that case, it makes sense to teach your validator to learn fields from model e.g.

function setModel($model){}
function getFields(){ -- iterates through $model fields}

alternatively, if you don't use models and form is plain html, the simple array of fields and validators makes most sense:

$fields = array(
    "name" => array("notNull"),
    "age" => array("notNull", array("size", 13, 99))
);

above approach lets you defined validators (one or more), and each validator may contain extra params. in this case, your validator would look like:

function validate($data, $fields){
    $this->valid = true;
    $this->data = $data;
    foreach ($fields as $field_name => $validators){
        foreach ($validators as $v){
            $params = array($field_name, isset($data[$field_name])?$data[$field_name]:null);
            if (is_array($v)){
                $m = "_" . $v[0];
                unset($v[0]);
                $params = array_merge($params, $v);
            } else {
                $m = "_" . $v;
            }
            if (method_exists($this, $m)){
                call_user_func_array(array($this, $m), $params);
            }
        }
    }
    if (!empty($this->errors)){
        $this->valid = false;
    }
    return $this->valid;
}

cool thing is that you can add your next validators as new methods to the validator class in following way:

function _notNull($field_name, $value){
    if (!$value){
        $this->errors[$field_name][] = "Must be filled";
    }   
}

function _size($field_name, $value, $min = null, $max = null){
    if ($value < $min && $min){
        $this->errors[$field_name][] = "Must be at least $min";
    } else if ($value > $max && $max){
        $this->errors[$field_name][] = "Must be at most $max";
    }   
}

so, then, using this approach you would have validator class which can be easily extended, you can have multiple parameters to the validators, and validators may use regex/filter or any other method of validating the fields. finally, $this->errors array will contain associative array with fields and the errors. Moreover, only one error per field, not to confuse the user. and obviously you can use just array or model based on the environment in which the validation will take place.

like image 38
jancha Avatar answered Oct 04 '22 12:10

jancha