浅谈 PHP 项目四层架构

如今 PHP 项目开发离不开框架。主流的框架 Laravel、Yaf、YII 等都属于 MVC 类型的框架。如此多的框架,我们该如何抉择。在项目开发中我们应该怎样划分项目的结构就成了一个待讨论的问题。

一、PHP 第一代开发模式:混编

相信很多 PHP 开发者刚开始学习 PHP 的时候,都是把 HTML 与 PHP 代码写在一个 PHP 脚本当中来运行。这个阶段被称为混编。也是 PHP 初学者学习 PHP 时必经的一个阶段。在后续使用 MVC 框架时,对理解 MVC 框架有着很重要的作用。

在这个阶段博主私以为各位初学 PHP 的同学可以多感受一下这种开发模式。在后续使用框架的时候,可以有一个对比认知。这样就知道我们为何要使用框架来开发项目。

同时,也知道框架有何缺点。各个框架之间有何区别。在选择框架的时候,就有一个比较清晰的认知。

总之,混编主要用于学习。真正的项目开发几乎不用。

二、PHP 第二代开发模式:前后端代码分离

所谓前后端代码分离。所谓前端是 HTML+CSS+JS 等这些代码。后端是指服务器 PHP 代码。为达到这种前后端代码的分离自然离不开模板引擎。比较著名且历史悠久的 PHP 模板引擎:Smarty。

模板引擎的原理实际上就是变量替换原理。

以 Smarty 模板引擎为例。我们会在 HTML 代码当中按照 Smarty 文档要求的把一些特殊标识写在模板中。Smarty 模板引擎会自动帮我们替换为对应的 PHP 的代码。最终生成一个混编的 PHP 脚本。

注意到了吗?最终还是生成一个混骗的 PHP 脚本。所以,后续任何框架的最终本质都是混编。只不过是用了一些技术手段来降低了前端代码与后端代码的耦合。

我们看几个 Smarty 模板示例:

{{foreach $list as $item}}
<tr>
    <td class="text-center">{{$item.adminid}}</td>
    <td class="text-center">{{$item.real_name}}</td>
    <td class="text-center">{{$item.mobile}}</td>
    <td class="text-center">{{$item.role_name}}</td>
    <td class="text-center">{{if $item.user_status}} 正常 {{else}} 已禁用 </span> {{/if}}</td>
    <td class="text-center">{{$item.u_time}}</td>
    <td class="text-center">{{$item.c_time}}</td>
    <td class="text-center">
        {{if $item.user_status}}
        <a class="forbid" rel="{{$item.adminid}}" realname="{{$item.real_name|escape}}" status="0" href="javascript:void(0)">禁用</a>
        {{else}}
        <a class="forbid" rel="{{$item.adminid}}" realname="{{$item.real_name|escape}}" status="1" href="javascript:void(0)">解禁</a>
        {{/if}}
        <a class="custom-a" href="javascript:void(0);" onclick="edit({{$item.adminid}}, '{{$item.real_name}}');">编辑</a>
        <a class="del" rel="{{$item.adminid}}" href="javascript:void(0)">删除</a>
    </td>
</tr>
{{/foreach}}

以上代码是博主摘自实际项目的代码片断。它是循环输出一个管理员列表。
admin-list.png

我们来看看 Smarty 模板引擎会生成的混编脚本代码:

