验证应该在哪里进行?

Posted

技术标签:

【中文标题】验证应该在哪里进行?【英文标题】:Where should validation take place? 【发布时间】:2014-06-14 16:40:55 【问题描述】:

我想在我的 php 项目中使用 MVC 模式。我还想为模型层使用 Dao-Service 模式,因为它使数据库引擎易于互换,并使业务逻辑远离数据库交互。

现在,I heard 验证应该发生在模型层,因为控制器只负责传输数据。这很合理。

但是,它应该在服务层还是实体本身中实现?

方法 1:实体中的验证

class Post extends Entity

    protected $title;

    public function getTitle()
    
        return $this->title;
    

    public function setTitle($newTitle)
    
        if (strlen($newTitle) == 0)
            throw new ValidationException('Title cannot be empty.');
        $this->title = $newTitle;
    


class PostService

    public static function saveOrUpdate(Post $post)
    
        PostDao::saveOrUpdate($post);
    

优点:

如果我做错了什么,我会立即知道, 一切都在一个地方,这似乎是一件好事。

缺点:

由于花哨的 setter 和 getter 导致大量样板代码, 向实体添加一些业务逻辑,这似乎是一件坏事(尤其是在验证非常复杂的情况下 - 例如需要查询数据库/其他服务), 序列化和反序列化可能会变得困难。

方法 2:服务中的验证

class Post extends Entity

    public $title;


class PostService

    public static function saveOrUpdate(Post $post)
    
        if (strlen($post->title) == 0)
            throw new ValidationException('Title cannot be empty.');
        PostDao::saveOrUpdate($post);
    

优点:

保持业务逻辑服务,这似乎是件好事, 样板文件保持在最低限度。

缺点:

我不知道什么时候出了问题,什么实际上出了问题。 我无法保证在将其保存到数据库之前确实会进行验证。一个例子:我必须在两个例程中保存帖子,忘记在其中一个例程中使用PostService::saveOrUpdate 代理并直接通过PostDao::saveOrUpdate 进行。噗,验证不会在该例程中进行,项目现在唯一的希望是单元测试或我自己在代码中发现它。因此,在这方面代码更难维护。

你有什么提示吗?我错过了什么吗?到目前为止,该项目还在绘图板上,所以我已经为任何事情做好了准备。

【问题讨论】:

【参考方案1】:

注意:控制器负责“传输数据”。控制器的职责是改变模型(在特殊情况下——当前视图实例)的状态。

实际上还有第三种方法:为domain object 提供一个单独的isValid() 方法(您称它们为“实体”)。当您有跨多个数据条目的验证规则时,setter 验证变得混乱。

示例:验证用户注册表单的重复密码。

在 setter 中对此进行验证会变得非常混乱。特别是如果您选择为每个失败的验证使用异常。

另外,我建议您使用data mappers,而不是data access objects。代码基本上看起来像这样:

$post = new Model\Domain\Post;
$mapper = new Model\Mappers\Post($pdo);

$post->setId(42);
$mapper->fetch($post);

$post->setTitle($title);
if ($post->isValid()) 
    $mapper->store($post);

如果您想重用某些验证规则,此方法还允许您通过构造函数在Model\Domain\Post 实例中注入某种Validator 实例来外部化验证。

不过,在制作更大的应用程序时,您可能会注意到,重复检查很少,超出现有的php filters。

注意:请不要使用静态类。这会迫使您在不需要的地方使用程序范式。

另外一个你需要注意的是:你在做什么样的验证?

应在域对象中验证业务规则,数据完整性检查(例如:“此电子邮件地址是否唯一”)是持久性逻辑的一部分。并且持久性逻辑应该(根据SRP 由单独的实例处理。在给定的示例中,该部分由数据映射器管理。

【讨论】:

哇,我很惊讶直到现在我才听说过 php 过滤器。由单独的实例处理持久性逻辑是什么意思?此外,为什么静态类/单例不好?我用它来减少与获取依赖项相关的样板文件。 例如:唯一性约束。当您尝试使用 PDO 插入重复条目时(也适用于 mysqli API),execute() 方法将抛出带有特定错误代码的异常。删除行也会引起类似的反应,该行用作具有ON DELETE RESTRICT 约束的外键。在 PGSQL 中,您将拥有 even more functionality。所有这些都将通过一个映射器来处理,它可以缓存异常并在它试图存储的域对象上设置一个“错误状态”。 这个解决方案似乎是我一直在寻找的。谢谢。【参考方案2】:

我认为你应该有两层验证,你的“模型”验证验证全局应用于数据模型的不变条件。例如,“名称”是必需的,因为它是数据库中的非空字段。在这种情况下,方法 2 是合适的。

验证的另一层是“表单”验证,它将进入您的表单模型,这将测试特定于表单的条件。例如,您的用户模型上有一个 is_admin 字段,在管理面板更新用户表单中设置它可能是有效的,但在用户“更改密码”表单上无效。这种方法可能更接近第一种。

关于实现它,我可能不会在 setter 中验证它,除非您想围绕每个“集合”进行测试,或者您很乐意只设置和保存有效的字段。通常,如果其中一个字段无效,您会拒绝整个更新,因此拥有一个 isValid() 函数会更有意义,该函数可以在保存之前自动调用,或者在不保存的情况下测试它是否有效。

【讨论】:

以上是关于验证应该在哪里进行?的主要内容,如果未能解决你的问题,请参考以下文章

我应该在哪里存储 JWT 令牌?

JWT应该保存在哪里?

Express.js 项目中在哪里进行验证——在数据库层进行验证(re. Mongoose)?

2019 年我应该在哪里存储我的 JWT,localStorage 真的不安全吗?

在哪里可以找到为验证 python 标准库中的函数而进行的测试?

当 Laravel 用作客户端时,JWT 存储在哪里进行身份验证?