【问题标题】:Why separate Model and Controller in MVC?为什么要在 MVC 中分离模型和控制器?
【发布时间】:2016-03-19 09:11:54
【问题描述】:

我正在尝试理解 Phalcon 中的 MVC 模式。

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

我尝试了 Phalcon 的 INVO 示例,发现 Companies 页面需要一个 Companies 模型、一个 CompaniesController、一个 CompaniesForm 和 4 个不同的视图。对我来说,与我现在的单个文件模板相比,拥有这么多不同的文件太令人困惑了。

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

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

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

【问题讨论】:

  • 如果您设计的只是一个基本的 CRUD 系统,一个控制器只有一个模型,那么您不需要这种分离....但是对于业务系统,每个控制器可能正在访问来自几个不同模型的数据,一个模型可能被几个不同的控制器引用......然后分离很重要
  • 控制器是模型和客户端之间的中介。您将控制器与模型层分开,因为可能有不同的方式来请求相同的模型:例如 HTTP API、CLI、Some-other-crazy-api。在所有这 3 种情况下,您都通过不同的 API 与相同的模型进行交互。这意味着 - API 对您的模型的了解越少 - 越好。

标签: php model-view-controller phalcon


【解决方案1】:

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

Phalcon 还为您提供initialize()beforeValidationOnCreate() 等方法。我承认这些方法在它们如何工作、何时运行以及为什么你一开始想要调用它时会让人非常困惑。

initialize() 是不言自明的,只要你的课程开始就会被调用。如果您的表与您的类的名称不同,您可以在这里执行setSource 之类的操作,或者调用belongsTohasMany 之类的方法来定义其与其他表的关系。

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

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

就为数据库中的每个表定义专用模型的意义而言,您可以定义自己的自定义方法来管理模型。例如,您可能希望在 ShoppingCart 类中定义 public function updateItemsInCart($productId,$quantity) 方法。然后的想法是,每当您需要与 ShoppingCart 交互时,您只需调用此方法并让 Model 担心业务逻辑。这不是编写一些复杂的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();

模型非常灵活,没有“最好”的排列方式。 “无论什么都行”的方法很好。以及“我希望我的模型对我可能想要的每个操作都有一个方法”。

我承认invovokuro 半功能示例(仅为演示目的而构建)对于养成良好的模型设计习惯并不是很好。我建议找一个真正认真使用的软件,比如论坛的代码:https://github.com/phalcon/forum/tree/master/app/models

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


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

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

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

然后在您的 Basemodel 中放置:

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();

这很好。

【讨论】:

    【解决方案2】:

    Model、View 和 Controller 旨在分离每个进程。

    不仅 Phalcon 使用这种方法,现在几乎 PHP 框架也使用这种方法。

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

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

    另外,Controllers应该是中间的脚本,它应该是分发每个请求的那个,当你保存记录时,当你需要使用Model时,如果你需要排队的东西,你需要调用一些事件,最后使用 json 响应或显示您的模板适配器(伏特)进行响应。

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

    HTTP 请求 -> 加载的服务(包括错误处理程序)-> 路由器 ->(路由解析器)->(调度到指定的控制器)-> 控制器 ->(使用 JSON 或模板适配器响应 | 调用模型 | Call ACL | Call Event | Queue | API Request | etc....) -> end.

    【讨论】:

      猜你喜欢
      • 2012-09-05
      • 1970-01-01
      • 2011-05-06
      • 2013-11-27
      • 1970-01-01
      • 2010-12-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多