8

只要我一直在开发 Web 应用程序,我就一直在寻找更好的方法来处理验证。经常需要捕获多个验证错误,所以我想知道是否有比以下更好的方法来做到这一点。

现在我assert在自己开发的框架中有一个方法。该方法的一个示例如下:

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

如果第一个参数中的条件为假,则将第二个参数中的错误消息添加到一个$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 语句似乎是解决问题的“蛮力”解决方案。

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

谢谢!

4

6 回答 6

6

请检查尊重\验证。这是一个为此目的而建的库。它可以非常轻松地处理多个规则并使用异常来处理错误。这是一个快速示例:

<?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 字符。对于每条未通过的规则,都会返回一条消息。由于“长度”是可以的,因此错误消息为空。

文档包括更多示例,包括嵌套、分层规则和更好的异常处理。还为所有 30 多个内置验证器提供了广泛的示例列表。

希望有帮助!

于 2012-05-28T17:06:33.650 回答
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方法,因为您可能需要进行一些测试以确保您正确处理“真实”或“虚假”值。我在考虑复选框类型的值。直接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摇滚:)

于 2012-05-19T02:57:36.540 回答
2

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

如果是这样,您可能想看看我的非常简单的验证类

<?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 数组。

于 2012-05-22T21:11:30.027 回答
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将包含带有字段和错误的关联数组。而且,每个字段只有一个错误,不要混淆用户。显然,您可以根据进行验证的环境仅使用数组或模型。

于 2012-05-26T08:43:54.953 回答
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. “字段验证器”类,用于验证字段的类型、长度、强制等。
  2. “表单验证器”类,用于验证特定的数据组合等。
  3. 控制表单动作的“表单”类。这也很有用,不同类型的表单类可以链接到数据库(类似于 VB/C# .Net)并从字段类型中提取字段验证,并具有标准的“编辑”、“添加”和“删除”功能。
  4. 控制字段动作的“字段”类。字段也可以链接到数据库,链接到其他字段等。

表单将使用完全相同的程序结构进行验证,除了它循环遍历字段对象(不是原始字段),并将异常吐回存储表单错误的循环(如 Darragh 所建议的)。有了类,它就更加结构化,更容易添加/编辑等。

对于没有框架悬垂的快速项目(一页表单),我只需使用您的代码进行特定验证。(这是个人选择——其他人会说即使是小型项目也应该始终使用该框架;这两个选项都是有效的,这里不讨论。有时我只使用中级选项。无论哪个适合项目。)

但无论如何——基本程序循环是相同的。您无需为此做任何事情,因为这是必需的。

于 2012-05-29T00:44:01.847 回答
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

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

注意:以上所有代码都可以直接复制和粘贴,给你一些实验的东西

于 2012-05-22T19:57:53.313 回答