MVC +方案与胖控制器

MVC +方案与胖控制器


现代的PHP框架(Symphony,Laravel,下文中无处不在)令人信服地表明,实现Model-View-Controller模式并不是那么简单。 由于某种原因,所有实现都容易受到所有人,开发人员以及框架本身的谴责。


为什么会这样呢? 有什么办法可以解决这个问题? 让我们做对。


术语学


  • 模型-模型(所需数据的整形)
  • 视图-视图(模型数据修饰器)
  • 控制器-控制器(根据需要提供模型视图协调器)
  • 模板-演示模板
  • 渲染-渲染(演示图像的形成,设计)
  • 渲染器-渲染器(形状,演示图像的设计器)

厚控制器


这是一个典型的胖控制器:


 class UserController { /** *   *      ID */ public function actionUserHello($userId) { //         ( ) $user = UserModel::find($userId); //       -   $name = $user->firstName.' '.$user->lastName; //         $view = new View('hello', ['name' => $name]); //  ( )     return $view->render(); } } 

我们看到了什么? 我们看到了香醋! 在控制器中,所有可能的东西都混在一起了-模型和表示,实际上还有控制器本身!


我们看到模型的名称和模板紧密地连接到控制器中。 这不是嗡嗡声。 我们在控制器中看到了对模型数据的操纵-由名字和姓氏组成的全名。 这不是嗡嗡声。


还有一件事:我们没有明确看到此示例,但它是隐式的。 即:只有一种渲染方式(图像形成)! 只有一个:根据php文件中的模板! 如果我要pdf? 如果我不想在文件中,而是在php行中? 我在数百个小模板上进行了精心设计的设计。 我不得不自己脱口而出字符串模板的渲染器。 我当然没有过热,但是原则上是这样。


简短摘要:


现代框架在每个人的MVC实施中都有共同的缺陷:
  1. 对MVC-view(View)的狭义解释“使用PHP文件中的模板进行 查看 ”,而不是“使用任何渲染器进行查看”。
  2. 只能将MVC模型的狭义解释为“数据库模型域”,而不是“任何用于表示的数据编译器”。
  3. 他们激发了同时包含所有逻辑(业务,演示和交互)的所谓“ Thick Controllers”的使用。 这完全破坏了MVC的主要目标-三合会组件之间的责任划分。

为了解决这些缺点,最好仔细研究一下MVC的组件。


视图是一个渲染器


看一下第一个缺点:


  1. 对MVC-view(View)的狭义解释“使用PHP文件中的模板进行 查看 ”,而不是“使用任何渲染器进行查看”。

在这里,一切都非常简单-问题声明本身已经表明了解决问题的方法。 我们只需要说任何渲染器都可以使用该视图。 为此,只需将新的renderer属性添加到View类:


 class View { public $template, $data, $renderer; public function __costruct($template, $data, $renderer = NULL) {} } 

因此,我们为视图定义了一个新的renderer属性。 在最一般的情况下,此属性的值可以是任何callable函数,该函数可形成使用已传输的模板传输到其的数据的图像。


大多数应用程序仅使用一个渲染器,即使使用多个渲染器,还是首选其中之一。 因此,假设存在一些默认渲染器,则将renderer参数定义为可选。


简单吗? 很简单 其实不是那么简单。 事实是,MVC中的View与框架中的View并不完全相同。 没有模板,框架中的View无法生存。 但是由于某种原因在MVC中使用的View对这些相同的模板一无所知。 怎么了 是的,因为对于MVC View ,这是将模型数据转换为图像的任何转换 ,而不仅仅是模板引擎。 当我们在请求处理程序中编写如下代码时:


 $name = ' '; return "Hello, {$name}!"; 

甚至:


 $return json_encode($name); // Ajax response 

然后我们真正定义了MVC中的View,而没有涉及框架中的任何View


但是现在一切都非常简单:框架中的View ,这是MVC中View的子集。 而且,一个非常狭窄的子集,即,它只是基于PHP文件的模板引擎。


摘要:它是 ,即 数据图像的任何装饰器都是MVC中的View。 那些位于框架中的View只是一种


域模型/视图模型(ViewModel / DomainModel)


现在来看第二个缺点:


  1. 只能将MVC模型的狭义解释为“数据库模型域”,而不是“任何用于表示的数据编译器”。

对每个人来说显而易见的是,MVC模型是由其他部分组成的复杂事物。 社区同意将模型分解为两个组件:域模型(DomainModel)和表示模型(ViewModel)。


域模型是存储在数据库中的模型,即 归一化模型数据。 在不同的字段中输入“名字”和“姓氏”。 框架仅被模型的这一特定部分所占据,这是因为数据存储是它自己的世界,并且经过了深入研究。


但是,应用程序需要聚合的数据而不是规范化的数据。 域数据必须编译为图像,例如:“ Hello,Ivan!”,“ Dear Ivan Petrov!”,甚至“ For Ivan a Petrov a !”。 这些转换后的数据称为另一个模型-表示模型。 因此,现代框架仍然忽略了模型的这一部分。 它被忽略,因为在如何处理上没有达成共识。 而且,如果框架不提供解决方案,那么程序员将采用最简单的方式-他们将视图模型扔到控制器中。 他们得到了讨厌但不可避免的胖控制器!


总计:为了实现MVC,您需要实现一个视图模型。 没有其他选择。 鉴于表示形式及其数据可以是任何形式,我们声明存在问题。


方案与胖控制器


框架的最后一个缺点是:


  1. 他们激发了同时包含所有逻辑(业务,演示和交互)的所谓“ Thick Controllers”的使用。 这完全破坏了MVC的主要目标-三合会组件之间的责任划分。

在这里,我们了解了MVC的基础知识。 让我们清楚一点。 因此,MVC假定三元组组件之间的职责分配如下:


  • 控制器是交互逻辑 ,即 与外界(请求-响应)和内部(模型-演示)的互动,
  • 该模型是业务逻辑 ,即 为特定请求生成数据,
  • 表示是表示逻辑 ,即 装饰模型生成的数据。

来吧 责任的两个层次清晰可见:


  • 组织级别是控制者,
  • 执行层是模型和表示。

简而言之,Controller可以操纵“模型”和“视图”犁。 这是一种简单的方法。 如果不是以简单的方式,而是更具体地? 控制器如何精确转向? 以及“模型”和“视图”犁到底如何?


控制器转向如下:


  • 收到应用程序的请求,
  • 决定要为此请求使用哪个模型和哪个视图,
  • 调用选定的模型并从中接收数据,
  • 使用从模型接收的数据调用选定的视图,
  • 将由View装饰的数据返回给应用程序。

这样的东西。 该方案的本质是,模型和表示形式成为查询执行链中的链接。 此外,通过连续的链接:首先,模型将请求转换为某些数据,然后视图将这些模型数据转换为根据特定请求的需要修饰的答案。 就像,人形请求在视觉上用模板装饰,而android请求则用JSON编码器装饰。


现在,让我们尝试弄清楚表演者的耕作方式-模型和演示。 上面我们说过,关于将模型分解为两个子组件存在共识:领域模型和表示模型。 这意味着可以有更多的表演者-不是两个,而是三个。 而不是执行链


>>

可能有一条链


>> >>

问题是:为什么只有两个或三个? 如果您需要更多? 自然的答案是,为了上帝的缘故,需要您所需的一切!


其他有用的艺术家也可以立即看到:验证器,重定向器,各种渲染器,以及通常所有不可预测但令人愉悦的东西。


让我们回顾一下:


  • 执行级别MVC( - )可以实现为链接链,其中每个链接将前一个链接的输出转换为下一个链接的输入。
  • 第一个链接的输入是应用程序请求。
  • 最后一个链接的输出是应用程序对请求的响应。

我将此链称为Scenario ,但对于链的链接,我尚未确定名称。 当前选项包括场景(作为脚本的一部分),过滤器(作为数据转换器),脚本动作。 一般来说,链接的名称不是那么重要,还有更重要的事情。


该方案出现的后果是巨大的。 即:方案承担了主计长的主要责任-确定请求所需的模型和表示并启动它们。 因此,控制器仅具有两个职责:与外界交互(请求-响应)和运行脚本。 从某种意义上说,这是很好的,MVC三合会的所有组件都将顺序分解,变得更加具体和易于管理。 而且在另一个方面还是不错的-MVCS控制器成为纯粹的内部不可变类,因此,即使在原则上也不会变胖。


使用场景会导致MVC模式的另一种变化;我将此变化称为MVCS - Model-View-Controller-Scenario


关于MVC分解的更多信息。 现代框架将所有典型的功能分解到了极限,很自然地摆脱了与外部世界交互的职责中概念MVC的部分。 因此,经过特殊训练的类(例如HTTP 将参与用户请求处理。 结果,Controller不会收到初始的用户请求,而是会收到一些改进的 ,从而可以将控制器与请求的细节隔离开来。 类似地,与HTTP响应的细节进行隔离,从而使MVC模块可以定义自己的响应类型。 另外,这些框架完全实现了MVC的两个组件-域模型和表示模板,但是,我们已经讨论过了。 我对MVC的不断完善和具体化一直在持续进行着,这真是令人耳目一新。


MVCS示例


现在,让我们看看如何在MVCS中实现本文开头的Fat Cortroller示例。


我们首先创建一个MVCS控制器:


 $mvcs = new MvcsController(); 

MVCS控制器从外部路由器接收请求。 让路由器将“ user / hello / XXX”形式的URI转换为这样的操作并请求参数:


 $requestAction = 'user/hello'; //   $requestParams = ['XXX']; //   -   

考虑到MVCS控制器接受脚本而不是URI,我们需要将一些脚本映射到请求的操作。 最好在MVCS容器中完成此操作:


 //   MVCS  URI  $mvcs->set('scenarios', [ 'user/hello' => 'UserModel > UserViewModel > view, hello', ..., ]); 

让我们仔细看看这种情况。 这是由'>'分隔的三个数据转换器的链:


  • “ UserModel”是域模型“ User”的名称,模型的输入将是请求参数,输出将是实际的模型数据,
  • “ UserViewModel”是用于将域数据转换为视图数据的视图模型的名称,
  • “视图,您好”是称为“ hello”的PHP模板的系统视图“模板”。

现在,我们只需要将脚本中涉及的两个转换器作为闭合函数添加到MVCS容器中:


 //   UserModel $mvcs->set('UserModel', function($id) { $users = [ 1 => ['first' => '', 'last' => ''], 2 => ['first' => '', 'last' => ''], ]; return isset($users[$id]) ? $users[$id] : NULL; }); //   UserViewModel $mvcs->set('UserViewModel', function($user) { //    PHP  : 'echo "Hello, $name!"'; return ['name' => $user['first'].' '.$user['last']]; }); 

仅此而已! 对于每个请求,有必要确定相应的脚本及其所有场景(系统场景除外,例如“视图”)。 仅此而已。


现在,我们准备测试MVCS的不同请求:


 //        $scenarios = $mvcs->get('scenarios'); $scenario = $scenarios[$requestAction]; //      ... //   'user/hello/1'  ' '   'hello' $requestParams = ['1']; $response = $mvcs->play($scenario, $requestParams); //   'user/hello/2'  ' '   'hello' $requestParams = ['2']; $response = $mvcs->play($scenario, $requestParams); 

PHP MVCS实现在github.com托管
该示例在example MVCS目录中。

Source: https://habr.com/ru/post/zh-CN424595/


All Articles