这是我在 PHP 中使用 MVC/关注点分离模式创建网站时采用的方法:
我使用的框架包含三个基本部分:
- 模型 - PHP 类。我向它们添加方法来获取和保存数据。每个
模型代表系统中不同类型的实体:用户、页面、
博文
- 视图 - Smarty 模板。这是 html 所在的位置。
- 控制器 - PHP 类。这些是应用程序的大脑。通常
站点中的 url 调用类的方法。 example.com/user/show/1 会
调用 $user_controller->show(1) 方法。控制器取出数据
模型并将其提供给视图。
这些作品中的每一个都有特定的工作或“关注点”。 模型的工作是为数据提供一个干净的接口。通常,站点的数据存储在 SQL 数据库中。我向模型中添加了用于获取数据和保存数据的方法。
视图的工作是显示数据。所有 HTML 标记都在视图中。处理数据表的斑马条纹的逻辑进入视图。处理日期显示格式的代码进入视图。我喜欢为视图使用 Smarty 模板,因为它提供了一些很好的功能来处理类似的事情。
控制器的工作是充当用户、模型和视图之间的中介。
让我们看一个例子,说明它们是如何结合在一起的以及好处在哪里:
想象一个简单的博客网站。主要数据是帖子。另外,假设该网站跟踪查看帖子的次数。我们将为此创建一个 SQL 表:
posts
id date_created title body hits
现在假设您想显示 5 个最受欢迎的帖子。以下是您可能会在非 MVC 应用程序中看到的内容:
$sql = "SELECT * FROM posts ORDER BY hits DESC LIMIT 5";
$result = mysql_query($sql);
while ($row = mysql_fetch_assoc($result)) {
echo "<a href="post.php?id=$row['id']">$row['title']</a><br />";
}
这个 sn-p 非常简单,并且在以下情况下运行良好:
- 这是您想要显示最受欢迎帖子的唯一地方
- 你永远不想改变它的外观
- 您永远不会决定改变什么是“热门帖子”
假设您想在主页上显示 10 个最受欢迎的帖子,并在子页面的侧边栏中显示 5 个最受欢迎的帖子。您现在需要复制上面的代码,或者将其放入包含逻辑的包含文件中以检查其显示位置。
如果您想更新主页的标记以向今天创建的帖子添加“新帖子”类怎么办?
假设您认为某个帖子很受欢迎,因为它有很多 cmets,而不是点击量。数据库将更改以反映这一点。现在,您的应用程序中显示热门帖子的每个位置都必须更新以反映新逻辑。
您开始看到一个复杂形式的雪球。很容易看出在项目过程中事情如何变得越来越难以维护。此外,还要考虑多个开发人员在一个项目上工作时的复杂性。在向输出中添加类时,设计人员是否必须咨询数据库开发人员?
采用 MVC 方法并在应用程序中实施关注点分离可以缓解这些问题。理想情况下,我们希望将其分为三个区域:
- 数据逻辑
- 应用逻辑
- 和显示逻辑
让我们看看如何做到这一点:
我们将从模型开始。我们将有一个$post_model 类并给它一个名为get_popular() 的方法。此方法将返回一个帖子数组。此外,我们将给它一个参数来指定要返回的帖子数:
post_model.php
class post_model {
public function get_popular($number) {
$sql = "SELECT * FROM posts ORDER BY hits DESC LIMIT $number";
$result = mysql_query($sql);
while($row = mysql_fetch_assoc($result)) {
$array[] = $row;
}
return $array;
}
}
现在对于主页,我们有一个控制器,我们将其称为“主页”。假设我们有一个 url 路由方案,它在请求主页时调用我们的控制器。它的工作是获取热门帖子并将它们提供给正确的视图:
home_controller.php
class home_controller {
$post_model = new post_model();
$popular_posts = $post_model->get_popular(10);
// This is the smarty syntax for assigning data and displaying
// a template. The important concept is that we are handing over the
// array of popular posts to a template file which will use them
// to generate an html page
$smarty->assign('posts', $popular_posts);
$smarty->view('homepage.tpl');
}
现在让我们看看视图会是什么样子:
homepage.tpl
{include file="header.tpl"}
// This loops through the posts we assigned in the controller
{foreach from='posts' item='post'}
<a href="post.php?id={$post.id}">{$post.title}</a>
{/foreach}
{include file="footer.tpl"}
现在我们有了应用程序的基本部分,并且可以看到关注点的分离。
模型关注的是获取数据。它了解数据库,了解 SQL 查询和 LIMIT 语句。它知道它应该交还一个不错的数组。
控制器知道用户的请求,他们正在查看主页。它知道主页应该显示 10 个热门帖子。它从模型中获取数据并将其提供给视图。
视图 知道帖子数组应该显示为一系列 achor 标签,后面带有 break 标签。它知道一个帖子有一个标题和一个ID。它知道应该将帖子的标题用于锚文本,并且应该在 href 中使用帖子 ID。视图也知道页面上应该显示页眉和页脚。
提及每件不知道的内容也很重要。
模型不知道热门帖子显示在主页上。
控制器和视图不知道帖子存储在 SQL 数据库中。
controller 和 model 并不知道主页上帖子的每个链接后面都应该有一个中断标记。
因此,在这种状态下,我们在数据逻辑(模型)、应用程序逻辑(控制器)和显示逻辑(视图)之间建立了清晰的关注点分离。那么现在怎么办?我们采用了一个简短的简单 PHP sn-p 并将其分解为三个令人困惑的文件。这给了我们什么?
让我们看看关注点分离如何帮助我们解决上述问题。重申一下,我们希望:
- 在子页面的侧边栏中显示热门帖子
- 使用额外的 CSS 类突出显示新帖子
- 更改“热门帖子”的基本定义
为了在侧边栏中显示热门帖子,我们将在子页面中添加两个文件:
子页面控制器...
subpage_controller.php
class subpage_controller {
$post_model = new post_model();
$popular_posts = $post_model->get_popular(5);
$smarty->assign('posts', $popular_posts);
$smarty->view('subpage.tpl');
}
...和一个子页面模板:
subpage.tpl
{include file="header.tpl"}
<div id="sidebar">
{foreach from='posts' item='post'}
<a href="post.php?id={$post.id}">{$post.title}</a>
{/foreach}
</div>
{include file="footer.tpl"}
新的子页面 controller 知道子页面应该只显示 5 个热门帖子。子页面 view 知道子页面应该将帖子列表放在侧边栏 div 中。
现在,我们要在主页上突出显示新帖子。我们可以通过修改homepage.tpl来实现。
{include file="header.tpl"}
{foreach from='posts' item='post'}
{if $post.date_created == $smarty.now}
<a class="new-post" href="post.php?id={$post.id}">{$post.title}</a>
{else}
<a href="post.php?id={$post.id}">{$post.title}</a>
{/if}
{/foreach}
{include file="footer.tpl"}
视图在这里处理所有用于显示热门帖子的新逻辑。 controller 和 model 不需要知道任何有关该更改的信息。这纯粹是显示逻辑。子页面列表继续像以前一样显示。
最后,我们想更改热门帖子的含义。我们希望它不是基于页面获得的点击次数,而是基于帖子获得的 cmets 数量。我们可以将该更改应用于模型:
post_model.php
class post_model {
public function get_popular($number) {
$sql = "SELECT * , COUNT(comments.id) as comment_count
FROM posts
INNER JOIN comments ON comments.post_id = posts.id
ORDER BY comment_count DESC
LIMIT $number";
$result = mysql_query($sql);
while($row = mysql_fetch_assoc($result)) {
$array[] = $row;
}
return $array;
}
}
我们增加了“热门帖子”逻辑的复杂性。但是,一旦我们在 模型 中进行了这种更改,新的逻辑就会在一个地方应用到所有地方。主页和子页面,在没有其他修改的情况下,现在将显示基于 cmets 的热门帖子。我们的设计师不需要参与其中。标记不受影响。
希望这提供了一个令人信服的示例,说明如何分离数据逻辑、应用程序逻辑和显示逻辑的关注点,可以使您的应用程序开发更加容易。一个领域的变化往往对其他领域的影响较小。
遵循这个约定并不是让您的代码自动变得完美的灵丹妙药。毫无疑问,你会遇到一些不太清楚应该在哪里分离的问题。最后,这一切都与管理应用程序中的复杂性有关。
您应该充分考虑如何构建模型。他们将提供什么样的接口(参见 Gregory 关于合同的回答)?控制器和视图期望使用什么数据格式?提前考虑这些事情会让事情变得更容易。
此外,在开始一个项目以使所有这些部分很好地协同工作时,可能会有一些开销。有许多框架为模型、控制器、模板引擎、url 路由等提供构建块。有关 PHP MVC 框架的建议,请参阅 SO 上的许多其他帖子。这些框架将使您启动并运行,但您作为开发人员负责管理复杂性并实施关注点分离。
我还要注意,上面的代码 sn-ps 只是简化的示例。他们可能(很可能)有错误。但是,它们在结构上与我在自己的项目中使用的代码非常相似。