在MVC中,模型应处理验证吗?


25

我正在尝试重新构造为使用MVC模式开发的Web应用程序,但是我不确定是否应在模型中处理验证。例如,我正在建立一个这样的模型:

class AM_Products extends AM_Object 
{
    public function save( $new_data = array() ) 
    {
        // Save code
    }
}

第一个问题:所以我想知道我的save方法是否应该在$ new_data上调用验证函数,或者假设数据已经被验证?

另外,如果要提供验证,我在想一些定义数据类型的模型代码看起来像这样:

class AM_Products extends AM_Object
{
    protected function init() // Called by __construct in AM_Object
    {
        // This would match up to the database column `age`
        register_property( 'age', 'Age', array( 'type' => 'int', 'min' => 10, 'max' => 30 ) ); 
    }
}

第二个问题: AM_Object的每个子类将对该特定对象的数据库中的每一列运行register_property。我不确定这是否是个好方法。

第三个问题:如果验证应由模型处理,它是否应返回错误消息或错误代码,并让视图使用该代码显示适当的消息?

Answers:


30

第一答案:模型的关键作用是保持完整性。但是,处理用户输入是控制器的责任。

也就是说,控制器必须将用户数据(大多数情况下只是字符串)转换为有意义的内容。这需要解析(并且可能取决于语言环境,例如,假设存在不同的十进制运算符等)。
因此,实际的验证(如“数据格式是否正确?”)应由控制器执行。但是,验证就像“数据有意义吗?” 应该在模型内执行。

用一个例子来阐明这一点:
假设您的应用程序允许您添加一些带有日期的实体(例如,带有最后期限的问题)。您可能有一个API,其中日期可能仅表示为Unix时间戳,而当来自HTML页面时,它将是一组不同的值或MM / DD / YYYY格式的字符串。您不希望模型中包含此信息。您希望每个控制器分别尝试找出日期。但是,当日期随后传递给模型时,模型必须保持完整性。例如,不允许过去的日期或假日/星期天等日期可能有意义。

您的控制器包含输入(处理)规则。您的模型包含业务规则。无论发生什么情况,您都希望始终强制执行业务规则。假设您在控制器中有业务规则,那么如果您要创建其他控制器,则必须复制它们。

第二个答案:该方法确实有意义,但是可以使该方法更强大。而不是最后一个参数是数组,它应该是一个实例,IContstraint其定义为:

interface IConstraint {
     function test($value);//returns bool
}

对于数字,您可能会得到一些

class NumConstraint {
    var $grain;
    var $min;
    var $max;
    function __construct($grain = 1, $min = NULL, $max = NULL) {
         if ($min === NULL) $min = INT_MIN;
         if ($max === NULL) $max = INT_MAX;
         $this->min = $min;
         $this->max = $max;
         $this->grain = $grain;
    }
    function test($value) {
         return ($value % $this->grain == 0 && $value >= $min && $value <= $max);
    }
}

'Age'老实说,我也看不出代表什么。它是实际的财产名称吗?假设默认情况下有约定,则该参数可以简单地转到函数的末尾,并且是可选的。如果未设置,则默认为数据库列名称的to_camel_case

因此,示例调用如下所示:

register_property('age', new NumConstraint(1, 10, 30));

使用接口的要点是,您可以随需添加越来越多的约束,并且它们可以随您所需而变得复杂。用于匹配正则表达式的字符串。日期至少要提前7天。等等。

第三个答案:每个Model实体都应具有类似的方法Result checkValue(string property, mixed value)。控制器应设置数据之前调用它。本Result应具有的所有信息有关校验是否失败,并在它没有的情况下,给出的理由,所以该控制器可以相应地传播那些视图。
如果将错误的值传递给模型,则模型应仅通过引发异常来做出响应。


感谢您的撰写。它阐明了有关MVC的许多内容。
AmadeusDrZaius 2014年

5

我并不完全同意“ back2dos”:我的建议是始终使用单独的表单/验证层,控制器可以使用该层来验证输入数据,然后再将其发送到模型。

从理论上讲,模型验证对可信数据(内部系统状态)进行操作,理想情况下应在任何时间点都可重复,而输入验证对来自不受信任源的数据显式进行一次操作(取决于用例和用户权限)。

这种分离使得构建可重用的模型,控制器和表单成为可能,这些模型,控制器和表单可以通过依赖注入松散地耦合在一起。将输入验证视为白名单验证(“可接受的公认的良好”),将模型验证视为黑名单验证(“拒绝已知的不良”)。白名单验证更安全,而黑名单验证可防止您的模型层过于受限于非常特定的用例。

