当前位置: 首页 > 后端技术 > PHP

关于PHP后端组织项目结构的思考

时间:2023-03-29 20:19:40 PHP

这是《后端开发者从零开始做一个移动应用》后端部分的第二篇。介绍下一个新项目,如何从零开始搭建后端。假设这个项目由两部分组成:为wap站点和应用程序提供的API;为运营商提供的管理后台。整个项目使用Phalcon,项目的demo可以在这里找到。备注:随着文章的进展,项目会不断更新,最终会与配套的wapapp形成一个整体项目。项目最终至少会包含以下内容:基于Codeceptionapi测试登录api的小米消息推送支付集成(支付宝、招商、微信)(这部分使用oauth2,将基于'bshaffer/oauth2-server-php')项目结构回顾后端系统一般采用MVC结构(这里以PHP为例),M代表模型,V代表视图,C代表控制器。我在重复几句话。模型是指数据模型。这个数据模型包括你Mysql中的表结构,或者redis的缓存对象结构。它代表一个数据操作单元。视图是指显示给用户浏览和直接操作的界面。每个人都明白这一点。Controller控制器就更不用说了,它主要是为了隔离View与Model直接打交道。作为中间人,它在两端之间传递小票。在我过去的项目中,我主要的困惑是业务逻辑放在C层还是M层。从对象的角度来看,业务逻辑无非就是操作数据,要么读取要么修改,所以应该放在M层,因为一个对象应该有它自己的属性和方法。业务放在M中。在实际工作中,我们经常会有这样的场景,比如:读取一个游戏列表数据,该数据包括游戏的详情、游戏的版本信息、下载信息。因为游戏app会有升级,一个游戏会对应多个包。那么游戏详情模型至少有两个模型,包括游戏名称、logo等基本信息游戏包信息模型,包括包所属平台、大小、下载地址、版本信息等,那么应该在哪里这个动作的方法被封装了?之前的做法是将相应的操作封装到相应的model中,然后在controller中单独调用。说起来,游戏模型封装了查询游戏列表的方法,包模型封装了根据游戏id查询包信息的方法。然后我们在controller中调用这两个方法,然后组装,将游戏对应的包设置到对应的游戏中。那么有一个问题,假设我们需要在游戏详情的controller方法中返回一个相关游戏的集合,是不是还要重复上面的操作呢?有人会说,游戏的处理部分分离成一个公共方法,那要是在新闻详情里调用呢?这根本不应该在同一个控制器中!业务放在C中,我们在model中复用方法遇到了一点麻烦,所以继续看放在controller中会发生什么?此时的一个好处是:我们可以使用连接查询,通过一个连接查询来完成刚才的两个查询,减少了mysql的时间,提高了程序性能,进而完成对查询结果的处理。好了,后面不说了,相信大家已经发现了,这个查询过程还是不可复用的。自然地,我们这里应该想到,将其提炼成一个方法是不能满足其他控制器的(想都别想一个控制器调用另一个控制器的想法)。那么只能细化为一个类,封装所有的服务。这样,任何需要游戏列表数据的地方都可以直接调用这个GameServer(假设封装的业务逻辑放在xxxServer中)获取相同的数据,然后如果业务发生变化,我们只需要改变这个地方,所有places得到的数据也会保持一致。因此,通过审查,我们得出结论,我们的后端项目需要一个服务器级别来存储业务逻辑。服务器层存在的意义是脱离这一层,集中覆盖所有业务功能,大大提高了代码的复用性。除了直接使用不同控制器的不同方法外,还包括不同模块之间的复用。使用。但是在采用不同的模块之前,服务器层还需要考虑一些额外的事情。比如我们有一个appapi模块和一个后台管理模块。然后就是获取列表数据,有些字段appapi模块可能不需要,但是后台管理需要知道所有的内容,还有后台用户权限的一些问题。这些部分可以继续拆分并与服务器组合。需要结合自身业务进行管理。我个人实践中的代码还有一个好处就是server层在一定程度上让C层变得更简单,可以让团队中的新人快速上手代码。比如小明是团队新人,所以在熟悉所用框架的前提下,可以马上开始C层的工作,因为这里没有业务,有的只是验证通过的数据由客户端,并调用服务器层返回。通过这个过程,可以加快融入团队的过程。统一返回格式约定api返回的数据格式,基本上是系统开发的第一步。原来常用的方法是通过returnjson_encode(['msg'=>'ok',//携带的信息可以在前端提醒用户'data'=>[//specificdata...。..],'code'=>'0',//0表示成功,其他表示对应错误])所以这里遇到的第一个问题就是为了简化前端对类型的判断,基本上都是字段值以字符串的形式返回。那么data中的内容需要在各个controller中进行处理,以处理字符串、utf-8编码等问题。重复代码,即使提取到一个方法中,仍然需要面对这个问题。更好的解决方案是在返回数据的拦截器内部进行统一处理(每个框架都有类似的概念)。像上面这样写代码可能会导致额外的问题,比如字段名打错了,例如:代码写成cdoe,数据写成date。对于程序代码的额外风险(尤其是最有可能发生bug修复的时候),那么应该从这里想到一个解决方案,使用对象对返回的数据结构进行归一化。比如我们定义一个类:classResultData{/***返回信息提示*@varstring$msg*/private$msg;/***返回的数据结构*@vararray|object|string*/private$data;/***api状态码*@varint$apiCode*@seeApiCode*/private$apiCode;公共函数__construct(int$apiCode,string$msg='ok',$data=null){$this->apiCode=strval($apiCode);$this->msg=trim(strval($msg));$this->data=$data;}/***获取数据结果*@returnarray*/publicfunctiongetRetData(){if(!is_array($this->data)&&is_object($this->data)&&method_exists($this->data,'toArray')){$this->data=$this->data->toArray();}//valueToString将data的值转为string并进行utf-8转码$result=['code'=>$this->apiCode,'msg'=>$this->msg,'data'=>$this-&G吨;数据?ArrayUtil::valueToString($this->data):[],];if(!APP_ENV_PROD){//测试环境显示API的处理时间信息,方便优化$result['use_time']=microtime(true)-$_SERVER['REQUEST_TIME_FLOAT'];}返回$结果;}}有了上面的类,我们所有的服务层或者控制器都应该把它作为返回值,然后在拦截器中统一进行json编码。这样一来,就减少了出错的可能性,同时,把数据处理的地方统一集中在ResultData中进行管理,所以以后如果有什么特别的变化,一次调整就生效到处。其他问题另外还有如何将oauth2集成到项目中等问题,这些部分在x-api项目中都有说明。日志记录也是系统开发中非常重要的一环。这部分就不多说了,指定的数据以标准化的格式存储(介质可以是:db、file)。在系统开发中,应拒绝使用var_dump和echo进行调试。另外推荐使用PhpStormIDE进行系统开发。后续分享完善一个x-api的基本结构,以及php自动化测试部分的文档教程,至此后台部分告一段落。(本系列的分享主要集中在代码层面,不涉及相关的系统部署问题)GitHub:https://github.com/helei112g