使用PHP处理验证错误的最简洁方法是什么?

时间:2012-05-19 02:53:24

标签: php validation error-handling

我一直在寻找更好的方法来处理验证,只要我一直在开发Web应用程序。通常需要捕获多个验证错误,因此我想知道是否有更好的方法来执行此操作。

现在我在自己开发的框架中有一个assert方法。方法的一个例子是:

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

如果第一个参数中的条件为false,则第二个参数中的错误消息将添加到$errors数组(包含在类中(由下面的$eh引用)中,以提供方便hasErrors())等函数。

这种方法有效但在实践中很麻烦。请考虑以下代码:

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();
}

这是相当普遍的事情。我想在继续之前检查两个条件,然后如果这些条件通过,请在最终做我想做的事情之前检查第三个条件。如您所见,此代码中的大多数行都与验证有关。在实际应用程序中,将会有更多验证,并且可能会有更多嵌套的if语句。

有没有人有更好的处理验证结构呢?如果有更优雅地处理这个问题的框架,那么它们是什么以及它们如何实现呢?多个嵌套的if语句似乎是解决这个问题的“蛮力”解决方案。

请注意,我理解在类中包含一些常见的验证函数可能是个好主意,这样我就可以通过调用这些函数来检查长度,字符串格式等。我要问的是更清晰的代码结构方法,而不是我实际检查错误的方法。

谢谢!

6 个答案:

答案 0 :(得分:6)

请检查Respect\Validation。这是一个为那个purpouse而建的图书馆。它可以非常轻松地处理多个规则并使用异常来处理错误。这是一个快速示例:

<?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

现在使用例外:

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

在上面的示例中,$errors变量将是这样的:

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

我使用“foo #bar”打破了两个先前声明的规则:它有空格,并且它有一个非alnum char。对于每个未通过的规则,将返回一条消息。由于“length”正常,因此错误消息为空。

documentation包含更多样本,包括嵌套的分层规则和更好的异常处理。还有30多个内置验证器的广泛样本列表。

希望有所帮助!

答案 1 :(得分:4)

如何抛出异常?您可以使用try / catch块捕获异常明确,和/或使用set_exception_handler()

捕获它们

PHP中定义了许多有用的异常类型,如果您需要在异常处理中使用粒度,可以使用它们。另外,您可以定义自定义例外。

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

修改

回答关于其他一些框架如何解决这个问题的问题 - 明智地使用异常似乎很常见。使用它们的有用之处在于,假设您有一个特定的方法可以执行许多可能错误的不同验证 - 您可以在每种情况下抛出适当的异常,但您不必处理不同的可能异常。那种方法。相反,根据您构建代码的方式,您可以允许异常冒泡到代码中更集中的位置,您可以捕获它并适当地处理它。

编辑2

详细说明我对filter_input_array()

的最新评论

基于POSTed用户数据的一个非常简单的示例。首先创建一个定义:

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

然后使用通用验证类(下面的类定义):

$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.
}

FormValidator的一个非常基本的(和未经测试的)实现。

基本用例是为请求方法调用适当的方法进行过滤。这反过来检查返回的值并确定输入是否有效。

这可能会使用很多爱 - 尤其是 filterInput方法,因为您可能需要进行一些测试以确保适当地处理'truthy'或'falsy'值。我在考虑复选框类型值。对in_array进行直接false检查可能不会像在此处执行的那样将其删除。但是有很多标志可以通过定义传递。

我猜你也可以通过计算生成的$values数组和定义的计数来检查缺失的输入,以确保它们匹配。不在定义中的其他输入被过滤掉了(您可能想要检查一下,但我有理由相信这一点。)

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

无论如何,我会说重构和测试bejayzis从那个类中,(甚至完全改变它)但希望它概述了基本思想:对于每种类型的输入,创建一个定义,然后使用通用验证类过滤并确保有效性。

希望这会有所帮助。 filter_inputfilter_input_array摇滚:)

答案 2 :(得分:2)

当您说“验证”时 - 我假设您在执行操作之前验证用户输入。我经常在使用jQuery通过AJAX提交数据时或者当我从网络服务响应时使用它。

如果是这样,您可能需要查看我的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

您可以使用它来验证任何数据 - 只要它是数组形式。不只是$ _POST / $ _ GET数组。

答案 3 :(得分:1)

我们已经创建并使用了许多不同的框架。表单处理通常是创建Web应用程序的重要部分。所以,为了回答你关于错误处理的问题,我建议更广泛地看待这个问题。

显然,对于要验证的任何内容,您需要输入数据的某种类型和输入数据的定义。接下来,您是拥有一个表单还是计划对多个表单进行集中验证。如果,那么,创建公共验证器对象是有意义的。

class validator {}

