我一直在寻找更好的方法来处理验证,只要我一直在开发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语句似乎是解决这个问题的“蛮力”解决方案。
请注意,我理解在类中包含一些常见的验证函数可能是个好主意,这样我就可以通过调用这些函数来检查长度,字符串格式等。我要问的是更清晰的代码结构方法,而不是我实际检查错误的方法。
谢谢!
答案 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_input
和filter_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
然后,您可以在以后显示/记录/记录所有错误。
注意:以上所有代码都可以直接复制和粘贴,以便为您提供实验
答案 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. }
为了从编码角度提高效率,您可以遵循几乎所有答案 - 添加“字段”类,并循环遍历这些类,或者验证条件数组并循环执行。您可以添加“验证器类”(但是您需要两种类型 - 一种类型附加到字段,一种类型附加到表单)并且您可以使用异常将您带回上面的循环 - 但是基本程序循环您'关心永远不会改变。
但要更恰当地回答我的工作方式(在较大的项目上)是:
Form将使用完全相同的过程结构进行验证,除了它遍历字段对象(不是原始字段),并将异常吐回到存储错误的循环(如Darragh所建议)。对于类,它更加结构化,更容易添加/编辑等。
对于没有框架悬空的快速项目(一个页面表单),我只使用具有特定验证的代码。 (这是个人选择 - 其他人会说你应该总是使用框架,即使是小项目;这两个选项都是有效的,而不是在这里进行讨论。有时我只是使用中级选项。无论哪个适合项目。)
但无论如何 - 基本程序循环都是一样的。你没有做任何事情,因为这就是所需要的。