<?php
$_from = $_smarty_tpl->smarty->ext->_foreach->init($_smarty_tpl, $_smarty_tpl->tpl_vars['list']->value, 'item');
if ($_from !== null) {
foreach ($_from as $_smarty_tpl->tpl_vars['item']->value) {
?>
<tr>
    <td class="text-center"><?php echo $_smarty_tpl->tpl_vars['item']->value['adminid'];?></td>
    <td class="text-center"><?php echo $_smarty_tpl->tpl_vars['item']->value['real_name'];?></td>
    <td class="text-center"><?php echo $_smarty_tpl->tpl_vars['item']->value['mobile'];?></td>
    <td class="text-center"><?php echo $_smarty_tpl->tpl_vars['item']->value['role_name'];?></td>
    <td class="text-center"><?php if ($_smarty_tpl->tpl_vars['item']->value['user_status']) {?> 正常 <?php } else { ?>已禁用</span> <?php }?></td>
    <td class="text-center"><?php echo $_smarty_tpl->tpl_vars['item']->value['u_time'];?></td>
    <td class="text-center"><?php echo $_smarty_tpl->tpl_vars['item']->value['c_time'];?></td>
    <td class="text-center">
        <?php if ($_smarty_tpl->tpl_vars['item']->value['user_status']) {?>
        <a class="forbid" rel="<?php echo $_smarty_tpl->tpl_vars['item']->value['adminid'];?>
" realname="<?php echo htmlspecialchars($_smarty_tpl->tpl_vars['item']->value['real_name'], ENT_QUOTES, 'UTF-8', true);?>
" status="0" href="javascript:void(0)">禁用</a>
                            <?php } else { ?>
                            <a class="forbid" rel="<?php echo $_smarty_tpl->tpl_vars['item']->value['adminid'];?>
" realname="<?php echo htmlspecialchars($_smarty_tpl->tpl_vars['item']->value['real_name'], ENT_QUOTES, 'UTF-8', true);?>
" status="1" href="javascript:void(0)">解禁</a>
                            <?php }?>
                            <a class="custom-a" href="javascript:void(0);" onclick="edit(<?php echo $_smarty_tpl->tpl_vars['item']->value['adminid'];?>
, '<?php echo $_smarty_tpl->tpl_vars['item']->value['real_name'];?>
');">编辑</a>
                            <a class="del" rel="<?php echo $_smarty_tpl->tpl_vars['item']->value['adminid'];?>
" href="javascript:void(0)">删除</a>
                        </td>
                    </tr>
                    <?php
}
}
$_smarty_tpl->smarty->ext->_foreach->restore($_smarty_tpl, 1);?>

在生成的混编代码当中,我们其实已经知道了这就是一个翻译的过程。Smarty 模板引擎会用自己实现的一套方法把这个变量输出在模板当中。没有什么特殊的东西。

这个前后端代码分离的开发模式阶段,在 Smarty 开发过程中经历了很长一段时间。博主在 2010 年开始接触的第一个项目就是如此。后续几年也陆续接触了很多这样的项目。

后来,一些 PHP 开发者突然意识到这种开发模式也并不是很好。每个项目都会有诸如分页、数据库操作、文件上传、路由等这些功能。于是,一群 PHP 开发者就开始设计一款通用的框架。这就是下面即将讲到的 MVC 模式。

三、第三代开发模式:MVC

MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写。一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法。将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC 被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。

MVC 并不是一种设计模式。这个一定要注意了。MVC 模式可以简单理解为是一种业务框架的拆分方式。MVC 是以观察者模式、策略模式、组合模式三种设计模式的合体。当然,现在很多 PHP Web 开发框架,并仅仅只有这三种模式。还有其他模式。这些我们并不需要太过关心。除非,你想设计一款新的 PHP Web 框架。

这种模式在第二代开发模式上面有何不同呢?

MVC 可以简单看作是在第二代基础上把服务器端代码进行了二次拆分。拆出了 C 与 M。

M 层,现在很多框架把它仅仅用作数据库表的映射层。即数据库中每一张表在 Model 层都有对应的一个类与之对应。这种映射用 IT 专业术语叫 ORM。

但是,在 Laravel 这种框架当中,M 层并没有限定只与数据库创建映射。当然,现在很多人由于很多框架都是这样做的。在使用 Laravel 的时候自然也潜移默化它 M 当作了数据库的映射。换句话说,你 M 层狭隘地理解为数据库的模型。也并没有太大问题。

C 层,即控制器层。它控制什么呢?它控制与用户的交互。在 Web 开发过程中就是控制用户(浏览器)发送给服务器的数据。再把响应的数据发送给用户。

一个优秀的设计,不应该在 C 控制器层编写有任何的业务代码。在 M 层不应该直接读取任何用户数据。只能通过 C 层传递给它。

