【问题标题】:Where to validate users input? [closed]在哪里验证用户输入? [关闭]
【发布时间】:2016-03-25 11:31:27
【问题描述】:

我正在使用涉及一些 REST 的面向对象方法开发一个新应用程序,我没有使用任何框架。

我的问题是,在下面的 setter 中验证用户输入的最佳位置在哪里:

public function setSalary($salary)
{
    if (Validator::money($salary))
        $this->salary = $salary;
    else
        return 'Error that is an invalid number';
}

还是在控制器中?

public function updateSalary()
{
    $errors = array();

    if (Validator::money($_POST['salary']))
        $salary = $_POST['salary'];
        else
            $errors ['salary']  = 'Error that is an invalid number';

    if(count($errors))
        return $errors;

    $employee = new Employee($_POST['e_Id']);
    $employee->setSalary($salary);

    $employee->save();
}

如果我要放入 setter,我的控制器应该如何查看并返回验证错误?

我看到大多数人在控制器中进行验证,但是我认为应该由模型负责验证,因为它将使用数据,我们可以重用该模型而无需重复自己。 然而,有时验证规则可能需要在某些特殊情况下有所不同,例如针对不同视图的不同验证或针对超级管理员的不同验证。

您认为哪一项符合最佳做法?

【问题讨论】:

  • 事实上,数据来自用户输入,只是一个“细节”。您应该验证的是 Employee 实例处于有效状态。您可能还应该尝试捕获任何可能从持久层抛出的异常。
  • 必须在控制器中验证用户的输入(例如:无效的 id - 应为整数)。业务逻辑验证应该在服务/模型中完成(例如:没有这样的用户 ID)
  • @MateiMihai 这是完全错误的。控制器不负责数据验证。您验证的业务规则将如何最终出现在那个“控制器”中?
  • @Mjh 你说的“他”是谁?和 ID 的无效性取决于它的使用方式。 Employee 实例知道,ID 必须是数字,而不是“115-124-1555”。

标签: php validation oop


【解决方案1】:

首先,由于您似乎渴望实现类似 MVC 的结构,让我们从一些与验证没有直接关系的一般错误开始。

  • 只有包含 PHP 超全局变量的部分代码应该是引导阶段。将超全局变量散布在您的代码中会使测试变得非常困难。而且您的代码也通过<input> 名称与您的HTML 紧密耦合。

  • 即使您的 forif 语句包含一行,您也应该始终使用大括号。好吧,一般来说,您的代码应该遵循PSR-1PSR-2 准则。

  • 控制器不应该有任何逻辑,或者正在处理数据的保存。阅读this post,也许它会清除一些东西。

好的..现在回到原来的主题。

一般来说有两种思想流派:

  1. 您在域实体中进行验证

    您的域实体(在您的情况下为 Employee)包含与其相关的所有业务角色。它可以使用这些规则来评估它是否处于有效状态。

    代码会是这样的:

    $employee = new Entity\Employee;
    $employee->setID($id);
    $employee->setSalary($money);    
    if ($employee->isValid()) {
        $mapper = new Mapper\Employee($dbConn);
        $mapper->store($emplyee);
    }
    
  2. 您永远不会创建无效的域实体

    这种方法来自DDD,您的域实体是由其他一些类创建的,它只能从一个有效状态更改为另一个有效状态。本质上,如果您想探索这种方法,您将不得不阅读this book(可能好几遍)。

此外,还有另一种验证形式,前两者涵盖了注意事项:数据完整性检查。这是一种验证,实际上是由我的 RDBMS 完成的。例如,UNIQUE 约束。

当你遇到完整性违规时,它通常会抛出一个异常,你在服务层处理。

【讨论】:

  • 总是有帮助并指出解释:),但是引导阶段的全局包含什么??
  • 建议在向rdbms发送任何数据之前检查唯一性和数据类型。即使 rdbms 有自己的约束,应用程序也是应该处理它们的应用程序。想想不同的存储系统,如 mysql、mongodb、redis、memcache,它们在存储数据方面有不同的方法..
  • @MateiMihai 我真的不明白这会如何改变任何事情。数据映射器仍然会抛出异常。它是由手动检查还是由存储抽象(无论是 postrgres 还是 coutchdb)产生的东西触发并不重要。
【解决方案2】:

每次将数据写入数据库时​​都必须调用验证。所以在这种情况下来自控制器。实际验证发生在模型中。模型是对象,它知道它的字段遵循哪些规则,并且可以检查数据是否有效。此外,模型是世界其他地方与数据库之间的边界。所以,我会做这样的事情:

public function updateSalary()
{
    $employee = new Employee($_POST['e_Id']);
    $employee->setSalary($_POST['salary']));
    if ($employee->validate()) {
        $employee->save();             
    } else {
        return $employee->getErrors();
    }
}

为什么我会以这种方式提供给你:

  • 因为您将验证保存在一个地方。稍后,如果您想验证另一个字段,您将再次调用 validate() 方法。您不会为每个字段或类编写另一个验证;
  • 您可以创建一个基类并将 validate() 方法放在那里 - 所有客户端都会调用 validate() 方法,并且不会关心字段的细节。 validate 方法只关心要验证的内容——哪些字段和规则是什么。此信息将在特定(子)类中设置,例如 Employee 类。
  • 如果您只想验证一个字段(如您的情况),在 validate() 方法中,您可以简单检查哪些字段已更改并仅对这些字段进行验证。

