5

我意识到这个话题已经被反复询问和解决,尽管我已经阅读了无数类似的问题并阅读了无数文章,但我仍然无法抓住一些关键问题......我正在尝试构建自己的 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 调用 View 可以解决这个问题吗?从技术上讲,它会遵循 MVC 流程图,但所提议的解决方案似乎是一个微不足道甚至微不足道的细节,假设它很简单,它可能不是......
  • 让我质疑我的整个实现的部分是我有一个用户对象,该对象是用用户相应的角色和权限实例化的,我一直在试图弄清楚如何或更具体地在哪里创建一个isAllowed()可以调用的方法控制器和视图。那么将这个方法放在模型中是否有意义,因为控制器和视图都应该可以访问模型?

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

4

3 回答 3

1
  • The $_POST superglobals should be abstracted by a request instance, as explained in this post.

  • Input validation is not the responsibility of the controllers. Instead it should be handles by domain objects within model layer.

  • Model factory is no a model.

  • Defining class parameter visibility as public breaks the object's encapsulation.

  • HTTP location header (redirect) is a form of response. Thus, it should be handled by view instance.

  • In its current form, your controllers are directly manipulating superglobals. This causes tight coupling to the globals state.

  • Authorization checks should be performed outside controller. Not inside of it.

  • Your "model factory" should instead be a service factory, that is injected in both controller and view. It would ensure that each service is instantiated only once and thus let your controllers work with same model layer's state.

于 2013-05-20T22:53:26.800 回答
1

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

其次,我建议你阅读这篇关于框架的维基百科文章。许多人没有意识到路由(url 调度、遍历)和视图(push、pull)有不同的模式。

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

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

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

于 2013-05-21T01:36:23.760 回答
0

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

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

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

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

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

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

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

于 2013-05-20T22:28:27.253 回答