1. 程式人生 > >Thinkphp5原始碼分析2--App.php 框架入口類

Thinkphp5原始碼分析2--App.php 框架入口類

<?php // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- // | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved. // +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) // +---------------------------------------------------------------------- // | Author: liu21st <[email protected]> // +----------------------------------------------------------------------
namespace think;
use think\exception\ClassNotFoundException
; use think\exception\HttpException; use think\exception\HttpResponseException; use think\exception\RouteNotFoundException;
/** * App 應用管理 * @author liu21st <[email protected]> */
class App { /** * @varbool 是否初始化過 */ protected static $init
= false;
/** * @varstring 當前模組路徑 */ public static $modulePath;
/** * @varbool 應用除錯模式 */ public static $debug = true;
/** * @varstring 應用類庫名稱空間 */ public static $namespace = 'app';
/** * @varbool 應用類庫字尾 */ public static $suffix = false;
/** * @varbool 應用路由檢測 */ protected static $routeCheck;
/** * @varbool 嚴格路由檢測 */ protected static $routeMust;
/** * @vararray 請求排程分發 */ protected static $dispatch;
/** * @vararray 額外載入檔案 */ protected static $file = [];
/** * 執行應用程式 * @accesspublic * @paramRequest $request 請求物件 * @returnResponse * @throwsException */
//thinkphp5框架入口,執行run方法 public static function run(Request $request = null) { //返回例項think\Request方法,該方法在thinkphp\library\think\Request.php中 $request = is_null($request) ? Request::instance() : $request; //捕獲異常 try { // 執行當前類的initCommon方法,返回 \thinkphp\convention.php裡的全部配置資訊 $config = self::initCommon(); // 模組/控制器繫結 if (defined('BIND_MODULE')) { BIND_MODULE && Route::bind(BIND_MODULE);
// \thinkphp\convention.php配置裡auto_bind_module引數設為true時執行,預設不執行 } elseif ($config['auto_bind_module']) { // 入口自動繫結 $name = pathinfo($request->baseFile(), PATHINFO_FILENAME); if ($name && 'index' != $name && is_dir(APP_PATH . $name)) { Route::bind($name); } }
//thinkphp\convention.php配置裡default_filter引數設為true時執行,預設不執行 $request->filter($config['default_filter']);
// 預設語言 Lang::range($config['default_lang']);
// 開啟多語言機制 檢測當前語言 $config['lang_switch_on'] && Lang::detect(); $request->langset(Lang::range());
// 載入系統語言包 Lang::load([ THINK_PATH . 'lang' . DS . $request->langset() . EXT, APP_PATH . 'lang' . DS . $request->langset() . EXT, ]); // 監聽 app_dispatch Hook::listen('app_dispatch', self::$dispatch); // 獲取應用排程資訊 $dispatch = self::$dispatch; // 未設定排程資訊則進行 URL 路由檢測 if (empty($dispatch)) { /*執行當前類的routeCheck方法,獲取排程資訊,如訪問index模組下index控制器裡的index方法,則 $dispatch = array(2) { ["type"]=> string(6) "module" ["module"]=> array(3) { [0]=> string(5) "index" [1]=> string(5) "index" [2]=> string(5) "index" } } */ $dispatch = self::routeCheck($request, $config); } // 記錄當前排程資訊 將獲取的排程資訊,即模組,控制器,方法名存入Request類的dispatch屬性中 $request->dispatch($dispatch); // 記錄路由和請求資訊 ,調式模式,在\application\config.php 引數app_debug可配置 if (self::$debug) {
//將排程資訊寫入日誌 ,執行\thinkphp\library\think\Log.php 中 Log類的record方法 Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info');
/*將\thinkphp\library\think\Request.php Request類中header方法 獲取的頭部資訊寫入日誌 */ Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info');
//記錄當前請求的引數 Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info'); } // 監聽 app_begin Hook::listen('app_begin', $dispatch); // 請求快取檢查 $request->cache( $config['request_cache'], $config['request_cache_expire'], $config['request_cache_except'] ); //執行當前類的exec方法 $data = self::exec($dispatch, $config); } catch (HttpResponseException $exception) {
$data = $exception->getResponse();
}
// 清空類的例項化 Loader::clearInstance(); // 輸出資料到客戶端 判斷$data是否為Response類的例項 if ($data instanceof Response) { $response = $data; } elseif (!is_null($data)) { // 預設自動識別響應輸出型別 $type = $request->isAjax() ? Config::get('default_ajax_return') : Config::get('default_return_type'); /* 這句程式碼是整個框架的核心,$data 是獲得反射類的例項,假如訪問的是index模組下的index控制器中index方法 $data = new app\index\controller\Index();$data = $data->index(); 為什麼tp框架要用反射來執行類的方法? 當前App.php的名稱空間是think,而控制器的名稱空間是app\index\controller 反射很好的解決了執行不同名稱空間下的類,反射可以簡單地理解為例項化類後執行類中的方法。 具體可以檢視本類的 invokeFunction invokeMethod invokeClass三個方法。 將返回的反射例項$data,也就是 app\index\controller\Index下的index方法, 存入 \thinkphp\library\think\Response.php Response類中的$data屬性 */ $response = Response::create($data, $type);
} else { $response = Response::create(); }
// 監聽 app_end Hook::listen('app_end', $response); //返回返回的反射例項 return $response; }
/** * 初始化應用,並返回配置資訊 * @accesspublic * @returnarray */
public static function initCommon() { // 當前類中的init變數預設為false,也就是空,執行以下語句 if (empty(self::$init)) { /*判斷APP_NAMESPACE這個常量是否存在,預設APP_NAMESPACE不存在,無需執行此語句,如要配置, 可在thinkphp\base.php下加上 define('APP_NAMESPACE', 'application');*/ if (defined('APP_NAMESPACE')) { //若配置APP_NAMESPACE,則當前類的$namespace變數修改為所配置的值 self::$namespace = APP_NAMESPACE; } /*self::$namespace當前類的namespace屬性,APP_PATH: application目錄路徑, 執行先前載入的\thinkphp\library\think\Loader.php檔案Loader類中addNamespace方法*/ Loader::addNamespace(self::$namespace, APP_PATH); // 初始化應用,執行當前類的init方法,載入配置檔案 $config = self::init(); self::$suffix = $config['class_suffix']; // 應用除錯模式 self::$debug = Env::get('app_debug', Config::get('app_debug')); if (!self::$debug) { //設定錯誤資訊的類別, ini_set用來設定php.ini的值,在指令碼執行時保持新的值,並在指令碼結束時恢復 ini_set('display_errors', 'Off'); } elseif (!IS_CLI) { // 重新申請一塊比較大的 buffer if (ob_get_level() > 0) { $output = ob_get_clean(); }
ob_start();
if (!empty($output)) { echo $output; }
}
if (!empty($config['root_namespace'])) { Loader::addNamespace($config['root_namespace']); }
// 載入額外檔案 if (!empty($config['extra_file_list'])) { foreach ($config['extra_file_list'] as $file) { $file = strpos($file, '.') ? $file : APP_PATH . $file . EXT; if (is_file($file) && !isset(self::$file[$file])) { include $file; self::$file[$file] = true; } } }
// 設定系統時區 date_default_timezone_set($config['default_timezone']);
// 監聽 app_init Hook::listen('app_init');
self::$init = true; }
return Config::get(); }
/** * 初始化應用或模組 * @accesspublic * @paramstring $module 模組名 * @returnarray */ //此方法主要載入application和模組中配置檔案的資訊 private static function init($module = '') { // 定位模組目錄 $module = $module ? $module . DS : ''; /* \tpfive\public/../application/init.php 載入初始化檔案 ,判斷application目錄下是否存在init.php */ if (is_file(APP_PATH . $module . 'init' . EXT)) { include APP_PATH . $module . 'init' . EXT;
//判斷runtime目錄下是否存在init.php } elseif (is_file(RUNTIME_PATH . $module . 'init' . EXT)) { //載入init.php檔案 include RUNTIME_PATH . $module . 'init' . EXT; } else { /* $module值為空時獲取application目錄下的config.php裡全部配置資訊,$module有值時, 則獲取$module,也就是模組目錄下config.php裡的配置,所以配置寫在模組裡面的config.php也是可用的 */ $config = Config::load(CONF_PATH . $module . 'config' . CONF_EXT); // 讀取資料庫配置檔案 application目錄下的database.php $filename = CONF_PATH . $module . 'database' . CONF_EXT; Config::load($filename, 'database'); // 讀取擴充套件配置檔案 判斷是否存在application目錄下extra目錄 if (is_dir(CONF_PATH . $module . 'extra')) { $dir = CONF_PATH . $module . 'extra'; $files = scandir($dir); foreach ($files as $file) { if ('.' . pathinfo($file, PATHINFO_EXTENSION) === CONF_EXT) { $filename = $dir . DS . $file; //載入 \application\extra下配置檔案 Config::load($filename, pathinfo($file, PATHINFO_FILENAME)); } } }
// 載入應用狀態配置 if ($config['app_status']) { Config::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); } /* 載入行為擴充套件檔案 $module值為空時,判斷application\tags.php 有值時判斷$module也就是位址列模組下tags.php是否存在*/ if (is_file(CONF_PATH . $module . 'tags' . EXT)) { /*application目錄下的tags.php,執行\thinkphp\library\think\Hook.php Hook類中的import方法 傳入引數為\application\tags.php 和位址列模組下tags.php中的全部配置資訊, 把參入引數的配置資訊賦值到Hook類的$tags靜態屬性中*/ Hook::import(include CONF_PATH . $module . 'tags' . EXT); }
// 載入公共檔案 $path = APP_PATH . $module;
//判斷\application\common.php 或位址列模組下common.php是否存在 if (is_file($path . 'common' . EXT)) {
//載入\application\common.php 和位址列模組下common.php中的配置資訊 include $path . 'common' . EXT; }
// 載入當前模組語言包 if ($module) { Lang::load($path . 'lang' . DS . Request::instance()->langset() . EXT); }
} //返回所有配置資訊 return Config::get(); }
/** * 設定當前請求的排程資訊 * @accesspublic * @paramarray|string $dispatch 排程資訊 * @paramstring $type 排程型別 * @returnvoid */ public static function dispatch($dispatch, $type = 'module') { self::$dispatch = ['type' => $type, $type => $dispatch]; }
/** * 執行函式或者閉包方法 支援引數呼叫 * @accesspublic * @paramstring|array|\Closure $function 函式或者閉包 * @paramarray $vars 變數 * @returnmixed */ public static function invokeFunction($function, $vars = []) { $reflect = new \ReflectionFunction($function); $args = self::bindParams($reflect, $vars);
// 記錄執行資訊 self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info');
return $reflect->invokeArgs($args); }
/** * 呼叫反射執行類的方法 支援引數繫結 * @accesspublic * @paramstring|array $method 方法 * @paramarray $vars 變數 * @returnmixed */ /*反射類的方法 $method = array(2) { [0]=> object(app\index\controller\Index)#5 (0) { } [1]=> string(5) "index" } */ public static function invokeMethod($method, $vars = []) { //首先判斷傳遞的第一個引數是否為陣列 if (is_array($method)) { $class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]); //例項化反射類中的方法 $reflect = new \ReflectionMethod($class, $method[1]); } else { // 靜態方法 $reflect = new \ReflectionMethod($method); } //執行當前類的bindParams方法 $args = self::bindParams($reflect, $vars);
//將當前反射類的資訊寫入日誌 self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info'); //執行反射類中的方法,並返回 return $reflect->invokeArgs(isset($class) ? $class : null, $args); }
/** * 呼叫反射執行類的例項化 支援依賴注入 * @accesspublic * @paramstring $class 類名 * @paramarray $vars 變數 * @returnmixed */
//反射操作 $class = ''app\index\controller\Index'; public static function invokeClass($class, $vars = []) { /* ReflectionClass函式是建立 $class 這個反射類,假設$class = 'app\index\controller\Index'; 也就是例項化 new app\index\controller\Index; */ $reflect = new \ReflectionClass($class);
//getConstructor 獲取類的建構函式 $constructor = $reflect->getConstructor(); //傳入類的建構函式,執行本類的bindParams方法 $args = $constructor ? self::bindParams($constructor, $vars) : []; //newInstanceArgs 建立一個類的新例項,給出的引數將傳遞到類的建構函式 return $reflect->newInstanceArgs($args); }
/** * 繫結引數 * @accessprivate * @param\ReflectionMethod|\ReflectionFunction $reflect 反射類 * @paramarray $vars 變數 * @returnarray */ /* $reflect 為例項化反射類中的方法 ,假設訪問index模組下index控制器中的index方法, $reflect = new app\index\controller\Index(); $reflect = $reflect->index(); */ private static function bindParams($reflect, $vars = []) { //自動獲取請求變數 if (empty($vars)) { /* 'url_param_type在\application\config.php配置 // URL引數方式 0 按名稱成對解析 1 按順序解析 'url_param_type' => 0,預設$vars 獲取路由規則或引數配置 */ $vars = Config::get('url_param_type') ? Request::instance()->route() : Request::instance()->param(); } $args = [];
//getNumberOfParameters 獲取建構函式引數的個數 if ($reflect->getNumberOfParameters() > 0) { // 判斷陣列型別 數字陣列時按順序繫結引數 reset($vars); $type = key($vars) === 0 ? 1 : 0; //getParameters 獲取函式全部引數 foreach ($reflect->getParameters() as $param) { //迴圈遍歷,作為引數傳遞給當前類的getParamValue方法 $args[] = self::getParamValue($param, $vars, $type); } } //返回處理後的引數 return $args; }
/** * 獲取引數值 * @accessprivate * @param\ReflectionParameter $param 引數 * @paramarray $vars 變數 * @paramstring $type 類別 * @returnarray */ private static function getParamValue($param, &$vars, $type) { $name = $param->getName(); $class = $param->getClass(); //執行反射類的方法 if ($class) { $className = $class->getName(); $bind = Request::instance()->$name;
if ($bind instanceof $className) { $result = $bind; } else { if (method_exists($className, 'invoke')) { $method = new \ReflectionMethod($className, 'invoke');
if ($method->isPublic() && $method->isStatic()) { return $className::invoke(Request::instance()); } }
$result = method_exists($className, 'instance') ? $className::instance() : new $className; } } elseif (1 == $type && !empty($vars)) { $result = array_shift($vars); } elseif (0 == $type && isset($vars[$name])) { $result = $vars[$name];
//解析執行類建構函式時執行 } elseif ($param->isDefaultValueAvailable()) { //getDefaultValue拿到建構函式引數的值 $result = $param->getDefaultValue(); } else { throw new \InvalidArgumentException('method param miss:' . $name); }
return $result; }
/** * 執行呼叫分發 * @accessprotected * @paramarray $dispatch 呼叫資訊 * @paramarray $config 配置資訊 * @returnResponse|mixed * @throws\InvalidArgumentException */ protected static function exec($dispatch, $config) { 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': // 執行控制器操作 $vars = array_merge(Request::instance()->param