理解/改进准系统MVC框架

时间:2013-05-20 22:19:42

标签: php oop model-view-controller

我意识到这个话题已经被反复询问和解决,虽然我已经阅读了无数类似的问题并阅读了无数文章,但我仍然没有抓住一些关键问题...我正在尝试构建我自己的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变量,因此我不能完全确定我是否正确地执行了此部分操作?我觉得这就是他们所谓的“胖控制器”,这很糟糕,但我不确定需要改变什么......
  • 控制器不应向View发送数据;相反,View应该有权访问Model以请求自己的数据。那么将$data属性移出FrontController并进入ModelFactory,然后从Controller调用View而不传递数据来解决这个问题?从技术上讲,它会遵循MVC流程图,但所提出的解决方案看起来像是一个微不足道甚至是微不足道的细节,假设它很简单,它可能不是......
  • 让我质疑我的整个实现的部分是我有一个User对象,它是用用户对应的角色和权限实例化的,我一直试图弄清楚如何或更具体地在哪里创建{{1可以从Controller和View调用的方法。将此方法放入模型中是否有意义,因为Controller和View都应该可以访问模型?

总的来说,我是在正确的轨道上,还是我需要解决哪些明显的问题才能走上正轨?我真的希望能够针对我的例子做出个人回应,而不是“去读这个”..我很感激任何诚实的反馈和帮助。

3 个答案:

答案 0 :(得分:1)

  • $_POST超全局应由请求实例抽象,如this post中所述。

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

  • 模型工厂不是模特。

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

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

  • 在目前的形式中,您的控制器直接操纵超全局。这会导致与全局状态的紧密耦合。

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

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

答案 1 :(得分:1)

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

其次,我建议你在框架上阅读这个Wikipedia article。许多人没有意识到路由(url dispatch,traversal)和视图(推送,拉取)有不同的模式。

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

你应该通过模型完成验证。您不验证表单,验证数据。对于访问数据的视图,这取决于您选择的模式。您可以将数据推送到View,或者View可以提取数据。

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

答案 2 :(得分:0)

  

验证应在模型中完成,而不是在控制器中完成。但是,一个   模型不应该有$ _POST变量的访问权限,所以我并不完全   确定我是否正确地做这个部分?我觉得这样   他们所谓的“胖控制器”是坏事,但我不确定是什么   需要改变...

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

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

  

控制器不应该向View发送数据;相反,View应该有权访问Model以请求自己的数据。那么将$ data属性移出FrontController并进入ModelFactory,然后从Controller调用View而不传递数据解决这个问题?从技术上讲,它会遵循MVC流程图,但所提出的解决方案看起来像是一个微不足道甚至是微不足道的细节,假设它很简单,它可能不是......

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

  

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

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