无效的模型数据应始终引发异常(否则,应用程序可以继续运行而不会注意到错误),而来自外部源的无效输入值不是意外的,而是常见的(除非您让用户从未犯过错误)。

另请参阅:https : //lastzero.net/2015/11/why-im-using-a-separate-layer-for-input-data-validation/


为简单起见,让我们假设有一个Validator类族,并且所有验证都是通过策略化的层次结构完成的。具体的验证者孩子也可能由专业验证者组成:电子邮件,电话号码,表格令牌,验证码,密码等。控制器输入验证是两类:?1)验证的控制器和方法/命令,2)的数据的初步检查(的存在即HTTP请求方法,有多少数据输入(太多太少)
Anthony Rutledge

输入量经过验证后,您需要知道正确提交的HTML控件(按名称),请记住每个请求的输入数量可能会有所不同,因为并非所有HTML表单控件都保留空白(特别是复选框)。此后,最后的初步检查是对输入大小的测试。我认为这应该是,而不是晚。在控制器验证器中进行数量,控件名称和基本输入大小的检查将意味着为控制器中的每个命令/方法都具有一个验证器。我觉得这使您的应用程序更安全。
安东尼·鲁特里奇

是的,命令的控制器验证器将与模型方法所需的参数(如果有)紧密耦合,但是控制器自身不会,除非引用了该控制器验证器。这是一个值得妥协的妥协,因为绝不能以大多数投入都是合法的这一假设为前提。您越早可以停止对应​​用程序的非法访问,那就越好。在控制器验证器类(输入的数量,名称和最大大小)中执行此操作,使您不必实例化整个模型来明确拒绝恶意HTTP请求。
安东尼·鲁特里奇

话虽如此,在解决最大输入大小问题之前,必须确保编码良好。考虑到所有因素,即使工作被封装,对于模型而言,这也是太多了。拒绝恶意请求变得不必要的昂贵。总之,控制器需要对发送给模型的内容承担更多责任。控制器级别的故障应该是致命的,除了200 OK之外,没有返回给请求者的信息。记录活动。引发致命异常。终止所有活动。尽快停止所有进程。
安东尼·鲁特里奇

最小控件,最大控件,正确控件,输入编码和最大输入大小都与请求的性质有关(以一种或另一种方式)。某些人没有将这五个核心问题确定为是否应满足请求。如果不满足所有这些条件,为什么要将这些信息发送给模型?好问题。
安东尼·鲁特里奇

3

是的,模型应该执行验证。用户界面也应验证输入。

确定有效值和状态显然是模型的责任。有时,此类规则经常更改。在那种情况下,我将从元数据中馈送模型和/或修饰它。


如果用户的意图明显是恶意的或错误的,该怎么办?例如,一个特定的HTTP请求应该具有不超过七(7)个输入值,但是您的控制器将获得七十(70)个输入值。当请求明显损坏时,您真的要允许十倍(10x)的允许值数量到达模型吗?在这种情况下,所讨论的是整个请求的状态,而不是任何一个特定值的状态。深度防御策略建议在将数据发送到模型之前,应检查HTTP请求的性质。
安东尼·鲁特里奇

(续)这样,您不是在检查用户提供的特定值和状态是否有效,而是在检查请求的全部有效。到目前为止,还没有必要进行深入研究。油已经在地表了。
安东尼·鲁特里奇

(续)无法强制进行前端验证。必须考虑到可以使用自动化工具与Web应用程序交互。
安东尼·鲁特里奇

(经过深思熟虑)模型中的数据的有效值和状态很重要,但是我所描述的内容对通过控制器进入请求的意图很重要。省略意图验证会使您的应用程序更容易受到攻击。意向只能是好的(按照您的规则进行游戏)或坏的(超出您的规则)。可以通过对输入的基本检查来验证意图:最小控件,最大控件,正确控件,输入编码和最大输入大小。这是一个全有或全无的主张。一切顺利,或者请求无效。无需向模型发送任何内容。
安东尼·鲁特里奇

2

好问题!

在万维网开发方面,如果您还要询问以下内容,该怎么办。

“如果从用户界面向控制器提供了错误的用户输入,则控制器应该以某种循环方式更新视图,强制命令和输入数据在处理之前是准确的吗?如何?在正常情况下如何更新视图条件?视图是否与模型紧密耦合?是用户输入验证模型的核心业务逻辑,还是模型的初步业务逻辑,因此应在控制器内部发生(因为用户输入数据是请求的一部分)?

(实际上,可以并且应该延迟实例化模型直到获得良好的输入吗?)

我的观点是,模型应管理尽可能纯净的情况,不受模型实例化之前(并且肯定在模型获取输入数据之前)应进行的基本HTTP请求输入验证的约束。由于管理状态数据(持久性或其他方式)和API关系是模型的重中之重,因此让基本HTTP请求输入验证在控制器中进行。

总结一下。

1)验证您的路由(从URL解析),因为在继续进行其他操作之前,控制器和方法必须存在。在进入真正的控制器之前,这肯定应该在前控制器领域(Router类)中发生。咄。:-)

