验证应该在哪里进行?

3

我希望在我的PHP项目中使用MVC模式。我还想在Model层中使用Dao-Service模式,因为它使数据库引擎易于交换,并将业务逻辑保持在DB交互之外。

现在,我听说验证应该发生在Model层,因为控制器只负责传输数据。这很合理。

然而,它应该在Service层还是实体本身中实现呢?

方法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,有很多样板代码,
  • 将一些业务逻辑添加到实体中,这似乎是一件不好的事情(特别是如果验证非常复杂-例如需要查询数据库/其他服务),
  • 序列化和反序列化可能会变得困难。

方法二:在服务中进行验证

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进行操作。这种情况下,验证不会在该例程中进行,并且项目的唯一希望现在是单元测试或者我自己在代码中发现它。因此,在这方面,代码更难维护。

您有任何提示吗?我有什么遗漏吗?到目前为止,该项目只是在草图阶段,所以我准备好迎接任何挑战。

2个回答

4

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

实际上有第三种方法:为域对象(您称之为“实体”)设置单独的isValid()方法。当您在多个数据条目中跨越验证规则时,setter验证会变得混乱。

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

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

此外,我建议您使用数据映射器而不是数据访问对象。代码基本上看起来像这样:

$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实例中来外部化验证,以便在构造函数中重用某些验证规则。
然而,当制作较大的应用程序时,您可能会注意到,除了现有的php过滤器之外,很少有重复的检查。
请注意:注意:请不要使用静态类。这会强制您使用不需要的过程式范例。 另一件需要注意的事情是:你正在做什么样的验证?
业务规则应该在领域对象中进行验证,但是数据完整性检查(例如:“此电子邮件地址是否唯一”)是持久性逻辑的一部分。根据SRP,持久性逻辑应该由单独的实例处理。在给定的示例中,该部分由数据映射器管理。

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

2

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

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

关于实施它,我可能不会在设置器中验证它,除非您想测试每个“set”,或者只设置并保存有效的字段。通常,如果其中一个字段无效,您会拒绝整个更新,因此在保存之前自动调用isValid()函数或在未保存时测试是否有效会更合理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接