好吧,那么,为了使验证器工作得很好,它必须知道要验证什么以及如何验证。所以,在这里我们回过头来讨论如何创建表单 - 是那些基于模型的动态,还是简单的html。如果表单基于模型,则通常会定义所有字段,并且通常大多数验证规则已存在于模型级别上。在这种情况下,教你的验证器从模型中学习字段是有意义的。

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

或者,如果你不使用模型和表单是纯HTML,那么简单的字段和验证器数组最有意义:

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

上述方法允许您定义验证器(一个或多个),每个验证器可能包含额外的参数。在这种情况下,您的验证器将如下所示:

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;
}

很酷的是,您可以通过以下方式将下一个验证器作为新方法添加到验证器类中:

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";
    }   
}

因此,使用这种方法,您将拥有可以轻松扩展的验证器类,您可以为验证器提供多个参数,验证器可以使用正则表达式/过滤器或任何其他验证字段的方法。最后,$this->errors array将包含带有字段和错误的关联数组。而且,每个字段只有一个错误,不要混淆用户。显然,你可以根据验证环境使用数组或模型。

答案 4 :(得分:0)

下面我写了一个示例,向您展示如何一般地使用异常(非特定于您的情况),并进一步了解您更具体的内容(仍使用异常)。前两个示例将一次处理1个错误。我提供的第三个示例给出了如何处理多个错误和异常的示例。

大多数解释都在代码的注释中,因此请务必仔细查看:)

常规异常处理

<?php
//Define some variables to work with
$var  = false;
$var2 = false;

try { //Outer try
    echo 'Do something here!<br />';

    try { //Inner try
        if($var !== true) { //Fail
            throw new Exception('$var is not true',123); //Exception is thrown (caught 2 lines down)
        }
    } catch (Exception $e) { //Exception caught here
        echo 'InnerError# '.$e->getCode().': '.$e->getMessage().'<br />'; //Exception handled (in this case printed to screen)
    }

    //Code is continuing here even after the exception was thrown
    echo 'Do something else here!<br />';

    if($var2 !== true) { //Fail
        throw new Exception('$var2 is not true', 456); //Exception is thrown (caught 6 lines down)
    }

    //Code fails to run as the exception above has been thrown and jumps straight into the below catch
    echo 'Do the third thing here!<br />';

} catch (Exception $e) { //Exception caught here
    echo 'Error # '.$e->getCode().': '.$e->getMessage().' on line '.$e->getLine().' in '.$e->getFile().'<br />'; //Exception handled (in this case printed to screen)
}

//Code is continuting here even after both of the exceptions
echo 'Do even more stuff here!<br />';
?>

标准异常类构造函数:

public __construct ([ string $message = "" [, int $code = 0 [, Exception $previous = NULL ]]] )

自定义例外

现在,将此与您的示例相关联,您可以按照以下方式执行操作:

<?php
class customException extends Exception { //Create a custom exception handler that allows you to pass more arguments in the constructor             
    public function __construct($errorString, $errorNumber, $errorFile, $errorLine) {
        $this->message = $errorString; //Using the Exception class to store our information
        $this->code = $errorNumber;
        $this->file = $errorFile;
        $this->line = $errorLine;
    }
}

function err2Exception($errNo, $errStr, $errFile, $errLine) {  //This function converts the error into an exception
    throw new customException($errStr, $errNo, $errFile, $errLine); //Throw the customException
}

set_error_handler('err2Exception'); //Set the error handler to the above function

try {
    assert(1==2); //This fails, calls the function err2Exception with the correct arguments, throws the error and is caught below
} catch (Exception $e) { //Error caught as an Exception here
    //Echo out the details (or log them, or whatever you want to do with them)
    echo 'Error String: '.$e->getMessage().'<br />';
    echo 'Error Number: '.$e->getCode().'<br />';
    echo 'File containing error: '.$e->getFile().'<br />';
    echo 'Line with error: '.$e->getLine().'<br />';

}
?>

http://php.net/manual/en/function.set-error-handler.php

输出上述代码:

  

错误字符串:assert():断言失败

     

错误号码:2

     

包含错误的文件:18

     

有错误的行:/var/www/test2.php

您可以在第一个代码示例中应用嵌套try / catch语句的概念,以及第二个自定义错误处理示例。

处理多个错误/例外

<?php
class errorLogger  { //create an errorLogger class
    private $errors; //Stores all errors

    public function addError($errCode, $errMsg, $errFile = null, $errLine = null) { //Manually add an error
        $this->errors[] = array( //Add to the error list
            'code' => $errCode,
            'message' => $errMsg,
            'file' => $errFile,
            'line' => $errLine
        );
    }

    public function addException($exception) { //Add an exception to the error list
        $this->errors[] = array( //Add to the error list
            'code' => $exception->getCode(),
            'message' => $exception->getMessage(),
            'file' => $exception->getFile(),
            'line' => $exception->getLine()
        );
    }

    public function getErrors() { //Return all of the errors
        return $this->errors;
    }

