【问题标题】:Understanding / improving a barebones MVC framework理解/改进准系统 MVC 框架
【发布时间】:2013-05-15 14:05:31
【问题描述】:

我意识到这个话题已经被反复询问和解决,尽管我已经阅读了无数类似的问题并阅读了无数文章,但我仍然无法抓住一些关键问题......我正在尝试构建我自己的 MVC 框架,用于学习目的和更好地熟悉 OOP。这是供个人私人使用的,并不是暗示这是懒惰的借口,而是我不太关心拥有更强大框架的所有花里胡哨。

我的目录结构如下:

public
- index.php
private
- framework
  - controllers
  - models
  - views
  - FrontController.php
  - ModelFactory.php
  - Router.php
  - View.php
- bootstrap.php

我有一个 .htaccess 文件,它将所有请求定向到 index.php,该文件包含基本配置设置,例如时区和全局常量,然后加载 bootstrap.php 文件。引导程序包含我的类自动加载器,启动会话,定义要在整个项目中使用的全局函数,然后调用路由器。路由器从 URL 中挑选出请求,使用 ReflectionClass 对其进行验证,然后以 example.com/controller/method/params 的形式执行请求。

我所有的控制器都扩展了 FrontController.php:

<?php
namespace framework;

class FrontController
{
    public $model;
    public $view;
    public $data = [];

    function __construct()
    {
        $this->model = new ModelFactory();
        $this->view = new View();
    }

    // validate user input
    public function validate() {}

    // determines whether or not a form is being submitted
    public function formSubmit() {}

    // check $_SESSION for preserved input errors
    public function formError() {}
}

此前端控制器加载 ModelFactory:

<?php
namespace framework;

class ModelFactory
{
    private $db       = null;
    private $host     = 'localhost';
    private $username = 'dev';
    private $password = '********';
    private $database = 'test';

    // connect to database
    public function connect() {}

    // instantiate a model with an optional database connection
    public function build($model, $database = false) {}
}

基础视图:

<?php
namespace framework;

class View
{
    public function load($view, array $data = [])
    {
        // calls sanitize method for output
        // loads header, view, and footer
    }

    // sanitize output
    public function sanitize($output) {}

    // outputs a success message or list of errors
    // returns an array of failed input fields
    public function formStatus() {}
}

最后,这里有一个示例控制器来演示当前如何处理请求:

<?php
namespace framework\controllers;

use framework\FrontController,
    framework\Router;

class IndexController extends FrontController implements InterfaceController
{
    public function contact()
    {
        // process form if submitted
        if ($this->formSubmit()) {
            // validate input
            $name = isset($_POST['name']) && $this->validate($_POST['name'], 'raw') ? $_POST['name'] : null;
            $email = isset($_POST['email']) && $this->validate($_POST['email'], 'email') ? $_POST['email'] : null;
            $comments = isset($_POST['comments']) && $this->validate($_POST['comments'], 'raw') ? $_POST['comments'] : null;

            // proceed if required fields were validated
            if (isset($name, $email, $comments)) {
                // send message
                $mail = $this->model->build('mail');
                $to = WEBMASTER;
                $from = $email;
                $subject = $_SERVER['SERVER_NAME'] . ' - Contact Form';
                $body = $comments . '<br /><br />' . "\r\n\r\n";
                $body .= '-' . $name;

                if ($mail->send($to, $from, $subject, $body)) {
                    // status update
                    $_SESSION['success'] = 'Your message was sent successfully.';
                }
            } else {
                // preserve input
                $_SESSION['preserve'] = $_POST;

                // highlight errors
                if (!isset($name)) {
                    $_SESSION['failed']['name'] = 'Please enter your name.';
                }
                if (!isset($email)) {
                    $_SESSION['failed']['email'] = 'Please enter a valid e-mail address.';
                }
                if (!isset($comments)) {
                    $_SESSION['failed']['comments'] = 'Please enter your comments.';
                }
            }
            Router::redirect('contact');
        }

        // check for preserved input
        $this->data = $this->formError();

        $this->view->load('contact', $this->data);
    }
}

据我所知,我的逻辑是错误的,原因如下:

  • 应在模型中进行验证,而不是在控制器中进行。但是,模型不应该访问 $_POST 变量,所以我不完全确定我是否正确地执行了这部分?我觉得这就是他们所说的“胖控制器”,这很糟糕,但我不确定需要改变什么......
  • 控制器不应向视图发送数据;相反,视图应该有权访问模型来请求自己的数据。那么将$data 属性从FrontController 移到ModelFactory 中,然后在不传递数据的情况下从Controller 调用视图可以解决这个问题吗?从技术上讲,它会遵循 MVC 流程图,但所提议的解决方案似乎是一个微不足道甚至微不足道的细节,假设它很简单,但它可能不是......
  • 让我质疑我的整个实现的部分是我有一个用用户相应的角色和权限实例化的用户对象,我一直在试图弄清楚如何或更具体地在哪里创建一个 isAllowed()可以从 Controller 和 View 调用的方法。那么将这个方法放在模型中是否有意义,因为控制器和视图都应该可以访问模型?