这个怎样理解呢?

在 MVC 刚出来的时候,把业务代码写在 C 层,这并没有错。不仅没错,还是最正确的做法。毕竟,这在第二代开发模式之上,已经有了巨大的进步。
M 层不能直接读取诸如 GET、POST、SESSION、COOKIE 等数据。应该由 C 层读取之前传递给 M 层。这个很好理解。

但是,大家有没有想过在 C 层编写业务逻辑代码是不是真的很正确呢?

博主寒冰在 2012 年的时候,就曾经无数次问题过自己这个问题。为何问这个问题?我当时就想:如果这个时候有手机触屏端、PC 端、APP 端,当我们要侯登录代码的时候,就会同时修改这一个端对应控制层的业务代码?

在当时博主就面临这样的问题,当时用的是 ThinkPHP 2.3 版本开发。一个模块对应一个端。这样手机触屏端、PC 端、APP 端就分别有三个控制器层代码。有一次接到一个需求要修改登录,增加一个验证码的需求。于是我只修改了 PC 端。测试的时候发现手机触屏端、APP 端代码登录时报错了。没办法我只能去修改这三处的代码。但是,这三处又是由不同的人写的。有些人离职了。而且这些代码之间不是单纯的拷贝。而是自己实现的一套逻辑。

这就直接出现了几个问题:

  • 维护成本翻倍。
  • 测试成本翻倍。
  • 报错风险。

于是,这个时候很多人(包括我)终于意识于,MVC 这种三层构架的拆分方式编写程序,已经不能满足实际需求了。

四、第四代开发模式:MVCS

大家可以通过 MVCS 这个名字可以看出,我们在 MVC 三层架构之上,增加了一层 S (业务/服务层)。目前很多人都知道要划分为四层。但是,目前还没有给四层定一个准确的名字。于是,博主寒冰我就厚着脸加了一个 S 进去。方便讲解这个四层构架开发模式。

这个 S 层就是为了解决 MVC 当中面对多终端系统时暴露出的问题。它是把 MVC 模式中的 C 层进行了再次拆分。即把 C 层当中编写的业务逻辑全部挪到 S 层。然后由 C 直接调用。这样就可以让多个终端的控制层直接调用封装好的 S 层代码了。在更大的项目中,我们可以指导 S 层封装成一个 Composer 包安装到各个项目当中。在 A 终端测试通过之后,那么在其他端基本上可以直接使用。从而真正意义上解决了第三代开发械MVC 的问题。

目前,还是有很多的项目是基于 MVC 三层模式开发项目。在开发一些小项目或不是很复杂的业务项目时,这种模式依然是最好的解决模式。如果,真的像博主寒冰这样遇到多终端的复杂业务时。四层架构 MVCS 也许是你最好的解决方案。

博主虽然从 2011 年开始认证四层架构的开发模式。但是,真正应用在项目当中是从 2014 年开始。我了解过身边其他资深的 PHP 开发朋友。他们也在使用这种四层框架模式。

五、其他类型的开发模式

我们谈到的四层构架开发模式,目前在大部分公司都成为了主流。但是,它是不是也有缺点呢?这个问题值得我们思考。我见过有的公司在四层构架之上又搞出一套逻辑层(Logic)。逻辑层顾名思义就是把所有的逻辑写入这层。S 层就不写业务了。而是专门处理那些调用了第三方接口服务的需求。

当然,这种五层架构目前在各大公司用得不是很少。因为,这种开发模式并不是很主流。在很多开发者眼中,这种划分过于细导致项目层级难以掌控。当然,每个人心中都有一套自己的划分逻辑。只要遵从易读、易维护就算合格的拆分。

本文是根据博主寒冰从实际开发总结而来。受限于个人视野与主观思想。有不妥之处请指教!谢谢!

博主 2011 年创建了一个《PHP 初学者官方群》,目前群成员 500 人左右。群号:168159147。为了防止广告,设置为付费入群。欢迎大家加入讨论技术!

标签: 无

发表评论: