在桌面应用程序的原始 MVC 模式中(由 Trygve Reenskaug 在 1979 年提出),控制器更新模型,模型通知视图有关更改,然后视图从中提取数据。此外,M、V 和 C 组件被认为与窗口中的单个控件(按钮、文本框、复选框等)相关。如果屏幕上有 15 个控件,那么每个控件都“附加”了一个 MVC。
在 Web 应用程序中,通知步骤(从模型到视图)不存在。但是 M、V 和 C 组件之间的其余关系保持不变。因此,在 Web MVC 中,控制器更新模型,视图从中提取数据。此外,所有三个组件都与网页相关,而不是与网页上的单个控件相关。
控制器和视图都不应使用域对象,例如entities(作为领域模型的一部分)直接。它们仅通过应用程序服务(也称为用例)与域模型进行(单独)通信。这些服务可以将(多个)任务委托给(多个)域服务。进而利用 域对象 (entities)。如果需要外部组件和/或服务(例如 持久层 中的组件和/或服务,例如存储库、数据映射器),则域模型和它们之间的连接是通过使用相应的接口。这些也是领域层的一部分。
视图不应该访问控制器。为什么要这样做呢?控制器解释用户发送的请求(通过 HTTP 协议,在 Web 应用程序中),并将所有与域相关的工作推迟到域中的服务层,并将请求数据传递给它。所以控制器只影响域层的变化。另一方面,视图仅从域层请求数据。它也通过服务来做到这一点。所以视图从域中读取。因此,在您的代码中,视图应该只接收 $m 作为参数。
关于视图直接从域层提取数据有一个重要方面:如果视图期望简单地直接呈现从域接收到的数据,那么这意味着域将负责准备(例如格式化)视图的数据。但这意味着模型应该知道准备数据的格式。例如。域模型必须了解位于其边界之外的组件。这可不好。所以视图的作用是接收未准备好的数据,为网页格式化,并呈现出来。总之,视图执行表示逻辑。
在下面的链接中,Robert C. Martin 进行了精彩的演示,介绍了应如何在应用程序中涉及的组件之间分离关注点,从而形成良好的应用程序架构。将上述想法与演示文稿中提出的想法进行比较,您会发现它们之间存在两个差异。
第一个是,在视频中,表示层由演示者、视图模型和视图组成。而在上述 Web MVC 中,整个呈现逻辑仅由视图执行 - 这不是很好。
第二个是,在视频中,将要由表现层处理和显示的数据从控制器通过领域层推送到表现层。现在试着想象你自己,那个演示者依赖于一个交互器(实际上是一个应用程序服务,一个用例)。例如。从演示者指向领域层的箭头描述了一种依赖关系,而不是继承关系。那么演示者的角色就是将信息拉出领域层。与上面介绍的 Web MVC 的类比是显而易见的。
资源:
Keynote: Architecture the Lost Years - 由Robert Martin 提供的演示文稿,在Creative Commons Attribution ShareAlike 3.0 下获得许可。
示例 1(在 index.php 中):显示数据
用户在浏览器的地址栏中输入 URL http://localhost/questions/12345,并期望一个带有问题和多个 cmets 的 html 页面。因此 HTTP 方法是“GET”。所以不需要控制器,只需要一个视图(用于从域模型读取和显示数据)。
<?php
namespace Test\MvcExample;
use Lib\Http\Message\Response;
use Lib\Router\RouteCollection;
use Lib\Http\Message\ServerRequest;
use Lib\Http\MessageEmitter\ResponseEmitter;
/*
* -----------------------------------------
* Before dispatching by a front-controller.
* -----------------------------------------
*/
$routes = new RouteCollection();
$routes->add('GET', '/questions/{id:\d+}', [
'view' => [QuestionsView::class, 'index'],
]);
$request = new ServerRequest('GET', '/questions/12345' /* , other args */);
$response = new Response(/* args */);
/*
* ------------------------------------------------------------------
* Dispatched by a front-controller.
* Route params are saved into the attributes list of server request.
* ------------------------------------------------------------------
*/
$view = new QuestionsView(new Template(), new QuestionsService(), new CommentsService());
$response = $view->index($response, $request->getAttribute('id'));
/*
* ----------------------------------------
* After dispatching by a front-controller.
* ----------------------------------------
*/
$responseEmitter = new ResponseEmitter();
$responseEmitter->emitResponse($response);
示例 2(在 index.php 中):更新和显示数据
在 URL http://localhost/questions/edit/12345 处,用户在带有属性 action="/questions/update" 的表单中编辑问题,然后单击提交按钮“更新”。因此 HTTP 方法是“POST”。因此需要一个控制器(用于更新模型层)和一个视图(用于从模型层读取和显示数据)。
除了使用视图之外,还可以更改为其他要求。例如,要使用演示者和视图,两者都共享一个视图模型。
<?php
namespace Test\MvcExample;
use Lib\Http\Message\Response;
use Lib\Router\RouteCollection;
use Lib\Http\Message\ServerRequest;
use Lib\Http\MessageEmitter\ResponseEmitter;
/*
* -----------------------------------------
* Before dispatching by a front-controller.
* -----------------------------------------
*/
$routes = new RouteCollection();
$routes->add('POST', '/questions/update', [
'controller' => [QuestionsController::class, 'updateQuestion'],
'view' => [QuestionsView::class, 'getQuestionUpdated'],
]);
$request = new ServerRequest('POST', '/questions/update' /* , other args */);
$response = new Response(/* args */);
/*
* ----------------------------------
* Dispatched by a front-controller.
* ----------------------------------
*/
$questionsService = new QuestionsService();
$controller = new QuestionsController($questionsService);
$controller->updateQuestion($request);
$view = new QuestionsView(new Template(), $questionsService);
$response = $view->getQuestionUpdated($response, $request);
/*
* ----------------------------------------
* After dispatching by a front-controller.
* ----------------------------------------
*/
$responseEmitter = new ResponseEmitter();
$responseEmitter->emitResponse($response);