【问题标题】:Where to validate and process form submission in MVC website在 MVC 网站中验证和处理表单提交的位置
【发布时间】:2011-06-27 22:07:10
【问题描述】:

我正在开发一个基于 PHP 的 Model-View-Controller 结构化网站。我知道模型应该处理业务逻辑,视图向用户呈现 HTML(或其他),并且控制器促进了这一点。我遇到困难的地方是表格。 我在控制器中放了多少处理,我的模型放了多少?

假设我正在尝试更新用户的名字和姓氏。我想要做的是使用 AJAX 向我的一个控制器提交一个表单。我希望(再次)在服务器端验证数据,如果有效,将其保存到数据库中,然后将 JSON 响应返回给视图,作为成功或错误。

我应该在控制器中创建用户模型的实例,还是应该让控制器中继到模型中的静态方法?这是如何工作的两个示例:

选项 #1:在模型中处理 POST

<form action="/user/edit-user-form-submit/" method="post">
    <input type="text" name="firstname">
    <input type="text" name="lastname">
    <button type="submit">Save</button>
</form>

<?php
    class user
    {
        public function __construct($id){} // load user from database
        public function set_firstname(){} // validate and set first name
        public function set_lastname(){} // validate and set last name
        public function save_to_database(){} // save object fields to database

        public static function save_data_from_post()
        {
            // Load the user
            $user = new user($_POST['id']);

            // Was the record found in the db?
            if($user->exists)
            {
                // Try to set these fields
                if(
                    $user->set_firstname($_POST['firstname'])
                    and
                    $user->set_lastname($_POST['lastname'])
                )
                {
                    // No errors, save to the dabase
                    $user->save_to_database();

                    // Return success to view
                    echo json_encode(array('success' => true));
                }
                else
                {
                    // Error, data not valid!
                    echo json_encode(array('success' => false));
                }
            }
            else
            {
                // Error, user not found!
                echo json_encode(array('success' => false));
            }
        }   
    }

    class user_controller extends controller
    {
        public function edit_user_form()
        {
            $view = new view('edit_user_form.php');
        }
        public function edit_user_form_submit()
        {
            user::save_data_from_post();
        }
    }
?>

选项 #1:在模型中处理 POST

<form action="/user/edit-user-form-submit/" method="post">
    <input type="text" name="firstname">
    <input type="text" name="lastname">
    <button type="submit">Save</button>
</form>

<?php
    class user
    {
        public function __construct($id){} // load user from database
        public function set_firstname(){} // validate and set first name
        public function set_lastname(){} // validate and set last name
        public function save_to_database(){} // save object fields to database
    }

    class user_controller extends controller
    {
        public function edit_user_form()
        {
            $view = new view('edit_user_form.php');
        }
        public function edit_user_form_submit()
        {
            // Load the user
            $user = new user($_POST['id']);

            // Was the record found in the db?
            if($user->exists)
            {
                // Try to set these fields
                if(
                    $user->set_firstname($_POST['firstname'])
                    and
                    $user->set_lastname($_POST['lastname'])
                )
                {
                    // No errors, save to the dabase
                    $user->save_to_database();

                    // Return success to view
                    echo json_encode(array('success' => true));
                }
                else
                {
                    // Error, data not valid!
                    echo json_encode(array('success' => false));
                }
            }
            else
            {
                // Error, user not found!
                echo json_encode(array('success' => false));
            }
        }
    }
?>

这两个例子做的事情完全相同,我意识到这一点。但是这样做有正确和错误的方法吗?我读过很多关于瘦控制器和胖模型的文章,选项#1 来自哪里。你是怎么处理的?谢谢,很抱歉这个问题太长了!

