应验证对象的位置


Where object should be validated?

我的问题是,我不知道用于验证输入的解决方案是更好的,如果有其他更好的解决方案。那个么,对象应该在哪里验证呢?一方面,对象应该始终是正确的。另一方面,如果用户指定了几个不正确的数据,那么通知他所有错误是一个更优雅的解决方案,而不仅仅是一个错误(第一次发生)。

// Solution 1:
try {
    $user = new User();
    $user->setFirstname($_POST['firstname']);
    $user->setSecondname($_POST['secondname']);
    $user->setLastname($_POST['lastname']);
    $user->hasLeftHand($_POST['has-left-hand']);
    $user->hasRightHand($_POST['has-right-hand']);
    $user->setHandedness($_POST['handedness']);
    $user->save($pdo);
} catch (Exception $e) {
    echo $e->getMessage();
}
// Solution 2:
$user = new User();
$user->setFirstname($_POST['firstname']);
$user->setSecondname($_POST['secondname']);
$user->setLastname($_POST['lastname']);
$user->hasLeftHand($_POST['has-left-hand']);
$user->hasRightHand($_POST['has-right-hand']);
$user->setHandedness($_POST['handedness']);
$errors = $user->validate();
if (empty($errors)) {
    $user->save($pdo);
} else {
    echo 'Some errors occured: ' . implode(', ', $errors);
}
// Solution 3:
try {
    $user = new User();
    $user->setFirstname($_POST['firstname']);
    $user->setSecondname($_POST['secondname']);
    $user->setLastname($_POST['lastname']);
    $user->hasLeftHand($_POST['has-left-hand']);
    $user->hasRightHand($_POST['has-right-hand']);
    $user->setHandedness($_POST['handedness']);
    $user->save($pdo);
} catch (Exception $e) {
    $errors = $user->validate();
    echo 'Some errors occured: ' . implode(', ', $errors);
}

解决方案1中,每个集合方法都验证输入。因此,对象总是正确的。save方法仅将对象保存在数据库中。另一方面,如果所有数据都不正确,则只显示第一个错误。

解决方案2中,我们允许对象在集合调用之间不正确,但只能保存到数据库的有效对象set方法不会验证输入validate方法将对象作为一个整体进行验证,并返回找到的所有错误的列表save方法如下:

public function save(PDO $pdo)
{
    if(! empty($this->validate())) {
        throw new Exception('Invalid state');
    }
    // Store in database
}

在这个解决方案中,更容易验证对象。Becuse,在解决方案1中应该如何处理下面的代码?

$user->hasLeftHand(true);
$user->hasRightHand(false);
$user->setHandedness('right');

或者这个代码:

$user->setHandedness('right');
$user->hasLeftHand(true);
$user->hasRightHand(false);

解决方案3方案2的副本。User类的代码相同。仅更改其use-try-catch块。在我看来,这个代码看起来更清晰。

输入的验证应该与域对象本身的正确性验证分开。许多框架为此使用Form类。也许可以看看它是如何在中完成的

  • Symfony
  • Zend框架(表单输入过滤器)

简而言之,表单将验证输入,并在数据有效的情况下填充绑定对象。

免责声明:这个问题是基于观点的,没有一个正确的答案。。。但我会如实重述我们在评论中讨论过的内容,以及我认为大多数人是如何解决的。

将输入验证与模型分离

<?php
// class for input validation
class UserValidator
{
    public function validate(array $data)
    {
        $errors = array();
        if (isset($data['email'])) {
            if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
                $errors['email'] = 'email not valid';
            }
        } else {
            $errors['email'] = 'email is required';
        }
    }
}
// model class itself, does not implement extensive validation
class User
{
    protected $email;
    public function setEmail($email)
    {
        // only make sure we have a valid string, dont validate email again
        if (!is_string($email) || !strlen($email)) throw new 'InvalidArgumentException('invalid email given');
        $this->email = $email;
        return $this;
    }
}

因此,此示例将输入验证分离,以便在处理表单数据时可以轻松地提供用户反馈。模型本身只执行基本检查,并假设开发人员足够聪明,可以设置合理的数据。。。

<?php
// where you process a form POST...
$validator = new UserValidator();
$errors = $validator->validate($_POST);
if (count($errors)) {
    // provide feedback to your user, he gave us bogus data...
    return $errors;
}
// if we are here, we passed validation and can assume our data is good
$user = new User();
$user->setEmail($_POST['email']);

这是一个非常简化的例子,你应该再次看看主要的框架是如何解决这个问题的,他们已经让很多人认真思考了。。。正如Zend和Symfony之间的差异所强调的那样:这是没有金锤的。