起因

最近回顾以前的代码,发现一个偶尔会见到的现象。一个类里面的方法可能需要Ajax返回,也有可能需要函数return。这个现象发生在网站MVC中的 逻辑层(或模型层),示例如下。IndexCtrl是控制器负责渲染页面,ProCtrl是逻辑器负责读取处理数据,A函数是实例化一个类,M函数是读取数据表的意思。现在只是简单的页面输出。

class IndexCtrl extends Ctrl{
function index(){
$proList = A('Pro')->getList();
$this->assign('proList',$proList)
->display();
}
} class ProCtrl extends Ctrl{
function getList(){
$proList = M('pro')->where("isMain='1'")->select();
return $proList;
}
}

现在来了一个管理后台中,需要用Ajax获取这些首页产品列表。怎么改呢?直接再加一个 function getListAjax(); 然后读取同样的数据库,做同样的操作?这显然不科学,同样的逻辑不应该被实现两次。

那就定义一个函数 getListAjax() 里面调用自身的 getList() 然后再Ajax返回。这样看起来似乎也没太大问题,但是当这种类似的场景增多的时候,岂不是所有的方法都有一个ajax的副本?

这样的话,就应该对 getList 函数进行改造了。怎么改呢? 加一个可选参数 $isReturn 默认值为FALSE,此时为AJAX请求返回JSON;调用者调用时传入参数值为TRUE,函数进行return。代码如下:

class IndexCtrl extends Ctrl{
function index(){
$proList = A('Pro')-> getList( TRUE );
$this->assign('proList',$proList)
->display();
}
} class ProCtrl extends Ctrl{
function getList( $isReturn=FALSE ){
$proList = M('pro')->where("isMain='1'")->select();
if($isReturn){
return $proList;
}else{
$this->ajaxReturn($proList);
}
}
}

这样的话,基本上实现了一个函数可以同时拥有两种返回方式,一种是AJAX,一种是函数return。但是这存在很多明显的问题:
1、需要修改调用者,调用者需要传递参数TRUE才能实现函数返回;
2、如果该函数本身就有参数,那加上这个附加参数就会显得很臃肿。
3、不自动,基本属于重复手写状态。

寻觅

