验证用户输入的位置


Where to validate users input?

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

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

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,我的控制器应该是什么样子,并返回验证错误?

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

你认为哪一个符合最佳实践?

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

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

  • 即使forif语句包含一行,也应该始终使用花括号。一般来说,您的代码应该遵循PSR-1和PSR-2指南。

  • 控制器不应该有任何逻辑,也不应该处理数据的保存。读一下这篇文章,也许它能澄清一些问题。

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

一般来说,有两个学派:

  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,在DDD中,域实体由其他类创建,并且只能从一个有效状态更改为另一个有效的状态。从本质上讲,如果你想探索这种方法,你必须读这本书(可能读几遍)。

此外,还有另一个验证表单,前两个表单中都提到了它:数据完整性检查。这是一种验证,实际上是在我的RDBMS中完成的。例如,UNIQUE约束。

当您遇到完整性冲突时,它通常会抛出一个异常,由您在服务层中处理。

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

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()方法中,您可以简单地检查哪些字段发生了更改,并只验证这些字段

取决于您,如果验证规则是"全局"的,换句话说,如果每次更新DB表/对象的适当性时它们都是相同的,请将它们放在模型中,否则,如果在不同情况下,您需要对同一实体使用不同的验证规则,请在控制器中验证用户输入。

首先,我不是一个极客。

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

实际上,我所理解的是,模型是你保持业务逻辑的地方,但如果你的领域价值都是错误的,那么你永远不会处理业务逻辑,你不希望你的模型发挥作用。

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

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

请注意,无论如何,数据库引擎甚至会执行一些验证(例如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;
}

在控制器中,你会捕捉错误并将其记录下来:就像你在$errors中所做的那样,但我也会将它们保存在模型中,以便视图稍后访问。这说明了模型如何检测验证错误,但控制器如何处理它

我还建议不要直接创建Employee实例,而是让Model为您创建:

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

视图将访问记录的错误,并将其报告给客户端。

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