理解/改进准系统MVC框架

mis*_*tin 5 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
Run Code Online (Sandbox Code Playgroud)

我有一个.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() {}
}
Run Code Online (Sandbox Code Playgroud)

这个前端控制器加载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) {}
}
Run Code Online (Sandbox Code Playgroud)

和基本视图:

<?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() {}
}
Run Code Online (Sandbox Code Playgroud)

最后,这是一个示例控制器,用于演示当前如何处理请求:

<?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);
    }
}
Run Code Online (Sandbox Code Playgroud)

从我能够理解的,我的逻辑是关闭的,原因如下:

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

总的来说,我是在正确的轨道上,还是我需要解决哪些明显的问题才能走上正轨?我真的希望得到针对我的例子的个人回复,而不是"去读这个"..我感谢任何诚实的反馈和帮助.

Hen*_*los 0

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

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

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

控制器不应该向视图发送数据;相反,视图应该有权访问模型来请求自己的数据。那么,将 $data 属性从 FrontController 移出并移入 ModelFactory,然后从控制器调用视图而不传递数据可以解决此问题吗?从技术上讲,它将遵循 MVC 流程图,但假设它是如此简单,那么所提出的解决方案似乎是一个微不足道甚至微不足道的细节,但事实可能并非如此。

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

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

好吧,这个没办法,我只好叫你去读一下ACL了。