自动判断应该Ajax还是return
起因
最近回顾以前的代码,发现一个偶尔会见到的现象。一个类里面的方法可能需要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的更多相关文章
- Andoid自动判断输入是电话,网址或者Email的方法----Linkify的应用!
本节要讲的是,当我们在一个EditText输入电话或者网址还是Email的时候,让Android自动判断,当我们输入的是电话,我们点击输入内容将调用打电话程序,当我们输入是网址点击将打开浏览器程序.而 ...
- 彻底解决android读取中文txt的乱码(自动判断文档类型并转码
原文:http://blog.csdn.net/handsomedylan/article/details/6138400 public String convertCodeAndGetText(St ...
- onsubmit校验表单时利用ajax的return false无效解决方法
代码: function checkNewEmail(){ var re_email=new RegExp("\\w+@\\w+\\.\\w+\\.?\\w*"); var new ...
- onsubmit校验表单时利用ajax的return false无效解决方法-转
原来的代码 function checkNewEmail(){ var re_email=new RegExp("\\w+@\\w+\\.\\w+\\.?\\w*"); ...
- 判断post,ajax,get请求的方法
判断post,ajax,get请求的方法 define('IS_GET',isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] ...
- jquery中ajax用return来返回值无效
jquery中,ajax返回值,有三种写法,只有其中一种是成功的 /** * async:false,同步调用 * 返回1:2 * 失败 * 分析:ajax内部是一个或多个定义的函数,ajax中ret ...
- jQuery的ajax中return语句无法返回值
今天在做一个新需求的时候,用到jQuery的ajax来返回一个查询结果: 但是调用这个方法的时候,data有数据,调用的地方获取到的却一直都是undefined,在网上搜索了一些资料,找到了问题所在, ...
- Pace.js – 超赞的页面加载进度自动指示和 Ajax 导航效果
在页面中引入 Pace.js 和您所选择主题的 CSS 文件,就可以让你的页面拥有漂亮的加载进度和 Ajax 导航效果.不需要挂接到任何代码,自动检测进展.您可以选择颜色和多种效果,有简约,闪光灯, ...
- Andoid自动判断输入是电话,网址或者Email的方法--Linkify
Andoid自动判断输入是电话,网址或者Email的方法----Linkify的应用!http://blog.csdn.net/android_tutor/article/details/500016 ...
随机推荐
- 在DevExpress中使用CameraControl控件进行摄像头图像采集
在我们以前的项目了,做摄像头的图片采集,我们一般还是需要做一个封装处理的,在较新版本的DevExpress控件里面,增加了一个CameraControl控件,可以直接调用摄像头显示的,因此也可以做头像 ...
- js正则表达式校验非正整数:^((-\d+)|(0+))$
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- 解决VS调试时断点不会命中
断点调试是VS中的一大利器,有了它我们可以快速定位到代码的问题所在.在某些情况下会导致设置了断点后程序无法在断点处停下,下面分4种情况来解决断点不会命中的问题 百度经验:jingyan.baidu.c ...
- Java 单例模式详解
概念: java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例.饿汉式单例.登记式单例三种. 单例模式有一下特点: 1.单例类只能有一个实例. 2.单例类必须自己自己创建自己的唯一实例. ...
- 2016年最新mac下vscode配置golang开发环境支持debug
网上目前还找不到完整的mac下golang环境配置支持,本人配置成功,现在整理分享出来. mac最好装下xcode,好像有依赖关系安装Homebrew打开终端窗口, 粘贴脚本执行/usr/bin/ru ...
- Maven部署构件至远程仓库
私服的一大作用就是部署第三方构件,包括组织内的生成的构件以及一些无法从外部仓库获取的构件.无论是日常开发中生成的构件,还是正式版本发布的构件,都需要部署到仓库中,供其它团队成员使用.Maven除了能对 ...
- 使用cmd打开java文件,报错:“错误,编码GBK的不可映射字符”
今天使用EditPlus写了一个小程序,用cmd运行时报错--"错误,编码GBK的不可映射字符". 处理办法是用EditPlus另存为时,把编码格式由UTF-8改为ANSI. 然后 ...
- 浅析CSS中的BFC和IFC
1. 为什么会有BFC和IFC 首先要先了解两个概念:Box和formatting context: Box:CSS渲染的时候是以Box作为渲染的基本单位.Box的类型由元素的类型和display属性 ...
- iOS - 静态库的创建与使用
在日常项目开发中,不论是为了两个公司项目上的业务交流还是为了减少项目的编译时间,有的时候我们会把项目中的私密内容打包成静态库,或者是把项目中变动较少一部分打包成静态库以便提高编译效率,那么下面我们就来 ...
- grunt-笔记
package.json: { "name": "grunt-uglify", "version": "1.0.0", ...