文章

TPEZ 我和PHP的梦幻情缘

前言

“PHP是世界上最好的语言”

这句话最早出现在2001年7月的PHP文档中,21世纪初,PHP一直是Web开发中的统治力量。[1]然而,时过境迁,在2021年的当下,PHP已经黯然失色。[2]目前招聘中主流的后端岗位都以Java、Golang、C++为主,PHP岗位大不如前。这句话也变成了讽刺PHP开发者的梗。

然而,PHP却是陪伴我大学三年成长之路的可谓最亲密的伙伴,它见证我做出第一个Web项目,帮助我完成一个个课程设计、参赛项目、外包项目,跟着我南征北战。从大一开始学习PHP,到大一下学期使用ThinkPHP5.0完成Quanta杯的项目,到大二上学期用ThinkPHP5.0重构思政学时3.0,再到大三上用ThinkPHP6.0重构培训学时,对TP属实是感情深厚。

(更多我的ThinkPHP文章可见https://blog.csdn.net/weixin_43409309/category_10168077.html

在使用ThinkPHP框架做开发的过程中,发现每次在新建项目的时候,我都会去找以前项目的代码,重用里面的封装。久而久之,就萌生了做一个再封装框架的想法。虽然封装的内容不多,但都是我认为优雅的实现。后续若在开发中遇到新的想法,可能还会更新。若有谬误之处,欢迎大佬指正。

简介

项目地址(欢迎star):https://github.com/LeslieLeung/TPEZ

TPEZ(读作tp easy)是一个基于ThinkPHP6.0.x再封装的框架,旨在帮助新手更快上手ThinkPHP框架,同时方便老手快速基于本框架进行项目开发。TPEZ凝聚作者三年PHP项目开发经验,对一些项目共性的功能(如JSON返回、权限管理、controller-service-model结构等)进行封装,方便后端同学使用TPEZ作为脚手架进行后续开发。同时,提供了登录、用户端、管理端模拟等示例接口,供前端同学学习。

特性

  • 对控制器返回Json进行了封装

  • 实现了权限管理机制(使用中间件)

  • 实现了token封装

  • 扩充了基本框架(增加service层,将业务逻辑与模型和控制器分开)

  • 增加了若干范例(包括controller、service、route、env等的使用)

  • 提供了可用的示例接口供前端学习

  • 与官方ThinkPHP6.0.x完全兼容,若后续官方版本更新,可以方便升级

封装思路

基控制器

控制器的基本功能是接受输入,通过业务逻辑层和模型层,返回数据。在项目中比较常用的数据交换格式一般是json,因此首先也是对返回json这一功能进行封装。这一部分主要需要把json封装成固定的格式,如

 {
  "code": "xxxxx",
  "msg": "xxxxx",
  "data": {
    "key1": "value1",
    ... // other infos
  }
}

因此,用一个ResponseController类继承BaseController类(表示该类为控制器),在里面定义返回json的各种方法(包括json结构、成功、成功(带数据)和失败的方法)(具体见app/controller/ResponseController.php);新的控制器只需要继承ResponseController,在返回的时候调用如$this→renderSuccess()即可快速返回json数据。

权限管理

这部分是官方文档一直在暗示但没有实现出来的功能[3]。这部分是我认为最得意的部分,我使用了中间件与路由配合token进行实现,采用了类似Java中拦截器的思路,完全不影响正常业务逻辑(即在业务逻辑中无需考虑权限的问题)。

正常而言,一个请求在tp内部是这样处理的(有部分省略):

请求-->控制器-->逻辑层-->模型层-->逻辑层-->控制器-->回应 

若要实现权限的管理,可以通过将权限值和用户信息放在一个数组中,在发放token时,使用token作为key,上述数组作为value存放在缓存中(可以使用file或者redis)。若不使用中间件,可以在逻辑层对token对应的权限值进行校验。然而,这会造成一个很大的问题:耦合度剧增。一个校验权限的逻辑像肿瘤一样侵入到控制器、逻辑层,这显然是不利于维护的。(考虑以下例子:若要修改一个管理权限接口为用户和管理皆可用,需要下到逻辑层进行修改。)

聪明的做法肯定是把权限管理提出来,作为一个最前置的逻辑,对token进行校验。这里就要引入tp中间件了。

中间件主要用于拦截或过滤应用的HTTP请求,并进行必要的业务处理。[4]

一个聪明的想法油然而生:在请求时,先根据请求的路径(路由)分配对应的校验中间件,在中间件中校验token、解出请求参数放入上下文(笑死,在学安卓开发,就想到这个表述了)中,然后继续控制器往下的操作。

引入中间件后,处理流程如下:

// token权限正确
请求-->参数检查中间件-->权限检查中间件-->
控制器-->逻辑层-->模型层-->逻辑层-->控制器-->回应
// token权限错误 
请求-->参数检查中间件-->权限检查中间件-->返回错误回应

实现上,只需要在中间件中实现权限的检查和请求参数的提取即可。然后,只需要把对应请求的路径注册到路由,然后指定使用权限校验中间件即可。实际上,我实现了一个Auth中间件基类,在新增权限时,只需要继承Auth基类,在enum中新增权限值,然后修改里面的权限值即可。具体的实现见app/middleware/Auth.phproute/route.php

回到刚才修改接口权限的例子,在引入中间件后,只需要在route/route.php中修改对应接口的中间件即可。如:

// 用户接口
Route::group('user', function() {
    // 这里添加用户的接口
    // add user apis here
    Route::rule('helloUser', 'User/helloUser', 'POST');
    Route::rule('helloAdmin', 'Admin/helloAdmin', 'POST'); // 新增用户可访问管理接口
})->middleware([\app\middleware\CheckParam::class, \app\middleware\UserAuth::class]);

Token

Token的封装主要是生成随机key,用用户信息数组作为value将登录态保存到cache中。为了配合上面权限管理增加了role字段。提供了如发token、销毁token(安全退出)、取token等功能。详见app\model\Token.php

Best Practice

其他的部分,主要是使用tp框架这几年积累下来比较优雅的一些写法。带强烈个人风格。

controller-service-model

一开始写的时候,所有的业务逻辑都写在model中。后来学Springboot,了解到持久层和业务逻辑层实际上是应该分开的。因此在写PHP中,我也使用了这种分开的方式。在controller中调用service中的业务逻辑,然后service中对持久层进行调用。

另外有一处地方也值得一提,先贴代码。

    private $indexService;
    private $data;
    private $id;

    public function __construct(App $app, Request $request)
    {
        parent::__construct($app);
        $this->id = $request->id;
        $this->data = $request->data;
        $this->indexService = invoke('app\service\IndexService');
    }

在控制器中,我先声明了诸如$data, $id等成员变量,在构造函数中注入。这个trick可以在后续代码中不必再考虑传入的参数,直接访问这几个成员变量就可以获取到。$indexService的做法则是对Springboot中@Autowired的模仿。

其他

tp6引入了一个我非常喜欢的功能——环境变量设置(见.env)。我习惯把诸如servername, ip, token_expire_time等等随时需要根据部署环境修改的环境变量写在这里,而不用通过hardcode等方式。这个功能在tp5中需要修改一个config.php,实现属实不如tp6优雅。

最后

写完才想起副标题是我与PHP的梦幻情缘,文章却写成了TPEZ的发布会。但我没有跑题,TPEZ就是我跟PHP三年写就的故事。还记得去年的时候,老EO安排我整理PHP后端方向的学习资料,我一直一直没有完成这个任务。前段时间我提议以后塔里新生后端方向与时俱进,从Java入门,不再学PHP了。另外另外,我也打算转Java/Go了,很有可能TPEZ就是我最后一个PHP的项目了,虽然封装很简单,但我觉得是这几年写的PHP项目凝聚出来的,希望对大家有帮助。

最后啰嗦一下,欢迎star我的项目(https://github.com/LeslieLeung/TPEZ),欢迎关注我的微信公众号和博客(https://blog.ameow.xyz/

引用

[1] 「PHP 是最好的语言」这个梗是怎么来的? - 蓬岸 Dr.Quest的回答 - 知乎
https://www.zhihu.com/question/26498147/answer/315847232 2021.4.22

[2] TIOBE Index for April 2021 https://www.tiobe.com/tiobe-index/ 2021.4.22

[3] 路由·ThinkPHP6.0完全开发手册 https://www.kancloud.cn/manual/thinkphp6_0/1037494 2021.4.22

[4] 中间件·ThinkPHP6.0完全开发手册 https://www.kancloud.cn/manual/thinkphp6_0/1037493 2021.4.22

License:  CC BY 4.0