ThinkPHP5源码分析之分发输出(4)

接上章,App获取调度后进行分发以及浏览器的响应输出,见代码:

function fun(Request $request = null) {
  ……
  //见源码分析app(3)
  ……

  switch ($dispatch['type']) {
    case 'redirect':    // 执行重定向跳转    
      $data = Response::create($dispatch['url'], 'redirect')->code($dispatch['status']);    
      break;
    case 'module':    // 模块/控制器/操作    
      $data = self::module($dispatch['module'], $config, isset($dispatch['convert']) ?   $dispatch['convert'] : null);      
       break;
    case 'controller':    // 执行控制器操作    
      $data = Loader::action($dispatch['controller'], $dispatch['params']);    
      break;
    case 'method':
      // 执行回调方法
      $data = self::invokeMethod($dispatch['method'], $dispatch['params']);
      break;
    case 'function':
      // 执行闭包
      $data = self::invokeFunction($dispatch['function'], $dispatch['params']);
      break;
    case 'response':
      $data = $dispatch['response'];
      break;
    ……  
  }catch (HttpResponseException $exception) {
    $data = $exception->getResponse();
  }

  //清空类的实例化
  Loader::clearInstance();

  //输出数据到客户端
  if ($data instanceof Response) {
    $response = $data;
  }elseif (!is_null($data)) {
    
    //获取请求类型以及配置返回格式类型等
    $isAjax   = $request->isAjax();
    $type     = $isAjax ? Config::get('default_ajax_return') : Config::get('default_return_type');

    //创建响应的一个对象 详细见Response类
    $response = Response::create($data, $type);
  }else{
    $response = Response::create();
  }
  ……
  return $response;
}

首先我们按照正常请求路由执行module,也是App类内方法,见代码:

function module($result, $config, $convert = null){
  //没的说,既然是个请求,例行单例下后续要用到
  $request = Request::instance();

  //配置,多模块的部署
  if ($config['app_multi_module']) {

    //默认模块index 如果是多模块部署,url不代模块参数,就是默认模块为当前模块咯
    $module    = strip_tags(strtolower($result[0] ?: $config['default_module']));

    //这里其实是route::bind()绑定模块,更简单的url访问。
    /*
      例:
      我在公共模块(先加载) Route::bind('index'); 
      那么就是说绑定当前模块到index模块
    */
    $bind      = Route::getBind('module');

    //模块绑定逻辑是否执行标记(我也不知道咋描述,姑且用一个自我感觉高大上的描述吧,哈哈)
    $available = false   ;

    if ($bind) {
      if ($module == $bindModule) {
        $available = true;
      }
    }elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) {
    //如果这个模块没有在禁用模块里面 并且也存在 那说有这个模块,必须执行啊。
      $available = true;
    }

    if ($module && $available) {
      //填充Request对象module
      $request->module($module);
      $config = self::init($module);
    } else {
      //抛出异常 module not exists;
    }
  }else{
    $module = '';
    $request->module($module);
  }
  // 是否自动转换控制器和操作名
  /*
  url_convert 官方文档是这样说的:关闭URL中控制器和操作名的自动转换
  如果controller TestConvert 那么以下
  http://server/Index/TestConvert/index  or http://server/Index/test_convert/index都是有效的
  http://server/Index/testconvert/index无效的 我还是建议搭建开启,不然url太难看了
  */
  $convert = is_bool($convert) ? $convert : $config['url_convert'];

  // 获取控制器名
  $controller = strip_tags($result[1] ?: $config['default_controller']);

  // 获取操作名
  $actionName = strip_tags($result[2] ?: $config['default_action']);

  //填充controller action
  $request->controller($controller)->action($actionName);

  try {
    //controller其实就是进行当前控制器的实例化,后面进行反射绑定执行action
    $instance = Loader::controller($controller, $config['url_controller_layer'], $config['controller_suffix'], $config['empty_controller']);

    //封装的反射 下面详细说
    $data = self::invokeMethod([$instance,$action]);
  }catch (\ReflectionException $e) {

    //魔术方法,如果请求的action不存在,如果控制器开发者有自定义一个_empty()那就执行咯
    if (method_exists($instance, '_empty')) {
      $method = new \ReflectionMethod($instance, '_empty');
      $data   = $method->invokeArgs($instance, [$action, '']);
    } else {
      //gg method(action)not found
    }
  }
  return $data;
}

function invokeMethod($method, $vars = []) {
  
  //这里其实就是获取请求的参数(post/get/put……)详细见Request类
  $vars = Request::instance()->param();
  
  //两种反射
  /*
    ① [$controller, $action] 类/方法
    ② [$static_function] 静态方法
    反射:ReflectionMethod:通过PHP代码,就可以得到某object的所有信息,并且可以和它交互,巴拉巴拉的
    说白了,就是执行类方法里的方法
  */
  if (is_array($method)) {
    $class   = is_object($method[0]) ? $method[0] : new $method[0];
    $reflect = new \ReflectionMethod($class, $method[1]);
  }else{
    $reflect = new \ReflectionMethod($method);
  }

  //绑定参数 $vars 为刚才Request::param() 获取的请求参数
  $args = self::bindParams($reflect, $vars);

  //官方文档说明:使用数组给方法传送参数,并执行他。
  return $reflect->invokeArgs(isset($class) ? $class : null, $args);
}

function bindParams($reflect, $vars){
  $args = [];

  // 判断数组类型 数字数组时按顺序绑定参数
  /*
    request 请求参数:
    ① ['params1' => 'value1', 'params2' => 'value2' ,……] 俗说就是关联数组啦
    ② ['params_val2', 'params_val2'] 当然是索引啦。
    如果key($vals)为1 那说明是索引的  按照controller类的action方法机型顺序绑定参数
    如果是关联的 根据参数名对应绑定
  */
  $type = key($vars) === 0 ? 1 : 0;

  //获取类方法的参数个数
  if ($reflect->getNumberOfParameters() > 0) {

    //获取参数 返回的是个对象集
    $params = $reflect->getParameters();

    foreach ($params as $param) {
      $name  = $param->getName();
      $class = $param->getClass();

      //没啥用
      if ($class && 'think\Request' == $class->getName()) {
        $args[] = Request::instance();
      } elseif (1 == $type && !empty($vars)) { //一个个按着顺序来
        $args[] = array_shift($vars);
      } elseif (0 == $type && isset($vars[$name])) { //一个个按着严格参数名来
        $args[] = $vars[$name];
      } elseif ($param->isDefaultValueAvailable()) { //如果这个值不是必须参数 默认值附上
        $args[] = $param->getDefaultValue();
      } else {
        //gg method param miss
      }
    }

    //过滤掉表单参数中的表达式 这里附上Request::filterExp  
    array_walk_recursive($args, [Request::instance(), 'filterExp']);
  }
}

Request::filterExp  
function filterExp(&$value){
  //看见没 是不是很像数据库里的各种表达式哇
  if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i', $value)) {
    $value .= ' ';
  }
}

至此,一个请求的主动执行流程就走完了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,103评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,577评论 25 707
  • Address:https://www.zybuluo.com/XiangZhou/note/208532 Exp...
    天蠍蒗漫阅读 11,179评论 2 55
  • 天空的阴霾随着雨水的冲刷渐渐散去,空气中环绕着清新的气息,突然,那片云裂开了,一道道白光从里面探出了头。天晴了...
    贝壳里的鱼阅读 297评论 0 0
  • 地雷花 我一直以来是不太喜欢花的人,直到人到中年以后,才对鲜花有了点感觉,觉得它们的存在适当的点缀了人生,...
    唏里呼噜阅读 447评论 2 1