总的来说,我是否走在正确的轨道上,或者我需要解决哪些明显的问题才能走上正确的轨道?我真的希望得到针对我的示例的个人回应,而不是“去阅读这个”。感谢任何诚实的反馈和帮助。

【问题讨论】:

标签: php oop model-view-controller


【解决方案1】:

验证应该在模型中完成,而不是在控制器中。然而,一个 模型不应该访问 $_POST 变量,所以我不完全 确定我是否正确地做这部分?我觉得这是 他们称之为“胖控制器”的东西很糟糕,但我不确定是什么 需要改变...

没错,Model 应该对请求一无所知,因此,您需要将 $_POST 传递给模型,但它不会知道这是请求参数。

有一件事:与业务逻辑无关的验证应该留在控制器中。假设您出于安全原因为表单创建了一个 CSRF 令牌,这个验证应该在控制器内部,因为它处理请求。

控制器不应该向视图发送数据;相反,视图应该有权访问模型来请求自己的数据。那么将 $data 属性从 FrontController 移到 ModelFactory 中,然后在不传递数据的情况下从 Controller 调用 View 可以解决这个问题吗?从技术上讲,它会遵循 MVC 流程图,但所提议的解决方案似乎是一个微不足道甚至微不足道的细节,假设它很简单,但它可能不是......

这不一定是真的。这种方法称为Active Model,通常您使用Observer 模式,模型由视图观察。如果某些模型发生更改,它会通知将自行更新的视图。这种方法更适合桌面应用程序,而不是基于 Web 的应用程序。在 Web 应用程序中,最常见的是将控制器作为模型和视图之间的中介(被动模型)。没有正确的方法,你应该选择你最喜欢的。

让我质疑我的整个实现的部分是我有一个用用户相应的角色和权限实例化的用户对象,我一直在试图弄清楚如何或更具体地在哪里创建一个 isAllowed()可以从 Controller 和 View 调用的方法。那么将这个方法放在模型中是否有意义,因为控制器和视图都应该可以访问模型?

好吧,这个没办法,我得告诉你读一下ACL

【讨论】:

  • 控制器不负责验证和访问控制。
  • 我没有说他们负责访问控制,但它确实负责与请求本身有关的验证,而不是逻辑。
【解决方案2】:
  • $_POST 超全局变量应由请求实例抽象,如 this post 中所述。

  • 输入验证不是控制器的责任。相反,它应该由模型层内的domain objects 处理。

  • 模型工厂不是模型。

  • 将类参数可见性定义为public 会破坏对象的封装。

  • HTTP 位置标头(重定向)是一种响应形式。因此,它应该由视图实例处理。

  • 在当前形式下,您的控制器直接操作超全局变量。这会导致与全局状态的紧密耦合。

  • 应执行授权检查outside controller。不在里面。

  • 你的“模型工厂”应该是一个服务工厂,它被注入到控制器和视图中。它将确保每个服务仅实例化一次,从而让您的控制器使用相同的模型层状态。

【讨论】:

  • 我注意到您总是第一个回答带有“MVC”标签的问题,我相信您非常了解这种模式。只要我一直在做这件事,我真的不想通过猜测让自己陷入进一步的混乱。我想我不可能说服您更新我的问题中提供的示例以向我展示应该如何完成?
【解决方案3】:

首先,我认为您尝试创建自己的框架很棒。许多人说每个人都应该这样做,即使只是为了学习目的。

其次,我建议您阅读有关框架的Wikipedia article。很多人没有意识到路由(url 调度、遍历)和视图(push、pull)有不同的模式。

就个人而言,我认为没有必要抽象出超级全局变量,因为它们已经是原始输入 (php://input) 的抽象(通过 php)并且可以修改。只是我的看法。

你说得对,验证应该由模型完成。你不验证表单,你验证数据。至于访问数据的视图,这取决于您选择的模式。您可以将数据推送到 View,也可以由 View 拉取数据。

如果您好奇,我在 MVC basic framework 上的尝试在 github 上。它的4个文件,少于2K行代码(DB层是1K行)。它实现了遍历(组件)路由和拉取数据,已经有很多框架实现了替代模式。

【讨论】:

    猜你喜欢
    • 2012-04-24
    • 2011-05-29
    • 2010-09-15
    • 2010-09-05
    • 1970-01-01
    • 1970-01-01
    • 2011-04-22
    • 2014-04-03
    • 1970-01-01
    相关资源
    最近更新 更多