为什么要在 MVC 中分离模型和控制器


Why separate Model and Controller in MVC?

我试图理解Phalcon中的MVC模式。

在我当前的应用程序中,每个表只需要一个模板文件。该模板包含数据网格,SELECT的SQL语句,表单,添加/编辑/删除按钮,搜索框以及与数据库交互所需的所有内容,例如连接信息(当然使用包含尽可能多以防止重复代码(。(我编写了自己的复杂框架,它将xml模板转换为完整的HTML页面,包括所有生成的Javascript代码和CSS,而无需任何PHP来执行业务逻辑。我没有为数据库中的每个表提供特定的 PHP 类,而是只使用可以执行所有操作的标准操作脚本和数据库类(。不过,我正在尝试更多地遵守网络标准,所以我正在研究替代方案。

我尝试了 Phalcon 的 INVO 示例,并注意到公司页面需要一个公司模型、一个公司控制器、一个公司窗体和 4 个不同的视图。对我来说,与我现在的单个文件模板相比,拥有这么多不同的文件太混乱了。

我同意将表示与业务逻辑分开是有意义的,但我真的无法理解为什么模型和控制器需要在不同的类中。这似乎只会让事情变得更加复杂。似乎很多人已经很难决定模型中应该包含哪些内容以及控制器中应该包含哪些内容。例如,如果需要业务逻辑,则有时会将验证放在模型中,但否则放在控制器中,这似乎相当复杂。

我只在一个小团队中工作,所以"关注点分离"(除了演示和业务逻辑(对我们来说并不是最重要的事情。

  • 如果我决定不使用单独的模型和控制器类,我可能会遇到什么问题?

Phalcon的Phalcon'Mvc'Model类,你的模型应该扩展它,旨在提供一种与数据库交互的面向对象的方式。 例如,如果您的表是Shopping_Cart那么您可以将您的类命名为 ShoppingCart 。如果你的表有一个列"id",那么你将在类public $id;中定义一个属性。

Phalcon还为您提供了initialize()beforeValidationOnCreate()等方法。我承认这些方法可能会非常令人困惑,因为它们是如何工作的,何时运行,以及为什么你想首先调用它。

initialize()是不言自明的,并且每当启动课程时都会调用。在这里,您可以执行诸如setSource表的命名是否与类不同之类的操作,或者调用 belongsTohasMany 等方法来定义其与其他表的关系。

关系很有用,因为它可以轻松执行诸如在用户购物车中搜索产品之类的操作,然后使用 id,您将获得对Accounts表的引用,最后获取买家购物车中商品卖家的用户名。

我的意思是,当然,你可以对这类东西做单独的查询,但是如果你一开始就定义了表关系,为什么不呢?

就为数据库中的每个表定义专用模型的意义而言,您可以定义自己的自定义方法来管理模型。例如,您可能希望在购物车类中定义public function updateItemsInCart($productId,$quantity)方法。然后的想法是,每当您需要与购物车交互时,只需调用此方法,让模型担心业务逻辑。这不是编写一些也可以工作的复杂update查询。

是的,你可以把这种东西放在你的控制器里。但还有一个DRY(不要重复自己(原则。MVC 的目的是分离关注点。那么,如果您不想要专门的模型部分,为什么要首先遵循 MVC?好吧,也许你不需要一个。并非每个应用程序都需要模型。例如,此代码不使用任何: https://github.com/phalcon/blog

就个人而言,在使用 Phalcon 的模型结构一段时间后,我开始不喜欢他们的 1 层模型方法。我更喜欢实体、服务和存储库方向的多层模型。你可以在这里找到这样的代码:https://github.com/phalcon/mvc/tree/master/multiple-service-layer-model/apps/models但是,由于使用了太多的抽象,这可能会很快变得矫枉过正,并且难以管理。介于两者之间的解决方案通常是可行的。

但老实说,使用 Phalcon 的内置数据库适配器进行查询并没有错。如果您遇到一个很难编写的查询,没有人说您的每个模型都需要扩展Phalcon'Mvc'Model。写这样的东西仍然是完全合理的逻辑:

$pdo = 'Phalcon'DI::getDefault()->getDb()->prepare($sql);
foreach($params as $key => &$val)
{
    $pdo->bindParam($key,$val);
}
$pdo->setFetchMode(PDO::FETCH_OBJ);
$pdo->execute();
$results=$pdo->fetchAll();

模型非常灵活,没有"最佳"的方式来排列它们。"一切有效"的方法很好。以及"我希望我的模型对我可能想要的每个操作都有一个方法"。

我承认 invo 和 vokuro 半功能示例(仅用于演示目的(对于养成良好的模型设计习惯并不是那么好。我建议找一个真正以严肃的方式使用的软件,比如论坛的代码:https://github.com/phalcon/forum/tree/master/app/models

Phalcon仍然是一个相当新的框架,可以找到好的榜样。


正如您所提到的,关于将所有模型放在一个文件中,这完全没问题。请注意,如前所述,使用 initialize 中的setSource,您可以以不同于它们正在处理的表命名类。您还可以利用命名空间,并使类与表名匹配。您可以更进一步,创建一个类来使用 setSource 动态创建所有表。这是假设你想使用Phalcon的数据库适配器。在PDO之上编写自己的代码或使用其他框架的数据库适配器并没有错。

正如你所说,在一个小团队中,关注点分离对你来说并不那么重要,所以你可以在没有模型目录的情况下逃脱。如果有任何帮助,您可以使用类似于我为数据库适配器编写的内容:http://pastie.org/10631358
然后你会把它扔到你的应用/库目录中。在配置中加载组件,如下所示:

$di->set('easySQL', function(){
    return new EasySQL();
});

然后在基本模型中,您将输入:

public function easyQuery($sql,$params=array())
{
    return $this->di->getEasySQL()->prepare($sql,$params)->execute()->fetchAll();
}

最后,从模型中,您可以执行以下简单操作:

$this->easyQuery($sqlString,array(':id'=>$id));

或者全局定义函数,以便控制器也可以使用它,等等。


还有其他方法可以做到这一点。希望我的"EasySQL"组件能让你更接近你的目标。根据您的需求,也许我的"EasySQL"组件只是很长的编写方式:

$query = new 'Phalcon'Mvc'Model'Query($sql, $di);
$matches=$query->execute($params);

如果没有,也许你正在寻找更多的东西

$matches=MyModel::query()->where(...)->orderBy(...)->limit(...)->execute();

这完全没问题。

模型、视图和控制器被设计为分离每个过程。

不仅仅是Phalcon使用这种方法,今天几乎PHP框架都使用这种方法。

模型应该是你保存或更新东西的地方,它不应该依赖于其他组件,而应该依赖于数据库表本身(仅!(,你只是传递一些布尔值(如果完成CRUD(或数据库记录查询。

您可以使用控制器来执行此操作,但是如果您要创建多个控制器并且正在执行相同的过程,则最好使用模型中的 1 个函数来调用和传入数据。

此外,控制器应该是中间的脚本,它应该是调度每个请求的脚本,在保存记录时,当您需要使用 Model 时,如果您需要排队的东西,您需要调用一些事件,最后使用 json 响应或显示您的模板适配器 (volt( 进行响应。

我们缩短了 M-V-C 一词,但实际上,我们正在处理这些:

HTTP 请求 -> 已加载服务(包括错误处理程序(->路由器 ->(路由解析器(->(

调度到指定的控制器(->控制器 ->(使用 JSON 或模板适配器响应 |调用模型 |呼叫 ACL |电话会议 |队列 |接口请求 |等等...->结束。