2)模型可能具有许多输入数据源:HTTP请求,数据库,文件,API,并且是网络。如果要将所有输入验证都放入模型中,则应将HTTP请求输入验证视为程序业务需求的一部分。案件结案。

3)但是,如果HTTP请求输入不好,则要花很多时间实例化对象!您可以通过实例化模型及其所有复杂性(是的,也许还有更多的API和DB输入/输出数据验证器)之前对其进行验证,从而确定HTTP请求输入**是否很好(随请求一起提供)。

测试以下内容:

a)HTTP请求方法(GET,POST,PUT,PATCH,DELETE ...)

b)最少的HTML控件(您有足够的控件吗?)。

c)最多HTML控件(您有太多吗?)。

d)正确的HTML控件(您有正确的控件吗?)。

e)输入编码(通常是UTF-8编码?)。

f)最大输入大小(是否有任何输入超出范围?)。

请记住,您可能会得到字符串和文件,所以等待模型实例化可能会因为请求到达您的服务器而变得非常昂贵。

我在这里描述的内容触及了通过控制器传入的请求的意图。省略意图验证会使您的应用程序更容易受到攻击。意图只能是好的(遵守基本规则)或坏的(超出基本规则)。

HTTP请求的意图是全有还是全无。一切顺利,或者请求无效。无需向模型发送任何内容。

HTTP的这个基层要求意图没有做定期的用户输入错误和验证。在我的应用程序中,HTTP请求必须以上述五种方式有效,我才能兑现。用纵深防御的方式来说,如果这五件事中的任何一项失败了,您就永远不会在服务器端进行用户输入验证。

是的,这意味着即使文件输入也必须符合您的前端尝试,以验证并告诉用户所接受的最大文件大小。只有HTML?没有JavaScript?很好,但是必须通知用户上传太大的文件的后果(主要是,它们将丢失所有表单数据并被踢出系统)。

4)这是否意味着HTTP请求输入数据不属于应用程序业务逻辑的一部分?不,这仅意味着计算机是有限的设备,必须明智地使用资源。尽早停止恶意活动是有道理的。您需要支付更多的计算资源,以等待稍后停止它。

5)如果HTTP请求输入不正确,则整个请求不正确。我就是这样看的。良好的HTTP请求输入的定义是从模型的业务需求派生的,但是必须在资源划分上有一些要点。在您杀死一个不好的请求并说:“哦,嘿,没关系。错误的请求”之前,您会忍受多长时间。

判断不仅仅在于用户犯了合理的输入错误,还在于HTTP请求越界越多,必须将其声明为恶意并立即停止。

6)因此,以我的钱,HTTP请求(方法,URL /路由和数据)要么全部正常,要么其他都不能继续。一个健壮的模型已经具有验证任务要考虑的问题,但是一位优秀的资源牧羊人说:“我的方法,还是高级方法。要正确,否则就根本不行。”

不过,这是您的程序。“有多种方法可以做到这一点。” 有些方法比其他方法花费更多的时间和金钱。稍后(在模型中)验证HTTP请求数据应在应用程序的整个生命周期内花费更多(尤其是向上扩展或向外扩展)。

如果您的验证器是模块化的,则验证控制器中的基本HTTP请求输入**应该不会有问题。只需使用策略化的Validator类,其中的验证器有时也由专门的验证器组成(电子邮件,电话,表单令牌,验证码等)。

有人认为这完全是错误的做法,但是当“四人帮”撰写《设计模式:可重用的面向对象软件的元素》时,HTTP才刚刚起步。

================================================== ========================

现在,由于它与普通用户输入验证有关(在HTTP请求被视为有效之后),当用户混乱时,它正在更新视图,您需要考虑一下!这种用户输入验证应在模型中进行。

您不能保证前端使用JavaScript。这意味着您无法保证使用错误状态异步更新应用程序的UI。真正的渐进增强也将涵盖同步用例。

解释同步用例是一门越来越失传的艺术,因为有些人不想花费时间和精力来跟踪其所有UI技巧的状态(显示/隐藏控件,禁用/启用控件) ,错误指示,错误消息)(通常通过跟踪数组中的状态)。

更新:在图中,我说View应当引用Model。否。您应该将数据View从传递到,Model以保持松散耦合。 在此处输入图片说明

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.