根据观察到的现象,我们发现这里判断的关键是 $isReturn 变量,这个变量是true还是false到底有没有办法做到自动识别呢?那么自动识别的前提是什么呢?根据项目的实际情况,我定出了规则 ,就是 直接访问这个方法的URL(如http://localhost/Pro/getList)则使用AJAX返回,访问其它URL则使用函数return

那怎么做呢?项目中使用的是Thinkphp框架,它里面有几个预定义魔法常量 MODULE_NAME,CONTROLLER_NAME,ACTION_NAME,分别表示当前访问的模块名(Home、Admin、Mobile等)、控制器名、操作名。而PHP原生也有几个魔法常量,__CLASS__、__FUNCTION__表示当前访问的类名和方法名。所以只要判断 模块名+控制器名==类名、操作名==方法名,就可以得出是URL直接访问的,使用AJAX返回,否则使用return。代码如下:

class ProCtrl extends Ctrl{
function getList(){
$proList = M('pro')->where("isMain='1'")->select(); $ctrlName = MODULE_NAME.'\Controller\\'.CONTROLLER_NAME.'Controller';
// Admin\Controller\ProController
if($ctrlName==__CLASS__ && ACTION_NAME==__FUNCTION__){
$this->ajaxReturn($proList);
}else{
return $proList;
}
}
}

封装

这样就完了吗?当然不是,这么长的一个判断总不能每个都复制一遍吧,肯定要把它封装起来成为一个公共函数。这个封装看起来很简单嘛,直接弄去一个函数里就完了。然而,too native!因为PHP的这两个魔术常量是会变的。当你把这段代码抽到一个函数中时(例 isNowAction() ),__CLASS__ 就变成了空字符串,__FUNCTION__ 就变成了 'isNowAction' 。好吧,既然普通函数不行,那有没有别的方法呢?

一、C语言中有一个叫做“内联函数”的概念,就是说这个函数虽然是写出来了,但是编译的时候是把它作为调用者的一部分直接编译到该函数中,而普通函数是通过返回跳转的(如汇编指令 RET 、 JMP)。所以,按理说这样的话这两个魔术常量就会像预期一样得出我们想要的值。然而,PHP里面并没有内联函数这个东西,并没有 inline 关键字 。。。

二、一技不成又生一技,有一个东西就做“宏定义”。C语言里面很多时候是用来封装一个小函数的,比如  #define pyth(x,y) sqrt(x*x+y*y) ,可以这样用来宏定义一个函数,或者说是简写一个函数。所以我也按照这样的方式写了一个(用了匿名函数,这很JS)

define('isNowAction()', function(){
$ctrlName = MODULE_NAME.'\Controller\\'.CONTROLLER_NAME.'Controller';
if($ctrlName==__CLASS__ && ACTION_NAME==__FUNCTION__){
return TRUE;
}else{
return FALSE;
}
});

但是发现这个并没有成功执行。翻了一下PHP的文档才知道这PHP的define并不能定义函数

还真不得不说,有时候特性太少真是一个麻烦事啊。难道这样就没有办法了吗?在函数里设两个参数,让调用者把__CLASS__和__FUNCTION__传过来?但这样的封装只是聊胜于无,并不理想。或者可以这样想,既然没有办法定义特殊的函数,那能不能有办法在函数里翻出调用者的信息呢?

功夫不负,还真有!有一个函数名为 debug_backtrace() ,可以找到调用者的信息。如图中红色箭头所指, 这个数组的第二项中的function和class正是调用者的函数名和类名。

封装好的函数代码如下:

    //是否为当前模块下的控制器下的方法,常用于判断是return还是ajax
function isNowAction(){
// var_dump( debug_backtrace() );
$ctrlName = MODULE_NAME.'\Controller\\'.CONTROLLER_NAME.'Controller';
$className = debug_backtrace()[1]['class'];
$funcName = debug_backtrace()[1]['function'];
if($ctrlName==$className && ACTION_NAME==$funcName){
return TRUE;
}else{
return FALSE;
}
}

当然还可以直接封装到逻辑层Ctrl基类中,作为一个基础方法

class Ctrl {
......//框架原来写好的一些代码
protected function reJax($data){
$ctrlName = MODULE_NAME.'\Controller\\'.CONTROLLER_NAME.'Controller';
$className = debug_backtrace()[1]['class'];
$funcName = debug_backtrace()[1]['function'];
if($ctrlName ==$className && ACTION_NAME==$funcName){
$this->ajaxReturn($data);
}else{
// echo 'return';
return $data;
}
}
}

这样的话 ProCtrl 可以非常简洁,而又能自动判断是应该AJAX返回还是return

class ProCtrl extends Ctrl{
function getList(){
$proList = M('pro')->where("isMain='1'")->select();
return $this->reJax($proList);
}
}

继承问题

本来以为,这个函数到此为止就算是结束了。然而并没有。为什么呢?继承的问题。比如 Admin模块里的 ProCtrl类的getList()方法 继承自Home中的ProCtrl类,那么debug_backtrace() 得出来的 class 的类名将会是 Home\ProCtrl,也就是得到的是父类的名字而不是自己的名字,这个问题用 __CLASS__ 魔法变量也是一样存在。不知这个算不算是PHP语言的一个BUG呢?

再看到PHP文档中下面的评论,确实有说到__CLASS__的这个问题,而与此很类似的代替方法是使用 get_class($this) 函数。注意到这里有个参数是$this,也就是说当前类,所以最终我们的这个封装的方法只能写在 Controller 基类中,而无法写在公共函数方法中。最终代码如下:

class Ctrl {
......//框架原来写好的一些代码
protected function reJax($data){
$ctrlName = MODULE_NAME.'\Controller\\'.CONTROLLER_NAME.'Controller';
$className = get_class($this);
$funcName = debug_backtrace()[1]['function'];
if($ctrlName ==$className && ACTION_NAME==$funcName){
$this->ajaxReturn($data);
}else{
// echo 'return';
return $data;
}
}
}

再说两句

万万没想到,想要实现一个如此基础的自动化功能,扯出了这么多一堆概念和技术,从C语言、汇编到PHP再到对象的问题,中间还有类似JS的影子。

最后,可能有人会问,折腾了这么久,这个到底有多大的用处呢?如果同时有 网页版(需要页面渲染) 和 APP(需要JSON数据),同一个功能是写两份代码呢,还是直接使用这个 reJax() 方法自动判断返回呢。

自动判断应该Ajax还是return的更多相关文章

  1. Andoid自动判断输入是电话,网址或者Email的方法----Linkify的应用!

    本节要讲的是,当我们在一个EditText输入电话或者网址还是Email的时候,让Android自动判断,当我们输入的是电话,我们点击输入内容将调用打电话程序,当我们输入是网址点击将打开浏览器程序.而 ...

  2. 彻底解决android读取中文txt的乱码(自动判断文档类型并转码

    原文:http://blog.csdn.net/handsomedylan/article/details/6138400 public String convertCodeAndGetText(St ...

  3. onsubmit校验表单时利用ajax的return false无效解决方法

    代码: function checkNewEmail(){ var re_email=new RegExp("\\w+@\\w+\\.\\w+\\.?\\w*"); var new ...

  4. onsubmit校验表单时利用ajax的return false无效解决方法-转

    原来的代码 function checkNewEmail(){ var re_email=new RegExp("\\w+@\\w+\\.\\w+\\.?\\w*");      ...

  5. 判断post,ajax,get请求的方法

    判断post,ajax,get请求的方法 define('IS_GET',isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] ...

  6. jquery中ajax用return来返回值无效

    jquery中,ajax返回值,有三种写法,只有其中一种是成功的 /** * async:false,同步调用 * 返回1:2 * 失败 * 分析:ajax内部是一个或多个定义的函数,ajax中ret ...

  7. jQuery的ajax中return语句无法返回值

    今天在做一个新需求的时候,用到jQuery的ajax来返回一个查询结果: 但是调用这个方法的时候,data有数据,调用的地方获取到的却一直都是undefined,在网上搜索了一些资料,找到了问题所在, ...

  8. Pace.js – 超赞的页面加载进度自动指示和 Ajax 导航效果

    在页面中引入 Pace.js  和您所选择主题的 CSS 文件,就可以让你的页面拥有漂亮的加载进度和 Ajax 导航效果.不需要挂接到任何代码,自动检测进展.您可以选择颜色和多种效果,有简约,闪光灯, ...

  9. Andoid自动判断输入是电话,网址或者Email的方法--Linkify

    Andoid自动判断输入是电话,网址或者Email的方法----Linkify的应用!http://blog.csdn.net/android_tutor/article/details/500016 ...

随机推荐

  1. 基于HTML5实现3D热图Heatmap应用

    Heatmap热图通过众多数据点信息,汇聚成直观可视化颜色效果,热图已广泛被应用于气象预报.医疗成像.机房温度监控等行业,甚至应用于竞技体育领域的数据分析. http://www.hightopo.c ...

  2. Asp.Net Core 项目实战之权限管理系统(6) 功能管理

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

  3. 【趣味分享】C#实现回味童年的24点算法游戏

    一.24点游戏玩法规则效果展示 1.初始化界面 2.开始游戏界面 3.游戏超时界面 4.查看答案界面 5.答对界面 6.答错界面 7.计算表达式的验证界面 8.一副牌算完开始新一副牌界面 到这里24点 ...

  4. 搭建公司内部的NuGet Server

    随着公司业务慢慢的拓展,项目便会越来越来多,很多项目会依赖其他项目DLL,比如一些底层的技术框架DLL引用,还有各业务系统的也有可能会有引用的可能. 项目多,交叉引用多,如果要是有一个DLL更新,那就 ...

  5. 最大半连通子图 bzoj 1093

    最大半连通子图 (1.5s 128MB) semi [问题描述] 一个有向图G = (V,E)称为半连通的(Semi-Connected),如果满足:∀ u, v ∈V,满足u->v 或 v - ...

  6. 谈谈对Spring IOC的理解(转)

    学习过Spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,今天和大家 ...

  7. spider RPC框架的需求来源与特性介绍(一)

    spider RPC 特性介绍 spider RPC 性能测试 spider RPC 入门指南 spider RPC 配置文件参考 spider RPC 开发指南 spider RPC 安全性 spi ...

  8. MySQL大数据优化

    我们考虑的情况是在你的数据量很大的情况下,千万级别的数据量.不要当我们的请求响应时间已经让我无法忍受的时候,再来想起来优化,可能有点迟了.因为可能会丢失很多潜在的价值客户.所以,在我们当初设计表,或者 ...

  9. Sqlite 存储自定义对象

    在iOS中如果想保存自定义对象,要让自定义对象实现NSCoding接口并实现方法-(id)initWithCoder:(NSCoder *)coder和-(void)encodeWithCoder:( ...

  10. Google C++单元测试框架GoogleTest---GMock的CheatSheet文档

    CheatSheet文档中包含了GMock所有常用的东西,看了这个基本上就可以用它了,本文接上篇博文:Google C++单元测试框架GoogleTest---Google Mock简介--概念及基础 ...