验证应该在哪里进行?
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() 函数会更有意义,该函数可以在保存之前自动调用,或者在不保存的情况下测试它是否有效。
【讨论】:
以上是关于验证应该在哪里进行?的主要内容,如果未能解决你的问题,请参考以下文章
Express.js 项目中在哪里进行验证——在数据库层进行验证(re. Mongoose)?
2019 年我应该在哪里存储我的 JWT,localStorage 真的不安全吗?