且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

MVC:这是初始化的正确顺序吗?调用 MVC 层

更新时间:2023-02-24 12:02:00

在桌面应用程序的原始 MVC 模式(由 Trygve Reenskaug 于 1979 年提出)中,控制器更新模型,模型通知视图有关更改,然后视图从中提取数据.此外,M、V &C 组件被认为与窗口中的单个控件(按钮、文本框、复选框等)相关.如果屏幕上有 15 个控件,那么每个控件都附加"了一个 MVC.

In the original MVC pattern for the desktop apps (presented by Trygve Reenskaug in 1979), the controller updates the model, the model notifies the view about the changes, and then the view pulls its data from it. Also, the M, V & C components were thought as related to a single control in a window (a button, a textbox, a checkbox, etc). If you had 15 controls on screen, then each of them had an MVC "attached" to it.

在 Web 应用程序中,不存在通知步骤(从模型到视图).但其余的 M、V & 之间的关系C 组件得到维护.因此,在 Web MVC 中,控制器更新模型,视图从中提取数据.此外,所有三个组件都与网页相关,而不是与网页上的单个控件相关.

In the web applications, the notification step (from model to view) is not present. But the rest of the relations between the M, V & C components is maintained. So, in a web MVC, the controller updates the model and the view pulls its data from it. Also, all three components are related to a web page, not to a single control on it.

控制器和视图都不应使用域对象,例如实体(作为域模型的一部分)直接.它们仅通过应用服务(也称为用例)与域模型(单独)通信.这些服务可以将(多个)任务委托给(多个)域服务.进而利用域对象(实体).如果需要外部组件和/或服务(例如持久层中的组件和/或服务,例如存储库、数据映射器),那么域模型和它们之间的连接是通过使用相应的接口.这些也是领域层的一部分.

Both, controller and view, should never use domain objects, e.g. entities (as part of the domain model) directly. They communicate (separately) with the domain model only through application services (also named use cases). These services may delegate (multiple) tasks to (multiple) domain services. Which, in turn make use of the domain objects (entities). If external components and/or services are needed (like the ones in the persistence layer, e.g. repositories, datamappers, for example), then the connection between the domain model and them is made through the use of corresponding interfaces. These also are part of the domain layer.

视图不应该访问控制器.为什么要这样做?控制器解释用户发送的请求(通过 HTTP 协议,在 Web 应用程序中)并将所有域相关的工作推迟到域中的服务层,将请求数据传递给它.所以控制器只影响域层的变化.另一方面,视图仅从域层请求数据.它也通过服务来做到这一点.所以视图从域中读取.因此,在您的代码中,视图应该只接收 $m 作为参数.

The view should never access the controller. Why should it, at all? The controller interprets the request sent by the user (through the HTTP protocol, in web apps) and defers all the domain related work to the service layer in the domain, passing it the request data. So the controller only effects changes in the domain layer. On the other side, the view requests data only from the domain layer. It does this through services as well. So the view reads from the domain. Therefore, in your code, the view should receive only the $m as argument.

关于直接从域层拉取数据的视图有一个重要方面:如果视图希望简单地直接呈现从域接收到的数据,那么这意味着域将负责准备(例如格式化)视图的数据.但这意味着模型应该知道准备的数据采用哪种格式.例如.域模型必须了解位于其边界之外的组件.这不会很好.因此,视图的作用是接收未准备好的数据,为网页格式化并呈现它.总之,视图执行呈现逻辑.

There is an important aspect regarding the view pulling data directly from the domain layer: If the view would expect to simply, directly present the data received from the domain, then this would mean that the domain would be responsible with the preparation (e.g. formatting) of the data for the view. But that would imply, that the model should know, in which format is to be the data prepared. E.g. the domain model would have to have knowledge about components residing outside its boundaries. This would not be good. So the role of the view is to receive unprepared data, to format it for the web page, and to present it. In a word, the view executes the presentation logic.

下面的链接中是 Robert C. Martin 的精彩演讲,介绍了如何在应用程序中涉及的组件之间分离关注点,从而形成良好的应用程序架构.将上述想法与演示文稿中提出的想法进行比较,您会发现它们之间存在两个不同之处.

In a link bellow is a great presentation by Robert C. Martin about how the concerns should be separated between the components involved in an application, resulting in a good application architecture. Making a parallel between the above ideas and the ones presented in the presentation, you will discover, that there are two differences between them.

第一个是,在视频中,展示层由展示器、视图模型和视图组成.而在上述 Web MVC 中,整个呈现逻辑仅由视图执行 - 这不太好.

The first one is, that, in the video, the presentation layer consists of a presenter, a view model and a view. Whereas in the web MVC described above, the whole presentation logic is performed only by the view - which is not so good.

第二种是,在视频中,表示层要处理和显示的数据从控制器通过域层推送到表示层.现在试着想象一下,演示者依赖于交互者(实际上是一个应用服务,一个用例).例如.从呈现者指向领域层的箭头描述了一种依赖关系,而不是一种继承关系.然后演示者的作用是将信息拉出域层.与上面介绍的 Web MVC 的类比是显而易见的.

The second one is, that, in the video, the data to be processed and displayed by the presentation layer is pushed from the controller through the domain layer to the presentation layer. Now try to imagine yourself, that that presenter depends on an interactor (which, actually is an application service, a use case). E.g. that the arrow pointing from the presenter to the domain layer describes a dependency relation, instead of an inheritance one. Then the role of the presenter will be to pull information out of the domain layer. The analogy with the web MVC presented above is then obvious.

资源:

主题演讲:失落岁月的建筑 - 给出的演示文稿由 Robert Martin 提供,根据 Creative Commons Attribution ShareAlike 3.0.

Keynote: Architecture the Lost Years - Presentation given by Robert Martin, licensed under a Creative Commons Attribution ShareAlike 3.0.

示例 1(在 index.php 中):显示数据

用户在浏览器的地址栏中引入 URL http://localhost/questions/12345 并期望一个带有问题和多个评论的 html 页面.因此 HTTP 方法是GET".所以不需要控制器,只需要一个视图(用于从域模型中读取和显示数据).

The user introduces the URL http://localhost/questions/12345 in the address bar of the browser and expects a html page with a question and multiple comments for it. Therefore the HTTP method is "GET". So no controller is needed, but only a view (for reading and displaying data from the domain model).

<?php

namespace TestMvcExample;

use LibHttpMessageResponse;
use LibRouterRouteCollection;
use LibHttpMessageServerRequest;
use LibHttpMessageEmitterResponseEmitter;

/*
 * -----------------------------------------
 * 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".因此需要一个控制器(用于更新模型层)和一个视图(用于从模型层读取和显示数据).

At URL http://localhost/questions/edit/12345 the user edits the question in a form with attribute action="/questions/update" and clicks the submit button "Update". Therefore the HTTP method is "POST". So both a controller (for updating the model layer) and a view (for reading and displaying data from the model layer) are needed.

您可以更改为其他要求,而不是仅使用视图.例如,要使用演示者和视图,两者共享一个视图模型.

Instead of only using a view, one can change to other requirements. For example, to use a presenter and a view, both sharing a view-model.

<?php

namespace TestMvcExample;

use LibHttpMessageResponse;
use LibRouterRouteCollection;
use LibHttpMessageServerRequest;
use LibHttpMessageEmitterResponseEmitter;

/*
 * -----------------------------------------
 * 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);