【问题讨论】:

    标签: php model-view-controller validation forms


    【解决方案1】:

    简而言之,您可以使用这两种方法中的任何一种——但是您应该稍微改变一下。

    考虑一下:模型并不真正“了解”post、get 等。他们应该只知道他们是什么与业务相关的东西——在你的情况下是用户。

    因此,虽然可以使用方法 #1,但您应该直接从模型中访问 post 变量。相反,让函数接受一个参数数组,然后使用这些参数创建用户。

    通过这种方式,您可以轻松地重用代码,例如在 shell 脚本或其他没有 $_POST 这样的东西的地方。

    虽然第二种方法在控制器中更为冗长,但您也可以这样做。然而,也许更好的方法是使用“服务类”。该服务将有一个方法,比如说“createUserFromArray”,它接受一个数组并返回一个用户。同样,您可以将 $_POST 作为参数传递给此方法 - 类似于您应该如何将它们传递给修改后的 #1 中的函数。

    只有控制器应该直接处理输入。这是因为控制器处理了请求,因此它可以知道 post。

    tl;dr你的模型不应该直接使用像$_POST 这样的超全局变量。

    【讨论】:

    • 也非常感谢您的意见。很高兴知道不要在模型中访问全局变量……这是有道理的。关于服务类的有趣想法。您是否会将服务类的功能限制为仅一种方法(即从数组创建新用户),或者添加其他功能是否有意义,例如 update_user_from_array?此外,对于这两个函数,您是强制在数组中输入特定数量的值,还是让函数简单地处理传递给它的内容(即名字和姓氏以及另一个时间的用户名和密码)?
    • 一般来说,服务通常会包含与某些特定类型的模型相关的任意数量的方法。例如,您可能有一个用户服务,其中包含与用户模型交互的不同方法。我认为方法的数组可以包含任意数量的值,但是您应该检查您实际想要的值,如果所需的值缺失,则抛出异常。如果您想查找更多信息,此概念称为“服务层”。
    • 一个服务层,没想到!但是,在使用多个域模型时,不是通常使用服务层吗?我喜欢这个想法,但在一个典型的小型网络应用程序中这会不会有点矫枉过正?
    • 假设您有一个稍微复杂的操作,您可以拥有一个仅使用单个模型的服务。是的,我同意它在一个更简单的应用程序中可能是矫枉过正的 - 但是,当您发现您可能需要复制粘贴您的代码时,考虑将其中一些代码移至服务可能是个好主意跨度>
    • 刚刚阅读了有关服务层的更多信息,我认为您确实了解了我正在努力解决的问题。我一直在读“保持你的控制器”瘦……但后来不知道该把逻辑放在哪里。非常感谢您的意见。对于那些有兴趣阅读有关服务层的更多信息的人,请查看此blog article,它很好地解释了这一点。
    【解决方案2】:

    从我的观点和我们的工作方式来看,模型将处理传递给它的数据的验证和过滤,但我们使用控制器将这些数据推送到模型中。

    在上面的 cmets 中已经说明,模型不必知道 $_POST 或 $_GET 这些是控制器必须处理的用户输入。另一方面,模型必须处理传递给它的所有数据验证,因为您绝对不想在项目的不同代码部分中一次又一次地进行数据验证。

    【讨论】:

    • 我创建了抽象的CleanerSanitizerValidator 类(三合会)。对于使用filter_input_array() 和$_FILES 的各种输入源,存在具体的Sanitizer 类。具体的Cleaner 类是特定于形式/情况的,并且具有Sanitizer 对象作为属性。 Cleaners 利用Sanitizers 清理输入,然后Cleaners 将数组传递给具体的Validator。这样的三合会实例应该住在哪里?这似乎很自然地出现在控制器中,因为这实际上是关于输入,而不是为什么要使用输入。有什么想法吗?
    • 哦,对了,还有ServerCleaner、ServerSanitizer、ServerValidator。这样做的目的是验证 $_SERVER 中的关键值。因为这处理的是请求本身,所以这个三元组是否应该在路由器执行其操作之前发生?
    【解决方案3】:

    MVC 设计模式的一个重要原因是它是维护关注点分离的好方法。视图应该不知道模型,反之亦然。控制器只是作为一种在模型和视图之间调解的交通警察。因此,Controller 应该从 View 中获取数据,进行所需的最少处理,以便 Model 可以理解数据而无需知道 View 是如何实现的(即通过 HTML 表单),并将其提供给 Model,以便模型可以持久化数据。

    这使得模型可以在需要通过 HTML 表单以外的其他方式创建/保存/持久化项目时在其他实例中重用,而无需跨多个控制器复制项目保存代码。

    更新:我忘了提验证。与持久化数据一样,Controller 应该获取数据并将其传递给 Model 进行验证,因为 Model 知道它需要的数据的确切格式。如果数据无效,您可以通过让模型抛出异常来结合验证和持久性,控制器可以根据需要捕获和处理(例如,呈现 JSON 错误响应。)

    【讨论】:

    • 感谢您花时间解释这一点,这很有帮助。尽管我必须承认模型/视图关系是我所苦苦挣扎的一种。在视图中访问模型实在是太容易了,我真的不明白让控制器传递这些信息有什么帮助。也许你可以详细说明?
    • @Jonathan:是的,很难证明拥有如此多的代码抽象层是合理的,特别是如果它是一个小项目。面向对象的设计确实在项目的早期阶段增加了复杂性。但美妙之处在于,如果您以后需要扩展,您不必为了避免重复功能而重构一大堆代码。如果您稍后添加 Web API 会怎样?如果您最终需要更改数据存储系统怎么办?如果一切都已经在可管理、可扩展的块中,那么对代码库的更改就不那么全面了,这样可以节省大量时间。
    猜你喜欢
    • 2013-05-06
    • 1970-01-01
    • 2011-07-09
    • 1970-01-01
    • 1970-01-01
    • 2011-03-26
    • 2014-09-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多