Best MVC Practices
最优的MVC布局策略
Although Model-View-Controller (MVC) is known by nearly every Web developer, how to properly use MVC in real application development still eludes many people. The central idea behind MVC is code reusability and separation of concerns. In this section, we describe some general guidelines on how to better follow MVC when developing a Yii application.
尽管MVC几乎为每一个WEB开发者所知道,但是如何在真实的应用开发中合理的使用它,很多人就不能说得很清楚了。MVC的核心思想是:代码的重用性与低耦合性。在这一部分,我们简要地描述一些关键概念,以帮助你在开发YII应用的过程中能更好的使用MVC。
To better explain these guidelines, we assume a Web application consists of several sub-applications, such as
- front end: a public-facing website for normal end users;
- back end: a website that exposes administrative functionality for managing the application. This is usually restricted to administrative staff;
- console: an application consisting of console commands to be run in a terminal window or as scheduled jobs to support the whole application;
- Web API: providing interfaces to third parties for integrating with the application.
为了更准确的解释这些概念,我们假设一个WEB应用包含一定数量的子应用,例如:
- 前端:一个面向最终用户的网站;
- 后端:一个一般只供管理人员操作的管理应用的网站。
- 控制台:一个由控制台命令构成的应用,运行于一个终端窗口或者作为计划任务以支持整个应用。
- WEB API:提供给第三方接口以接入应用。
The sub-applications may be implemented in terms of modules, or as a Yii application that shares some code with other sub-applications.
子应用通常用模块的方式来实现,或者作为一个和其他子应用公用一些代码的YII应用。
1. Model
1.数据层(数据处理类文件)
Models represent the underlying data structure of a Web application. Models are often shared among different sub-applications of a Web application. For example, a LoginForm
model may be used by both the front end and the back end of an application; a News
model may be used by the console commands, Web APIs, and the front/back end of an application. Therefore, models
-
should contain properties to represent specific data;
-
should contain business logic (e.g. validation rules) to ensure the represented data fulfills the design requirement;
-
may contain code for manipulating data. For example, a
SearchForm
model, besides representing the search input data, may contain asearch
method to implement the actual search.
数据层承载着WEB应用的核心数据结构。数据层通常被众多的子应用所共享。例如,一个用户登录数据层很可能同时被前端和后端应用所公用。一个新闻数据层很可能被控制台命令行,WEB APIS,和其他网站前后台子应用所使用。因此,数据层
- 应该包含可以代表特定数据的属性。
- 应该包含商业逻辑(例如验证规则)以保证用户提交的数据符合应用设计者的要求。
- 很可能包含操作数据的代码。例如,一个搜索数据层,除了含有搜索输入的数据,很有可能还包含实现这个搜索的方法(代码)。
Sometimes, following the last rule above may make a model very fat, containing too much code in a single class. It may also make the model hard to maintain if the code it contains serves different purposes. For example, aNews
model may contain a method named getLatestNews
which is only used by the front end; it may also contain a method named getDeletedNews
which is only used by the back end. This may be fine for an application of small to medium size. For large applications, the following strategy may be used to make models more maintainable:
-
Define a
NewsBase
model class which only contains code shared by different sub-applications (e.g. front end, back end); -
In each sub-application, define a
News
model by extending fromNewsBase
. Place all of the code that is specific to the sub-application in thisNews
model.
有时候,按照上面最后一条所说得做的话,可能导致你的数据层十分的臃肿,因为在一个类中包含了太多的代码。而且如果这个数据层包含多套业务逻辑的话,这个数据层维护起来会相当的麻烦。例如,aNews这个数据层可能包含一个名叫getLastNews的方法,这个方法只会被前端用户所用到。同时这个数据层可能还包含一个叫做getDeletedNews的方法,这个方法只会被后端管理员用到。如果这个应用的规模很小或者中等还好,对于大型应用,以下的策略可能使你的数据层更易维护:
- 定义一个NewsBase的数据类文件,这个文件只包含不同应用都需要用到的代码。(例如前后端);
- 在每一个子应用中,定义一个News数据层类文件来继承NewsBase这个类。在这个News类文件中只写当前应用需要使用到得方法。
So, if we were to employ this strategy in our above example, we would add a News
model in the front end application that contains only the getLatestNews
method, and we would add another News
model in the back end application, which contains only the getDeletedNews
method.
所以,如果我们采取这个策略,应用到上面的例子的话,我们就先写这个NewsBase,然后再在前端写一个News类来继承NewsBase,在这个News类中只包含getLastNews方法,同样的方法,在后端也写一个BackNews来继承这个NewBase,在这个BackNews中只写后台管理员才用到的getDeletedNewsz这个方法。
In general, models should not contain logic that deals directly with end users. More specifically, models
-
should not use
$_GET
,$_POST
, or other similar variables that are directly tied to the end-user request. Remember that a model may be used by a totally different sub-application (e.g. unit test, Web API) that may not use these variables to represent user requests. These variables pertaining to the user request should be handled by the Controller. -
should avoid embedding HTML or other presentational code. Because presentational code varies according to end user requirements (e.g. front end and back end may show the detail of a news in completely different formats), it is better taken care of by views.
总得来说,数据层不应该直接处理终端用户的逻辑,详细一点的说,数据层
- 不应该使用$_GET,$_POST,或者其他相似的直接跟终端用户有关联的变量。记住,一个数据层可能被完全不同的子应用所使用,而这些子应用不可能都用相同的变量来处理用户的请求。和用户请求相关的变量应该都交给控制器来处理。
- 应该避免嵌入HTML或者其他“显示代码”。因为显示代码根据用户请求不同而不同(例如前段和后端可能以完全不同的格式来显示新闻),而这最好交给视图层来完成。
2. View
2.视图层
Views are responsible for presenting models in the format that end users desire. In general, views
-
should mainly contain presentational code, such as HTML, and simple PHP code to traverse, format and render data;
-
should avoid containing code that performs explicit DB queries. Such code is better placed in models.
-
should avoid direct access to
$_GET
,$_POST
, or other similar variables that represent the end user request. This is the controller's job. The view should be focused on the display and layout of the data provided to it by the controller and/or model, but not attempting to access request variables or the database directly. -
may access properties and methods of controllers and models directly. However, this should be done only for the purpose of presentation.
Views can be reused in different ways:
-
Layout: common presentational areas (e.g. page header, footer) can be put in a layout view.
-
Partial views: use partial views (views that are not decorated by layouts) to reuse fragments of presentational code. For example, we use
_form.php
partial view to render the model input form that is used in both model creation and updating pages. -
Widgets: if a lot of logic is needed to present a partial view, the partial view can be turned into a widget whose class file is the best place to contain this logic. For widgets that generate a lot of HTML markup, it is best to use view files specific to the widget to contain the markup.
-
Helper classes: in views we often need some code snippets to do tiny tasks such as formatting data or generating HTML tags. Rather than placing this code directly into the view files, a better approach is to place all of these code snippets in a view helper class. Then, just use the helper class in your view files. Yii provides an example of this approach. Yii has a powerful CHtml helper class that can produce commonly used HTML code. Helper classes may be put in an autoloadable directory so that they can be used without explicit class inclusion.
视图层的任务是根据用户的请求来呈现数据层的内容,视图层
- 应该主要包含“显示代码”,例如HTML,和简单的PHP代码,作为穿插,格式化数据或者发送数据。
- 应该避免直接执行数据库操作语句,这样的操作最好放在数据层。
- 应该避免直接去操作用户请求相关变量如$_GET,$_POST,这是控制器的活。视图层应该专注于显示或者布局从控制器层或者数据层拿过来的数据,而不是去试图操作用户请求变量或者数据库。
- 可能会直接地操作数据层或者控制器层的属性或者方法,但是这些只能在呈现数据的前提之下。
- 布局:公共展示部分(例如页头和页脚)可以放在layout视图中。
- 部分视图:使用部分视图(不在公共展示部分),以重复利用某“显示代码”。例如我们可以使用_form.php部分视图来提交数据创建业务和数据修改业务中得数据层表单。
- 挂件:如果很多逻辑都需要展示一个“部分视图”,那么我们可以将这个“部分视图”转换为一个“挂件”,这个挂件的类文件就是放这个逻辑的最好的地方。对于生成很多HTML标记的挂件,最好是使用特定的视图文件来包含这些标记。
3. Controller
3.控制器
Controllers are the glue that binds models, views and other components together into a runnable application. Controllers are responsible for dealing directly with end user requests. Therefore, controllers
-
may access
$_GET
,$_POST
and other PHP variables that represent user requests; -
may create model instances and manage their life cycles. For example, in a typical model update action, the controller may first create the model instance; then populate the model with the user input from
$_POST
; after saving the model successfully, the controller may redirect the user browser to the model detail page. Note that the actual implementation of saving a model should be located in the model instead of the controller. -
should avoid containing embedded SQL statements, which are better kept in models.
-
should avoid containing any HTML or any other presentational markup. This is better kept in views.
In a well-designed MVC application, controllers are often very thin, containing probably only a few dozen lines of code; while models are very fat, containing most of the code responsible for representing and manipulating the data. This is because the data structure and business logic represented by models is typically very specific to the particular application, and needs to be heavily customized to meet the specific application requirements; while controller logic often follows a similar pattern across applications and therefore may well be simplified by the underlying framework or the base classes.
控制器是粘合数据层和视图层和其他组件的胶水。控制器负责处理用户的直接请求,因此,控制器
- 可以处理$_GET,$_POST和其他代表用户请求的变量。
- 可以创建数据层实例,并管理他们的生存周期。例如在一个典型的更新操作中,控制器可能最先创建数据层的实例;接下来接收来自用户POST过来的数据;成功更新数据之后,控制器层可能会将用户导向详情页。注意,实际的执行数据更新的操作应该在数据层完成,而非在控制器层。
- 应该避免包含SQL语句的嵌入,最好把他们放置在数据层。
- 应该避免包含任何HTML或者其他的“显示标记”,最好把他们交给视图层来处理。
在一个优秀的比较优秀的MVC应用中,控制器层同层非常精简,只包含数十行代码;而数据层通常会非常臃肿,他们包含了展示和操作数据的代码。这时因为特定的应用所要求的数据结构和数据层需要处理的商业逻辑都不相同,因而需要专门定制以满足需求。而控制器层通常依据一些应用间比较相似的逻辑,因此被框架或者基类预先简化了。