2025-07-08
namespace think;// 放到同一個大包 下面
use think\Config;// 使用 配置類
use think\Env;// 使用環境類
use think\Exception;// 使用異常包
use think\exception\HttpException;// 使用異常包 http包
use think\exception\HttpResponseException;// 使用異常包 http response 包
use think\Hook;// 使用 鉤子 類
use think\Lang;// 使用 語言 類
use think\Loader;// 使用 加載 類
use think\Log;// 使用 日志 類
use think\Request;// 使用 請求 類
use think\Response;// 使用 返回 類
use think\Route;// 使用 路由 類
// 這個類應算是 皇上類瞭 可以調度基本上 全部的資源
/**
 * App 應用管理
 */
class App
{
    /**
     * @var bool 是否初始化過
     */
    protected static $init = false;// 初始化 標志位

    /**
     * @var string 當前模塊路徑
     */
    public static $modulePath;// 初始化 當前 模塊 路徑

    /**
     * @var bool 應用調試模式
     */
    public static $debug = true;// 應用調試 模式

    /**
     * @var string 應用類庫命名空間
     */
    public static $namespace = 'app';// 應用 類庫 命名空間

    /**
     * @var bool 應用類庫後綴
     */
    public static $suffix = false;// 應用 類庫 後綴

    /**
     * @var bool 應用路由檢測
     */
    protected static $routeCheck;// 應用 路由 檢測

    /**
     * @var bool 嚴格路由檢測
     */
    protected static $routeMust; // 嚴格 路由檢測

    protected static $dispatch;// 路由調度
    protected static $file = []; // 文件加載

