🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] * * * * * ## 1 应用调度源代码(thinkphp/library/think/App::run()) ~~~ switch (self::$dispatch['type']) { case 'redirect': header('Location: ' . self::$dispatch['url'], true, self::$dispatch['status']); break; case 'module': $data = self::module(self::$dispatch['module'], $config); break; case 'controller': $data = Loader::action(self::$dispatch['controller'], self::$dispatch['params']); break; case 'method': $data = self::invokeMethod(self::$dispatch['method'], self::$dispatch['params']); break; case 'function': $data = self::invokeFunction(self::$dispatch['function'], self::$dispatch['params']); break; default: throw new Exception('dispatch type not support', 10008); } ~~~ ## 2 应用调度分析 ### 1 路由解析 ~~~ switch (self::$dispatch['type']) {} ~~~ $dispatch['type']是App::run()中 经过self::route(),self::dispatch()后得到 * * * * * ~~~ if (empty(self::$dispatch['type'])) { self::route($config); } ~~~ >[info] 解析url,生成$dispatch 检查self::$dispatch['type'], 调用self::route()解析url,生成$dispatch。 * * * * * ~~~ public static function route(array $config) { self::parsePathinfo($config); if (empty($_SERVER['PATH_INFO'])) { $_SERVER['PATH_INFO'] = ''; define('__INFO__', ''); define('__EXT__', ''); } else { $_SERVER['PATH_INFO'] = trim($_SERVER['PATH_INFO'], '/'); define('__INFO__', $_SERVER['PATH_INFO']); define('__EXT__', strtolower(pathinfo($_SERVER['PATH_INFO'], PATHINFO_EXTENSION))); if ($config['url_deny_suffix'] && preg_match('/\.(' . $config['url_deny_suffix'] . ')$/i', __INFO__)) { throw new Exception('url suffix deny'); } $_SERVER['PATH_INFO'] = preg_replace($config['url_html_suffix'] ? '/\.(' . trim($config['url_html_suffix'], '.') . ')$/i' : '/\.' . __EXT__ . '$/i', '', __INFO__); } $depr = $config['pathinfo_depr']; $result = false; if (APP_ROUTE_ON && !empty($config['url_route_on'])) { if (!empty($config['route'])) { Route::register($config['route']); } $result = Route::check($_SERVER['PATH_INFO'], $depr, !IS_CLI ? $config['url_domain_deploy'] : false); if (APP_ROUTE_MUST && false === $result && $config['url_route_must']) { throw new Exception('route not define '); } } if (false === $result) { $result = Route::parseUrl($_SERVER['PATH_INFO'], $depr); } self::dispatch($result); } ~~~ self::route()中,根据配置参数,解析url。 最后调用self::dispatch(),设置$disptach 。 具体分析见[附:应用启动文件](http://www.kancloud.cn/zmwtp/tp5/119429) * * * * * ~~~ public static function dispatch($dispatch) { self::$dispatch = $dispatch; } ~~~ 在self::route中调用,设置调度类型$dispatch * * * * * ### 2 五种调度类型 >[info] Redirect类型调度 ~~~ case 'redirect': header('Location: ' . self::$dispatch['url'], true, self::$dispatch['status']); break; ~~~ $dispatch['type']为**redirect**时的应用调度 直接跳转到**self::$dispatch['url']** >[info] Module调度类型 ~~~ case 'module': $data = self::module(self::$dispatch['module'], $config); break; ~~~ $dispatch['type']为**module**时的应用调度 调用**self::module()** >[info] Controller调度类型 ~~~ case 'controller': $data = Loader::action(self::$dispatch['controller'], self::$dispatch['params']); break; ~~~ $dispatch['type']为**controller**时的调度 调用自动加载器**Loader::action()** >[info] Method调度类型 ~~~ case 'method': $data = self::invokeMethod(self::$dispatch['method'], self::$dispatch['params']); break; ~~~ $dispatch['type']为**method**时的调度 调用**self::invokeMethod()** >[info] Function调度类型 ~~~ case 'function': $data = self::invokeFunction(self::$dispatch['function'], self::$dispatch['params']); break; ~~~ $dispatch['type']为**function**时的调度 调用**self::invokeFunction()** ~~~ default: throw new Exception('dispatch type not support', 10008); ~~~ 超出以上调度类型的直接抛出异常报错 * * * * * ## 3 调度方法实现 上面的五种调度类型中 除了redirect,controller调度类型外的其余三种调度类型 都是调用的App内的静态方法 moudle调度类型的self::module() method调度类型的self::invokeMethod() function调度类型的self::invokeFunction() 三个静态方法的实现原理基本相同, 依次为调用参数分析,反射回调相应方法, 关于反射原理见基本原理的[php的反射](http://www.kancloud.cn/zmwtp/tp5/119469) 下面按照源代码顺序依次分析这三个静态方法: ### 1 self::invokeFunction() ~~~ public static function invokeFunction($function, $vars = []) { $reflect = new \ReflectionFunction($function); $args = self::bindParams($reflect, $vars); APP_DEBUG && Log::record('[ RUN ] ' . $reflect->getFileName() . '[ ' . var_export($vars, true) . ' ]', 'info'); return $reflect->invokeArgs($args); } ~~~ > $function 调用的函数名 > $vars 调用函数的参数 `$reflect = new \ReflectionFunction($function);` 创建$function的反射函数类 `$args = self::bindParams($reflect, $vars);` 创建反射函数的参数 `APP_DEBUG && Log::record('[ RUN ] ' . $reflect->getFileName() . '[ ' . var_export($vars, true) . ' ]', 'info');` 日志记录调度运行 `return $reflect->invokeArgs($args);` 使用反射函数类调用函数,并返回结果保存到App::run()中的$data~~~ * * * * * ### 2 self::invokeMethod() ~~~ public static function invokeMethod($method, $vars = []) { if (empty($vars)) { switch (REQUEST_METHOD) { case 'POST': $vars = array_merge($_GET, $_POST); break; case 'PUT': parse_str(file_get_contents('php://input'), $vars); break; default: $vars = $_GET; } } 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); } $args = self::bindParams($reflect, $vars); APP_DEBUG && Log::record('[ RUN ] ' . $reflect->getFileName() . '[ ' . var_export($args, true) . ' ]', 'info'); return $reflect->invokeArgs(isset($class) ? $class : null, $args); } ~~~ > $method 调用的方法 > $vars 调用的方法的参数 ~~~ if (empty($vars)) { switch (REQUEST_METHOD) { case 'POST': $vars = array_merge($_GET, $_POST); break; case 'PUT': parse_str(file_get_contents('php://input'), $vars); break; default: $vars = $_GET; } } ~~~ 合并$_POST $_GET $_input等参数到$vars ~~~ 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); } ~~~ 检查$method 如果为数组则是调用对象的方法 如果不是数组则是调用静态方法 创建$method反射方法类 `$args = self::bindParams($reflect, $vars);` 同上创建参数 `APP_DEBUG && Log::record('[ RUN ] ' . $reflect->getFileName() . '[ ' . var_export($args, true) . ' ]', 'info');` 同上记录调度运行日志 `return $reflect->invokeArgs(isset($class) ? $class : null, $args);` 同上获取调用返回值, * * * * * ### 3 self::module() ~~~ private static function module($result, $config) { if (APP_MULTI_MODULE) { $module = strtolower($result[0] ?: $config['default_module']); if ($maps = $config['url_module_map']) { if (isset($maps[$module])) { define('MODULE_ALIAS', $module); $module = $maps[MODULE_ALIAS]; } elseif (array_search($module, $maps)) { $module = ''; } } define('MODULE_NAME', strip_tags($module)); if (MODULE_NAME && !in_array(MODULE_NAME, $config['deny_module_list']) && is_dir(APP_PATH . MODULE_NAME)) { define('MODULE_PATH', APP_PATH . MODULE_NAME . DS); define('VIEW_PATH', MODULE_PATH . VIEW_LAYER . DS); self::initModule(MODULE_NAME, $config); } else { throw new Exception('module [ ' . MODULE_NAME . ' ] not exists ', 10005); } } else { define('MODULE_NAME', ''); define('MODULE_PATH', APP_PATH); define('VIEW_PATH', MODULE_PATH . VIEW_LAYER . DS); } $controllerName = strip_tags($result[1] ?: Config::get('default_controller')); define('CONTROLLER_NAME', Config::get('url_controller_convert') ? strtolower($controllerName) : $controllerName); $actionName = strip_tags($result[2] ?: Config::get('default_action')); define('ACTION_NAME', Config::get('url_action_convert') ? strtolower($actionName) : $actionName); if (!preg_match('/^[A-Za-z](\/|\.|\w)*$/', CONTROLLER_NAME)) { throw new Exception('illegal controller name:' . CONTROLLER_NAME, 10000); } $instance = Loader::controller(CONTROLLER_NAME, '', Config::get('empty_controller')); $action = ACTION_NAME . Config::get('action_suffix'); try { $call = [$instance, $action]; APP_HOOK && Hook::listen('action_begin', $call); if (!preg_match('/^[A-Za-z](\w)*$/', $action)) { throw new Exception('illegal action name :' . ACTION_NAME, 10001); } $data = self::invokeMethod($call); } catch (\ReflectionException $e) { if (method_exists($instance, '_empty')) { $method = new \ReflectionMethod($instance, '_empty'); $data = $method->invokeArgs($instance, [$action, '']); APP_DEBUG && Log::record('[ RUN ] ' . $method->getFileName(), 'info'); } else { throw new Exception('method [ ' . (new \ReflectionClass($instance))->getName() . '->' . $action . ' ] not exists ', 10002); } } return $data; } ~~~ > $result 调用的模块信息 > $config 调用相关的配置信息 >[info] 1 模块参数处理 `if (APP_MULTI_MODULE) {}` 根据全局变量APP_MULTI_MODULE分为多模块部署和单模块部署 >[info] 1-1多模块部署 `$module = strtolower($result[0] ?: $config['default_module']);` 将调用的模块名称转换为小写 ~~~ if ($maps = $config['url_module_map']) { if (isset($maps[$module])) { define('MODULE_ALIAS', $module); $module = $maps[MODULE_ALIAS]; } elseif (array_search($module, $maps)) { $module = ''; } ~~~ 检查全局配置是否有别名配置 `define('MODULE_NAME', strip_tags($module));` 定义全局变量MODULE_NAME, ~~~ if (MODULE_NAME && !in_array(MODULE_NAME, $config['deny_module_list']) && is_dir(APP_PATH . MODULE_NAME)) { define('MODULE_PATH', APP_PATH . MODULE_NAME . DS); define('VIEW_PATH', MODULE_PATH . VIEW_LAYER . DS); self::initModule(MODULE_NAME, $config); } else { throw new Exception('module [ ' . MODULE_NAME . ' ] not exists ', 10005); } ~~~ 检查模块是否禁用$config['deny_module_list'] 模块对应目录是否存在APP_PATH.MODULE_NAME 定义全局变量MODULE_PATH 模块控制器文件路径 定义全局变量VIEW_PATH 模块视图文件路径 `self::initModule(MODULE_NAME, $config);` 初始化相应模块信息 >[info] 1-2 单一模块部署 ~~~ define('MODULE_NAME', ''); define('MODULE_PATH', APP_PATH); define('VIEW_PATH', MODULE_PATH . VIEW_LAYER . DS); ~~~ 定义全局变量MODULE_NAME,MODULE_PATH,VIEW_PATH。 * * * * * >[info] 2 控制器参数处理 ` $controllerName = strip_tags($result[1] ?: Config::get('default_controller'));` 获取控制器名称 ` define('CONTROLLER_NAME', Config::get('url_controller_convert') ? strtolower($controllerName) : $controllerName);` 定义全局变量CONTROLLER_NAME 默认为小写的控制器名字 * * * * * >[info] 3 操作参数处理 `$actionName = strip_tags($result[2] ?: Config::get('default_action'));` 获取操作名称 `define('ACTION_NAME', Config::get('url_action_convert') ? strtolower($actionName) : $actionName);` 定义全局变量ACTION_NAME 默认为小写的操作名字 ~~~ if (!preg_match('/^[A-Za-z](\/|\.|\w)*$/', CONTROLLER_NAME)) { throw new Exception('illegal controller name:' . CONTROLLER_NAME, 10000); } ~~~ 检查控制器名称的合法性 `$instance = Loader::controller(CONTROLLER_NAME, '', Config::get('empty_controller'));` 加载控制器文件并创建控制器实例 `$action = ACTION_NAME . Config::get('action_suffix');` 获取回调控制器的操作 * * * * * >[info] 4 应用调度运行 * * * * * >[info] try部分 `$call = [$instance, $action];` 合并控制器对象 方法为数组参数 `APP_HOOK && Hook::listen('action_begin', $call);` 运行方法开始的监听回调 ~~~ if (!preg_match('/^[A-Za-z](\w)*$/', $action)) { throw new Exception('illegal action name :' . ACTION_NAME, 10001); } ~~~ 检查方法名称的合法性 `$data = self::invokeMethod($call);` 使用反射调用相应对象的方法 >[info] catch部分 ~~~ if (method_exists($instance, '_empty')) { $method = new \ReflectionMethod($instance, '_empty'); $data = $method->invokeArgs($instance, [$action, '']); APP_DEBUG && Log::record('[ RUN ] ' . $method->getFileName(), 'info'); } else { throw new Exception('method [ ' . (new \ReflectionClass($instance))->getName() . '->' . $action . ' ] not exists ', 10002); } ~~~ 对异常的处理 如果操作不存在 检查_empty方法是否定义 并调用_empty方法 或者返回方法不存在异常 `return $data;` 返回调用的值 * * * * * ### 4 self::bindParams() ~~~ private static function bindParams($reflect, $vars) { $args = []; $keys = array_keys($vars); $type = array_keys($keys) === $keys ? 1 : 0; if ($reflect->getNumberOfParameters() > 0) { $params = $reflect->getParameters(); foreach ($params as $param) { $name = $param->getName(); if (1 == $type && !empty($vars)) { $args[] = array_shift($vars); } elseif (0 == $type && isset($vars[$name])) { $args[] = $vars[$name]; } elseif ($param->isDefaultValueAvailable()) { $args[] = $param->getDefaultValue(); } else { throw new Exception('method param miss:' . $name, 10004); } } array_walk_recursive($args, 'think\\Input::filterExp'); } return $args; } ~~~ > $reflect 反射类 > $vars 调用参数 ~~~ $keys = array_keys($vars); $type = array_keys($keys) === $keys ? 1 : 0; ~~~ 参数的数组类型转换, ~~~ if ($reflect->getNumberOfParameters() > 0) { $params = $reflect->getParameters(); foreach ($params as $param) { $name = $param->getName(); if (1 == $type && !empty($vars)) { $args[] = array_shift($vars); } elseif (0 == $type && isset($vars[$name])) { $args[] = $vars[$name]; } elseif ($param->isDefaultValueAvailable()) { $args[] = $param->getDefaultValue(); } else { throw new Exception('method param miss:' . $name, 10004); } } array_walk_recursive($args, 'think\\Input::filterExp'); } ~~~ 将参数$args添加到回调方法的参数$args中 `return $args;` 返回合成的参数 * * * * * ## 4 总结 应用调度总的思路是根据调度类型,通过不同的调度处理,返回调度处理的结果到App::run(),然后调用Response::send()返回到客户端。 **这里的调度是从框架底层到应用业务的跳转核心,需要认真分析。**