起因

最近回顾以前的代码,发现一个偶尔会见到的现象。一个类里面的方法可能需要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. 代码的坏味道(10)——发散式变化(Divergent Change)

    坏味道--发散式变化(Divergent Change) 发散式变化(Divergent Change) 类似于 霰弹式修改(Shotgun Surgery) ,但实际上完全不同.发散式变化(Dive ...

  2. 混合框架中Oracle数据库的还原处理操作

    在较早期的随笔<Oracle如何实现创建数据库.备份数据库及数据导出导入的一条龙操作>粗略介绍了Oracle数据库的备份还原操作,本文想从开发框架的基础上介绍Oracle数据库的脚本或者还 ...

  3. linux中输入输出和重定向问题

    输入输出解释 当我们执行shell的时候,每个进程都和三个打开的文件有关系,并使用文件描述符来引用这些文件.但这些文件不容易记忆,所以shell给了相应的文件名: 0:输入文件-标准输入(它的命令是输 ...

  4. python序列,字典备忘

    初识python备忘: 序列:列表,字符串,元组len(d),d[id],del d[id],data in d函数:cmp(x,y),len(seq),list(seq)根据字符串创建列表,max( ...

  5. 浅谈Hybrid技术的设计与实现第二弹

    前言 浅谈Hybrid技术的设计与实现 浅谈Hybrid技术的设计与实现第二弹 浅谈Hybrid技术的设计与实现第三弹——落地篇 接上文:浅谈Hybrid技术的设计与实现(阅读本文前,建议阅读这个先) ...

  6. 数据可视化案例 | 如何打造数据中心APP产品

    意识到数据探索带来的无尽信息,越来越多的企业开始建立自有的数据分析平台,打造数据化产品,实现数据可视化. 在零售商超行业,沃尔玛"啤酒与尿布"的故事已不再是传奇.无论是大数据还是小 ...

  7. Atitit mac os 版本 新特性 attilax大总结

    Atitit mac os 版本 新特性 attilax大总结 1. Macos概述1 2. 早期2 2.1. Macintosh OS (系统 1.0)  1984年2 2.2. Mac OS 7. ...

  8. [Android]使用Dagger 2依赖注入 - API(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092525.html 使用Dagger 2依赖注入 - API ...

  9. GreenDao3.0新特性解析(配置、注解、加密)

    Greendao3.0release与7月6日发布,其中最主要的三大改变就是:1.换包名 2.实体注解 3.加密支持的优化 本文里面会遇到一些代码示例,就摘了官方文档和demo里的例子了,因为他们的例 ...

  10. 如何用Github版本控制非Github库

    Git的图形化客户端有很多,不同的人可能习惯用不同的客户端.本人更习惯于Github的客户端,因为上Github比较多,同步代码到Github用官方的客户端是最方便的,所以也就更习惯于使用Github ...