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

作者: 伊凡丶real | 来源:发表于2016-07-23 20:15 被阅读611次

接上章,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 .= ' ';
  }
}

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

相关文章

网友评论

    本文标题:ThinkPHP5源码分析之分发输出(4)

    本文链接:https://www.haomeiwen.com/subject/wajwjttx.html