    public function numErrors() { //Return the number of errors
        return count($this->errors);
    }
}

$el = new errorLogger(); //New errorLogger
set_error_handler(array($el, 'addError')); //Set the default error handler as our errorLoggers addError method
set_exception_handler(array($el, 'addException')); //Set the default exception handler as our errorLoggers addException method

if(!is_numeric('a')) //Will fail
    $el->addError('Invalid number', 1); //Adds a new error

if(($name = 'Dave') !== 'Fred') //Will fail
    $el->addError('Invalid name ('.$name.')', 2, 'test.php', 40); //Adds another error

assert(1==2); //Something random that fails (non fatal) also adds to the errorLogger

try {
    if('Cats' !== 'Dogs') //Will fail
        throw new Exception('Cats are not Dogs', 14); //Throws an exception
} catch (Exception $ex) { //Exception caught
    $el->addException($ex); //Adds exception to the errorLogger
}

trigger_error('Big bad wolf blew the house down!'); //Manually trigger an error

//throw new Exception('Random exception', 123); //Throw an exception that isn't caught by any try/catch statement
                                                //(this is also added to the errorLogger, but any code under this is not run if it is uncommented as it isn't in a try/catch block)

//Prints out some 
echo '<pre>'.PHP_EOL;
echo 'There are '.$el->numErrors().' errors:'.PHP_EOL; //Get the number of errors

print_r($el->getErrors());

echo '</pre>'.PHP_EOL;
?>

显然,您可以根据自己的需要更改和调整errorLogger课程。

输出上述代码:

  

有5个错误:

     

数组(

[0] => Array
    (
        [code] => Invalid number
        [message] => 1
        [file] => 
        [line] => 
    )

[1] => Array
    (
        [code] => Invalid name (Dave)
        [message] => 2
        [file] => test.php
        [line] => 10
    )

[2] => Array
    (
        [code] => 2
        [message] => assert(): Assertion failed
        [file] => /var/www/test.php
        [line] => 42
    )

[3] => Array
    (
        [code] => 14
        [message] => Cats are not Dogs
        [file] => /var/www/test.php
        [line] => 46
    )

[4] => Array
    (
        [code] => 1024
        [message] => Big bad wolf blew the house down!
        [file] => /var/www/test.php
        [line] => 51
    )
     

以上代码允许您:

  • 抛出异常并将其添加到errorLogger
  • 处理通常会导致错误显示的随机函数的任何未处理情况
  • 手动添加您自己的错误
  • 触发错误(http://uk3.php.net/trigger_error

然后,您可以在以后显示/记录/记录所有错误。

注意:以上所有代码都可以直接复制和粘贴,以便为您提供实验

答案 5 :(得分:0)

为了帮助您放松,无论您做什么,您最终都会得到与您描述的相同的基本程序循环。你可以略微删除嵌套(见下文),但不是很多。

对于验证,您需要一个程序流程,这就是您所拥有的。可能存在微妙的变化(例如,即使某些其他字段错误,您也可以使用组合验证器),但这是程序流程。

1.  Loop through all fields, and validate them; store errors if any
2.  If (no errors) {
3.      loop though all multiple combinations and validate them, store errors if any
4.  }
5.  If (no errors) {
6.     do action, store errors if any
7.  }
8.  If (no errors) {
9.     Report back success
10. } else {
11.    Report back problems
12. }

为了从编码角度提高效率,您可以遵循几乎所有答案 - 添加“字段”类,并循环遍历这些类,或者验证条件数组并循环执行。您可以添加“验证器类”(但是您需要两种类型 - 一种类型附加到字段,一种类型附加到表单)并且您可以使用异常将您带回上面的循环 - 但是基本程序循环您'关心永远不会改变。


但要更恰当地回答我的工作方式(在较大的项目上)是:

  1. “Field Validator”类,用于验证字段类型,长度,强制等。
  2. “表单验证程序”类,用于验证特定的数据组合等。
  3. “表单”类,用于控制表单的操作。这也是有用的,不同类型的表单类可以链接到数据库(类似于VB / C#.Net)并从字段类型中提取字段验证,并具有标准的“编辑”,“添加”和“删除”功能。 / LI>
  4. “字段”类,用于控制字段的操作。字段也可以链接到DB,链接到其他字段等。
  5. Form将使用完全相同的过程结构进行验证,除了它遍历字段对象(不是原始字段),并将异常吐回到存储错误的循环(如Darragh所建议)。对于类,它更加结构化,更容易添加/编辑等。

    对于没有框架悬空的快速项目(一个页面表单),我只使用具有特定验证的代码。 (这是个人选择 - 其他人会说你应该总是使用框架,即使是小项目;这两个选项都是有效的,而不是在这里进行讨论。有时我只是使用中级选项。无论哪个适合项目。)

    但无论如何 - 基本程序循环都是一样的。你没有做任何事情,因为这就是所需要的。