【讨论】:

  • imo,我绝不允许model 看到“原始输入”($_POST 等)。它将看到一个“形式化对象”,即由“控制器”等“上游”创建的“数据传输对象”(DTO)。它可能是 $_POST 的副本,但它不知道。它还将返回一个可以传递的“DTO”。它将包含错误消息等。
  • 好的,我同意。我刚刚创建了一个简化版本来给出这个想法。我不得不提到这一点。感谢您的评论!
【解决方案3】:

取决于你,如果验证规则是“全局的”,换句话说,如果每次更新数据库表/对象属性时它们都相同,请将它们放在模型中,否则在控制器中验证用户输入不同的情况你需要对同一个Entity的不同的验证规则。

【讨论】:

  • 如果您有 3 个使用相同模型的接口怎么办?例如:soap 请求、rest 请求和 xmlrpc 请求。用户的输入验证意味着在向模型发送任何信息之前,您必须首先验证 soap、rest 或 xmlrpc 请求参数。模型必须使用相同的数据类型,并且必须进行业务逻辑验证
【解决方案4】:

首先,我不是极客,下面就是我的想法。

应该在控制器中完成,因为现在你只是验证数字,这只是简单的检查,我认为你只需要为此应用正则表达式。

我的实际理解是,模型是您保留业务逻辑的地方,但如果您的字段值全部错误,那么您将永远不会处理业务逻辑并且您不希望您的模型发挥作用。

【讨论】:

  • 在两个不同的地方进行相同的验证?有人改变了一个,但没有改变另一个?为什么? model(不是数据库模型)必须完成所有验证和记录状态。控制器未更新 - 未传递有效数据?该模型始终使用最新规则进行更新。其他任何事情都会导致错误。
【解决方案5】:

我建议尽可能在模型中应用验证。优点是可以更完整的方式直接测试Model,保证Model只持久化有效数据。

当然,控制器需要处理验证,当涉及分布式项目的复杂验证时,它可能是调用验证的第一层。但是在您给出的示例中,没有这样的复杂性。

请注意,无论如何,数据库引擎甚至会执行一些验证(例如NOT NULL 和主键要求)。

我还建议在模型中使用异常,因为这保证了正在运行的函数的中断,并允许您在控制器中以类似的方式处理所有(验证)错误。我建议配置您的数据库访问层以触发异常。在 PDO 的情况下,您可以按以下方式进行操作:

$dbh = new PDO($dsn, $user, $password);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

在模型中,当验证失败时会抛出异常:

public function setSalary($salary) {
    if (!Validator::money($salary)) {
        throw new Exception('Invalid value provided as salary.');
    }
    $this->salary = $salary;
}

在 Controller 中,您将捕获错误并记录它们:就像您在 $errors 中所做的那样,但我会将它们保留在 Model 中,以供 View 以后访问。这说明了模型如何检测验证错误,但控制器会处理它。

我还建议不要直接创建一个 Employee 实例,而是让模型为你做这件事:

public function updateSalary($emp_id, $salary) {
    try {    
        // Note that any of the following statements could trigger exceptions:
        $employee = $this->$model->getEmployee($emp_id);
        $employee->setSalary($salary);
        $employee->save();
    } catch(Exception $e) {
        $this->$model->logError('salary', $e->getMessage());
    }
}

使用发布的参数调用后一个函数,因为这可以更好地指示该方法使用什么作为输入。顶级 PHP 代码如下所示:

$model = new Model();
$controller = new Controller($model);
$view = new View($controller, $model);

$controller->updateSalary($_POST['e_Id'], $_POST['salary']);

echo $view->output();

视图将访问记录的错误以将它们报告给客户端。

我意识到关于在哪里检测验证错误、在哪里处理它们、何时触发异常(以及何时不触发异常)等等的争论永远不会结束。但这对我有用。

【讨论】:

  • imo,任何异常都是失败的,除非它是可以处理的“有效异常”。这些是罕见的。通常,唯一要做的就是报告他们。而且,可悲的是,imo,在开发和测试中采取的最善意的行动是杀死进程;-/至少有人会看它?在生产中,它们会升级。所以,imo,不要捕获异常。在生产中,有一个 error handler 报告它们 - 总是。
  • 我同意。实现(生产)错误处理程序的地方在 Controller 中,这就是我的建议。报告它们的地方可以是:以不泄露敏感信息的方式向用户(视图)和日志中,这将在我建议的logError 方法中完成。这甚至可能会触发给管理员的电子邮件等。在大多数情况下,控制器是“决定”异常严重性以及如何处理异常的人。
  • 也许反对者可以解释问题所在?
  • 像你自己一样 - 我不明白“downvote”。和你一样,我真希望他们能解释为什么他们没有这么强烈地同意;-/ 至少我们可以了解他们的观点。
  • -1 的原因:可能有害的 PDO 初始化,$this->model->getEmployee() 到底做了什么(它是一个服务定位器吗?),使用活动记录模式,说控制器处理日志记录......哦,还有“模型”不是一个类(尤其不是泛型类)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-27
  • 1970-01-01
  • 2020-09-29
  • 1970-01-01
  • 2014-03-06
  • 2011-04-23
相关资源
最近更新 更多