业务代码风格循环体重复调用建议代码示例://$array是一个大变量,可能超过1000个数据foreach($arrayas$ar){$model=newModel();$modelData=$model->findOne(['id'=>$ar['id']]);$modelData['is_less']=$modelData['tag']==3?真假;$model2=newAnotherModel();$model2->find(['id'=>$ar['id']])->update(['is_less'=>$modelData['is_less']]);}上面的代码块有很严重的问题.在循环体中,数据库查询不能重复使用太多次,尤其是相似或一致的SQL。必须批量查询获取数据,然后再进行相应的逻辑处理。如果循环次数多,不仅会体现在慢循环逻辑上,还会在并发读写业务上由于频繁的硬盘读取、表锁等,可能对数据库造成巨大的压力服务器。所以上面的代码可以转化为:$ids=array_columns($array,'id');if($ids){$model=newModel();$modelDataList=$model->where(['in','id',$ids])->all();$modelDataList=array_combine(array_columns($modelDataList,'id'),$modelDataList);}foreach($arrayas$ar){$modelData=empty($modelDataList[$ar['id']])?[]:$modelDataList[$ar['id']];如果(!$modelData){继续;}$modelData['is_less']=$modelData['tag']==3?真假;$model2=newAnotherModel();$model2->find(['id'=>$ar['id']])->update(['is_less'=>$modelData['is_less']]);}又如:foreach($ids作为$id){$data=RpcService::getMyData(['my_id'=>$id]);$data['field1']=$data['field2']+$data['field3'];$sendPost=RpcService::sendToBoss(['field1'=>$data['field1']]);}像这样通过接口获取数据或者更新数据,一般不能在循环体中重复调用,因为http或者其他实现rpc的网络协议在数据传输上或多或少会有些慢,而且对方业务的实现一般不建议调用方循环调用,所以如果有批量调用接口的要求应该要求接口提供者为批量操作提供接口,在循环外进行操作。上面的代码可以修改为:$dataList=RpcService::getMyDataList(['ids'=>$ids]);array_walk($dataList,function(){//processfields});RpcService::multiSendToBoss(['list'=>array_columns($dataList,'field1')]);和文件读取也类似,php读写文件效率不高,应该避免频繁读写同一个文件。例如:$readFilePath='current_file';foreach($writeFilePathsas$path){$content=file_get_content(realpath($readFilePath));file_put_content($path,$content);}应该将文件内容放在循环体之外。其他比较耗时或者不建议经常调用的逻辑应该写在循环外。关于业务层面的类调用或方法调用的可读性问题。关于这一点,我们来看一下代码示例:classDemoClass{public$handler=[];publicfunctionhandlerRegister(Handler$handler){$this->handler[]=$hanlder;}publicfunctionrun($id,$params){foreach($this->handleras$h){if($h->id===$id){return$h->handle($params);}}}}类处理程序{public$id;publicfunctionhandle($params){$result=call_user_func($params['callback'],$params['params']);$resultData=(new$result)->getData();$next=$params['下一个'];返回$next($resultData);}}call_user_func、call_user_func_array、Reflection类等可以直接实例化或者调用变量中的一些内容,机器编译运行貌似没问题,但是人的阅读起来就比较麻烦了。你得追根溯源,才能知道调用的是什么,反射的是什么。虽然写起来简单粗暴,但对读者不是很友好。而且,即使是像phpstorm这样强大的IDE也无法帮助你识别和追溯这些变量的来源。因此,业务代码应尽量避免使用相似的代码。但是,如果你写的是low-levelscaffolding,也就是丰富框架功能的工具包,那你就按照自己的想法去做,用户看不懂就无所谓了。所以上面的例子经常出现在vendor依赖包中,写起来比较简单,不用担心别人看不懂的问题。关于写辅助方法的问题,不同的框架有不同的表现。有些框架可能有自己的实现思路,有些东西不方便全局调用。但是目前很多框架都使用了容器,所以使用辅助的方法来进一步简化代码。有必要。编写了辅助方法文件,需要在框架加载过程中和业务使用前导入,最好全局导入。比如在Yii2中要实现json返回,需要写如下代码:Yii::$app->response->format=Response::FORMAT_JSON;return['code'=>1,'message'=>'成功'];如果你写一个辅助方法如下:functionajax(array$data){Yii::$app->response->format=Response::FORMAT_JSON;return$data;}那么代码可以写成:returnajax(['code'=>1,'message'=>'success']);另一个例子:获取帖子提交的所有参数:$post=Yii::$app->request->post();//获取所有$field1=Yii::$app->request->post('field1');//获取其中一个参数的辅助方法如下:functionpost($key=null,default=null){if($key===null){returnYii::$app->request->post();//获取所有}返回Yii::$app->request->post($key,$default)}调用如下:$post=post();$field1=post('field1');再比如,根据key销毁数组中的一个值。//原生写法if(isset($arr[$key])){unset($key);}//辅助方法函数array_pull(&$arr,$key){if(isset($arr[$key])){取消设置($键);}}//调用array_pull($arr,$key);关于逻辑块重用和可读性的问题例如:publicfunctionhandle($params){$dataList=(newModel)->query($params)->all();$返回=[];foreach($dataListas$data){if($data['key']==1){$data['field1']="123";}elseif($data['key']==2){$data['field1']='678';$data['field2']='7hj';}elseif($data['key']==3){$data['field1']='uyo';}else{$data['field1']='other';}if($data['field1']=="123"){$other=['other1'=>34,'other2'=>35,'other3'=>98];$return[]=$other;}elseif($data['field1']=="678"){$other=['other1'=>341,'other2'=>351,'other3'=>981];如果($data['field2']=='7hj'){$other=['other1'=>3412,'other2'=>3512,'other3'=>9812];}$return[]=$other;}elseif($data['field1']==='uyo'){$other=['other1'=>3412,'other2'=>3512,'other3'=>9812];$return[]=$other;}else{$other=['other1'=>34123,'other2'=>35123,'other3'=>98123];$return[]=$other;}}}上面的代码主要存在三个问题:没有注释,判断条件太多,逻辑块不能复用。如果有更复杂的逻辑,这段代码无疑可能超过100行,这对于开发者和维护者来说似乎是非常吃力的。过多的注解问题和判断条件可能是业务问题,有时无法避免。这里我们将重点关注逻辑块的复用。在上面的代码片段中,dataList的获取应该是一个独立的方法,因为以后其他函数可能会使用相同的方法获取相应的数据;对于data['field1']的值,也应该是一个独立的method方法;下面对data['field1']的判断也应该是一个独立的方法。这就需要进行代码拆分,这样既可以保证代码的简洁性、可重用性和可读性,又可以避免多个无用变量堆积在一个逻辑块中。在代码逻辑上,一个功能应该由一个方法来实现,一个方法应该只做一件事。这段代码拆分后会变成:键']==1){$data['field1']="123";}elseif($data['key']==2){$data['field1']='678';$data['field2']='7hj';}elseif($data['key']==3){$data['field1']='uyo';}else{$data['field1']='other';}return$data;}publicfunctiongetOther(){if($data['field1']=="123"){$other=['other1'=>34,'other2'=>35,'other3'=>98];}elseif($data['field1']=="678"){$other=['other1'=>341,'other2'=>351,'other3'=>981];如果($data['field2']=='7hj'){$other=['other1'=>3412,'other2'=>3512,'other3'=>9812];}}elseif($data['field1']==='uyo'){$other=['other1'=>3412,'other2'=>3512,'other3'=>9812];}else{$other=['other1'=>34123,'other2'=>35123,'other3'=>98123];}return$other;}publicfunctionhandle($params){$dataList=$this->getDataList($params);$返回=[];foreach($dataListas$data){$data=$this->handleField($data);}$return[]=$other;}return$return;}这些方法负责每个只完成一件事的函数。handle()方法只是用来整理几个方法的数据,简单明了。建议每个方法的代码不要超过30行,除非有特殊情况。公共方法的写法建议对于公共方法调用的代码风格,应遵循调用者最少知道的原则。调用者只需要传入相应的参数即可。并且调用者应该能够完全知道正确和错误的信息,并且应该保证其健壮性。如果传入的参数是一个数组,应该告知调用者数组内部参数的详细说明,或者在注释中给出相应的例子。如果使用专用文件服务器进行页面下载涉及下载网页直接生成的数据,尽量使用专用文件服务器而不是直接在页面上下载,尽量使用异步生成的文件进行下载。比如用户点击页面下载按钮后,跳转到自己的下载页面。这个页面有一个他自己文件下载的历史表,有一个状态标记这个文件是否可以下载。当文件在后台生成并上传到文件服务器时,会被标记。变得可下载。好处:记录下载历史,下载历史文件,优化下载性能,可以处理大文件。缺点:需要多做一页和一张表,需要等待文件生成上传到文件服务器的时间。文件服务器可以使用对象云。