    /**
     * 執行應用程式
     * @access public
     * @param Request $request Request對象
     * @return Response
     * @throws Exception
     */
    public static function run(Request $request = null)
    {// thinkphp經過瞭 自動加載、錯誤接管、配置文件預設,終於開始執行瞭。
        // 第一步:獲取請求參數
        is_null($request) && $request = Request::instance();
        // self::$instance = new static($options); 執行瞭 這個 instance
        // 默認 沒有傳入任何數值,is_null 所以執行 後面 $request = Request::instance();
        // 這個寫法 真的 很經典,我喜歡你,老劉,哈哈
        // 最終獲取瞭 一個 request 對象

        $config = self::initCommon();// 加載 初始化 配置 文件 選項


        // 2016-10-09
        if (defined('BIND_MODULE')) {// 默認此處是沒有定義的
            // 模塊/控制器綁定
            BIND_MODULE && Route::bind(BIND_MODULE);//self::$bind = ['type' => $type, $type => $bind];
            // 對 Route 的 $
        } elseif ($config['auto_bind_module']) {// 默認這裡也是沒有定義的
            // 入口自動綁定
            $name = pathinfo($request->baseFile(), PATHINFO_FILENAME);
            if ($name && 'index' != $name && is_dir(APP_PATH . $name)) {
                Route::bind($name);
            }
        }

        $request->filter($config['default_filter']);// 指定 過濾 參數 為空 直接 返回
        try {

            // 開啟多語言機制
            if ($config['lang_switch_on']) {
                // 獲取當前語言
                $request->langset(Lang::detect());
                // 加載系統語言包
                Lang::load(THINK_PATH . 'lang' . DS . $request->langset() . EXT);// 加載 系統 配置的語言包
                if (!$config['app_multi_module']) {// 如果沒有 多模塊配置
                    Lang::load(APP_PATH . 'lang' . DS . $request->langset() . EXT);
                }
            }

            // 獲取應用調度信息
            $dispatch = self::$dispatch;// 默認 為空
            if (empty($dispatch)) {
                // 進行URL路由檢測
                $dispatch = self::routeCheck($request, $config);// 獲取調度信息
                // 分別 通過 請求 跟 配置 文件
            }
            // 記錄當前調度信息
            $request->dispatch($dispatch);// 配置調度 信息

            // 記錄路由和請求信息
            if (self::$debug) {
                Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info');
                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);// 這個基本上 什麼事情 都沒幹啊

            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']);
                    break;
                case 'method':
                    // 執行回調方法
                    $data = self::invokeMethod($dispatch['method']);
                    break;
                case 'function':
                    // 執行閉包
                    $data = self::invokeFunction($dispatch['function']);
                    break;
                case 'response':
                    $data = $dispatch['response'];
                    break;
                default:
                    throw new \InvalidArgumentException('dispatch type not support');
            }
        } 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::create($data, $type);
        } else {
            $response = Response::create();
        }

        // 監聽app_end
        Hook::listen('app_end', $response);// 監聽 結尾,

        return $response;// 此處返回 一個默認的類
    }

    /**
     * 設置當前請求的調度信息
     * @access public
     * @param array|string  $dispatch 調度信息
     * @param string        $type 調度類型
     * @return void
     */
    public static function dispatch($dispatch, $type = 'module')
    {// 賦值 當前資源
        self::$dispatch = ['type' => $type, $type => $dispatch];
    }

    /**
     * 執行函數或者閉包方法 支持參數調用
     * @access public
     * @param string|array|\Closure $function 函數或者閉包
     * @param array                 $vars     變量
     * @return mixed
     */
    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);
    }

    /**
     * 調用反射執行類的方法 支持參數綁定
     * @access public
     * @param string|array $method 方法
     * @param array        $vars   變量
     * @return mixed
     */
    public static function invokeMethod($method, $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);
        }
        $args = self::bindParams($reflect, $vars);
        // 記錄執行信息
        self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info');
        return $reflect->invokeArgs(isset($class) ? $class : null, $args);
    }

    /**
     * 綁定參數
     * @access public
     * @param \ReflectionMethod|\ReflectionFunction $reflect 反射類
     * @param array             $vars    變量
     * @return array
     */
    private static function bindParams($reflect, $vars = [])
    {
        if (empty($vars)) {
            // 自動獲取請求變量
            if (Config::get('url_param_type')) {
                $vars = Request::instance()->route();
            } else {
                $vars = Request::instance()->param();
            }
        }
        $args = [];
        // 判斷數組類型 數字數組時按順序綁定參數
        reset($vars);
        $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) {
                    $className = $class->getName();
                    if (isset($vars[$name]) && $vars[$name] instanceof $className) {
                        $args[] = $vars[$name];
                        unset($vars[$name]);
                    } else {
                        $args[] = method_exists($className, 'instance') ? $className::instance() : new $className();
                    }
                } 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 {
                    throw new \InvalidArgumentException('method param miss:' . $name);
                }
            }
            // 全局過濾
            array_walk_recursive($args, [Request::instance(), 'filterExp']);
        }
        return $args;
    }

    /**
     * 執行模塊
     * @access public
     * @param array $result 模塊/控制器/操作
     * @param array $config 配置參數
     * @param bool  $convert 是否自動轉換控制器和操作名
     * @return mixed
     */
    public static function module($result, $config, $convert = null)
    {// 其實就對 模型 路徑的一個解析
        if (is_string($result)) {
            $result = explode('/', $result);
        }
        $request = Request::instance();
        if ($config['app_multi_module']) {
            // 多模塊部署
            $module    = strip_tags(strtolower($result[0] ?: $config['default_module']));
            $bind      = Route::getBind('module');
            $available = false;
            if ($bind) {
                // 綁定模塊
                list($bindModule) = explode('/', $bind);
                if (empty($result[0])) {
                    $module    = $bindModule;
                    $available = true;
                } elseif ($module == $bindModule) {
                    $available = true;
                }
            } elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) {
                $available = true;
            }

            // 模塊初始化
            if ($module && $available) {
                // 初始化模塊
                $request->module($module);
                $config = self::init($module);
            } else {
                throw new HttpException(404, 'module not exists:' . $module);
            }
        } else {
            // 單一模塊部署
            $module = '';
            $request->module($module);
        }
        // 當前模塊路徑
        App::$modulePath = APP_PATH . ($module ? $module . DS : '');

        // 是否自動轉換控制器和操作名
        $convert = is_bool($convert) ? $convert : $config['url_convert'];
        // 獲取控制器名
        $controller = strip_tags($result[1] ?: $config['default_controller']);
        $controller = $convert ? strtolower($controller) : $controller;

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

        // 設置當前請求的控制器、操作
        $request->controller(Loader::parseName($controller, 1))->action($actionName);

        // 監聽module_init
        Hook::listen('module_init', $request);

        try {
            $instance = Loader::controller($controller, $config['url_controller_layer'], $config['controller_suffix'], $config['empty_controller']);
            if (is_null($instance)) {
                throw new HttpException(404, 'controller not exists:' . Loader::parseName($controller, 1));
            }
            // 獲取當前操作名
            $action = $actionName . $config['action_suffix'];
            if (!preg_match('/^[A-Za-z](\w)*$/', $action)) {
                // 非法操作
                throw new \ReflectionException('illegal action name:' . $actionName);
            }

            // 執行操作方法
            $call = [$instance, $action];
            Hook::listen('action_begin', $call);

            $data = self::invokeMethod($call);
        } catch (\ReflectionException $e) {
            // 操作不存在
            if (method_exists($instance, '_empty')) {
                $reflect = new \ReflectionMethod($instance, '_empty');
                $data    = $reflect->invokeArgs($instance, [$action]);
                self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info');
            } else {
                throw new HttpException(404, 'method not exists:' . (new \ReflectionClass($instance))->getName() . '->' . $action);
            }
        }
        return $data;
    }

    /**
     * 初始化應用
     */
    public static function initCommon()
    {
        if (empty(self::$init)) {// 初始化 配置 選項
            // 初始化應用
            $config       = self::init();
            self::$suffix = $config['class_suffix'];// 此處 默認是 false 方式。

            // 應用調試模式
            self::$debug = Env::get('app_debug', Config::get('app_debug'));
            if (!self::$debug) {// 如果 非 系統 顯示
                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;
                }
            }

            // 註冊應用命名空間
            self::$namespace = $config['app_namespace'];
            Loader::addNamespace($config['app_namespace'], APP_PATH);
            if (!empty($config['root_namespace'])) {// 默認為空
                Loader::addNamespace($config['root_namespace']);
            }

            // 加載額外文件
            if (!empty($config['extra_file_list'])) {// 加載幫助文件
                //'extra_file_list'        => [THINK_PATH . 'helper' . EXT],
                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 = $config;
        }
        return self::$init;// 返回 他【它】
    }

    /**
     * 初始化應用或模塊
     * @access public
     * @param string $module 模塊名
     * @return array
     */
    private static function init($module = '')
    {
        // 定位模塊目錄
        $module = $module ? $module . DS : '';// 默認為空

        // 加載初始化文件
        if (is_file(APP_PATH . $module . 'init' . EXT)) {// 如果存在 模塊 初始化 文件
            include APP_PATH . $module . 'init' . EXT;
        } elseif (is_file(RUNTIME_PATH . $module . 'init' . EXT)) {// 如果存在 運行編譯後的,初始化模塊
            include RUNTIME_PATH . $module . 'init' . EXT;
        } else {// 進行默認 項目的加載
            $path = APP_PATH . $module;
            // 加載模塊配置
            $config = Config::load(CONF_PATH . $module . 'config' . CONF_EXT);// 默認加載 application/config.php 文件

            // 讀取擴展配置文件
            if ($config['extra_config_list']) {
                // 默認配置為 'extra_config_list'      => ['database', 'validate'],
                foreach ($config['extra_config_list'] as $name => $file) {
                    $filename = CONF_PATH . $module . $file . CONF_EXT;
                    Config::load($filename, is_string($name) ? $name : pathinfo($filename, PATHINFO_FILENAME));
                }
            }

            // 加載應用狀態配置
            if ($config['app_status']) {
                $config = Config::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT);
            }

            // 加載行為擴展文件
            if (is_file(CONF_PATH . $module . 'tags' . EXT)) {
                Hook::import(include CONF_PATH . $module . 'tags' . EXT);
            }

            // 加載公共文件
            if (is_file($path . 'common' . EXT)) {
                include $path . 'common' . EXT;
            }

            // 加載當前模塊語言包
            if ($config['lang_switch_on'] && $module) {
                Lang::load($path . 'lang' . DS . Request::instance()->langset() . EXT);
            }// 語言包 加載 默認為空
        }
        return Config::get();// 返回 Config::$config['_sys_']// 內容,
        // 以及對應的 普通 文件包含
    }

    /**
     * URL路由檢測(根據PATH_INFO)
     * @access public
     * @param  \think\Request $request
     * @param  array          $config
     * @return array
     * @throws \think\Exception
     */
    public static function routeCheck($request, array $config)
    {
        $path   = $request->path();
        $depr   = $config['pathinfo_depr'];
        $result = false;
        // 路由檢測
        $check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on'];
        if ($check) {
            // 開啟路由
            if (is_file(RUNTIME_PATH . 'route.php')) {
                // 讀取路由緩存
                $rules = include RUNTIME_PATH . 'route.php';
                if (is_array($rules)) {
                    Route::rules($rules);
                }
            } else {
                $files = $config['route_config_file'];
                foreach ($files as $file) {
                    if (is_file(CONF_PATH . $file . CONF_EXT)) {
                        // 導入路由配置
                        $rules = include CONF_PATH . $file . CONF_EXT;
                        if (is_array($rules)) {
                            Route::import($rules);
                        }
                    }
                }
            }

            // 路由檢測(根據路由定義返回不同的URL調度)
            $result = Route::check($request, $path, $depr, $config['url_domain_deploy']);
            $must   = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must'];
            if ($must && false === $result) {
                // 路由無效
                throw new HttpException(404, 'Route Not Found');
            }
        }
        if (false === $result) {
            // 路由無效 解析模塊/控制器/操作/參數... 支持控制器自動搜索
            $result = Route::parseUrl($path, $depr, $config['controller_auto_search']);
        }
        return $result;
    }

    /**
     * 設置應用的路由檢測機制
     * @access public
     * @param  bool $route 是否需要檢測路由
     * @param  bool $must  是否強制檢測路由
     * @return void
     */
    public static function route($route, $must = false)
    {
        self::$routeCheck = $route;
        self::$routeMust  = $must;
    }
}

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *