模型-视图-控制器新视角


Model - View - Controller new perspective

大家早上好。最近我读到一篇关于mvc模式的文章,说大多数php框架都错误地实现了mvc模式。php主mvc模式第1部分php主mvc模式第2部分在阅读了这篇文章并查看了该实现之后,出现了一个问题。究竟如何在视图中调用模型中调用的方法?我想说的是这个。这是文章中的一段代码。

<?php
$model = $_GET['model'];
$view = $_GET['view'];
$controller = $_GET['controller'];
$action = $_GET['action'];
if (!(empty($model) || empty($view) || empty($controller) || empty($action))) {
    $m = new $model();
    $c = new $controller($m, $action);
    $v = new $view($m);
    echo $v->output();
} 

假设我们在阅读了这篇文章之后,已经编写了这个设计模式的一个小实现,我们有以下代码:

<?php
class Index extends Controller
{
   public function __construct(IndexModel $model, $action)
   {
       $this->model = $model;
   }
   public function someAction($id)
   {
       $this->model->getData($id);
   }
}
class Index extends View
{
   public function __construct(IndexModel $model, $action)
   {
       $this->model = $model;
   }
   public function someAction()
   {
       $this->model->getData();
   }
}
class Index extends Model
{
   public function __construct()
   {
        //Some Code Here
   }
   public function someAction()
   {
       // Inserting Data into database.
   }
}

正如您所看到的,我们在控制器和视图中都调用了相同的方法来从数据库中获取数据。但是,如果我知道正确的话,视图应该负责控制器的工作,所以$id在视图中再次解析它或类似的东西是不对的。那么如何解决呢?

  1. 控制器和模型各部分之间没有1:1的相关性(注意:不是"模型",而是模型各部分)。拥有"索引控制器"并不意味着需要"索引模型"answers"索引视图"
  2. M、V和C不是一起构造的。控制器被构造,然后决定加载/构造/调用哪个模型方法以及哪个视图应该响应请求。
    1. 讨论了控制器是否应该调用视图,或者模型是否应该"更新"视图。由于web请求是短暂的,并且没有"恒定"视图,后者在PHP中没有什么意义;它更适合于原始的SmallTalk或Obj-C或类似的环境
  3. 第一个代码片段对empty的使用非常糟糕(请参阅此处),而且不应该将所有部分一起构造

MVC应该这样处理:

  • 模型是 应用程序。它不仅仅是一个"数据处理程序"或"数据存储",它还是核心应用程序。应用程序所做的一切,从商业逻辑上讲,都是"模型"。这包括发送电子邮件通知、数据库维护工作等辅助工作,它们都在模型中
  • 视图负责产生各种形式的输出。视图应该能够与模型交互,以获得完成其工作所需的数据。数据不应该被"推入"视图,视图应该能够"拉取"它所需要的数据;否则,视图外部的东西需要知道视图需要什么数据,这意味着视图逻辑不再包含在视图中
  • 控制器只是剩下的一点点胶水,用来调用正确的模型方法和视图来响应传入的请求

我通常这样构造这些部分:

  • 该模型由多个部分组成,包括数据存储处理程序、"基元"(为单个业务对象建模的类)和"服务"。"服务"包含应用程序可以执行的所有"操作",并形成API模型。每当你想在应用程序中"做"一些事情,比如"注册用户"或"获取日期范围X到Y的所有记录"时,API服务中有一个专门的方法。只要看看这个服务API,你就应该能够列举你的应用程序"做"的所有事情
  • 有一个对象是"调度器"或"服务定位器",或者只是一个依赖注入容器,它简化了这些服务类的实例化,并允许有人调用它们。控制器会得到其中一个,视图也会得到
  • 有一个路由器根据URL进行一些"粗略路由",调用一个控制器方法。控制器进一步查看请求的细节,并决定调用模型方法和/或用视图进行响应
  • 视图可以根据请求的具体情况来决定呈现某些数据的最佳方式,例如,是使用HTML页面还是JSON数据blob进行响应。是的RESTful服务

在粗略的伪代码:

$r = new Router;
$r->route($_GET, $_POST, $_SERVER); // or something like that

这发送到类似的东西:

class FooController {
    public function __construct(ServiceLocator $services) { ... }
    public function bar(Request $request) {
        $request->assertIsPost();
        $this->services->locate('Baz')->froogleTheWibbles($request->getPostParams());
        (new BarView($this->services))->displayWibbles($request);
    }
}
class BarView {
    public function __construct(ServiceLocator $services) { ... }
    public function displayWibbles(Request $request) {
        switch ($request->accepts()) {
            case 'html' :
                $this->loadTemplate(...);
                ...
            case 'json' :
                echo json_encode($this->services->locate('Baz')->getWibbles());
        }
    }
}

该模型可以做它需要做的任何事情…

class Baz {
    public function froogleTheWibbles(array $data) {
        foreach ($data as $wibbleData) {
            $wibble = new Wibble($wibbleData);
            $this->wibbleStore->save($wibble);
        }
        ...
    }
}

MVC没有"唯一的答案",重要的是模型包含了应用程序独立于输入和输出所"做"的一切,视图可以尽可能独立地根据请求产生正确的输出,控制器只是处理输入条件的一点点粘合剂。有多种方式可以实现这一点。重要的设计原则应该是实现视图和控制器可以互换以适应不同的条件(网页、JSON API、XML API、SOAP API、CLI调用、ZeroMQ节点等),但"模型"不是。