App.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +----------------------------------------------------------------------
  9. // | Author: liu21st <liu21st@gmail.com>
  10. // +----------------------------------------------------------------------
  11. namespace think;
  12. use think\exception\ClassNotFoundException;
  13. use think\exception\HttpException;
  14. use think\exception\HttpResponseException;
  15. use think\exception\RouteNotFoundException;
  16. /**
  17. * App 应用管理
  18. * @author liu21st <liu21st@gmail.com>
  19. */
  20. class App
  21. {
  22. /**
  23. * @var bool 是否初始化过
  24. */
  25. protected static $init = false;
  26. /**
  27. * @var string 当前模块路径
  28. */
  29. public static $modulePath;
  30. /**
  31. * @var bool 应用调试模式
  32. */
  33. public static $debug = true;
  34. /**
  35. * @var string 应用类库命名空间
  36. */
  37. public static $namespace = 'app';
  38. /**
  39. * @var bool 应用类库后缀
  40. */
  41. public static $suffix = false;
  42. /**
  43. * @var bool 应用路由检测
  44. */
  45. protected static $routeCheck;
  46. /**
  47. * @var bool 严格路由检测
  48. */
  49. protected static $routeMust;
  50. /**
  51. * @var array 请求调度分发
  52. */
  53. protected static $dispatch;
  54. /**
  55. * @var array 额外加载文件
  56. */
  57. protected static $file = [];
  58. /**
  59. * 执行应用程序
  60. * @access public
  61. * @param Request $request 请求对象
  62. * @return Response
  63. * @throws Exception
  64. */
  65. public static function run(Request $request = null)
  66. {
  67. $request = is_null($request) ? Request::instance() : $request;
  68. try {
  69. $config = self::initCommon();
  70. // 模块/控制器绑定
  71. if (defined('BIND_MODULE')) {
  72. BIND_MODULE && Route::bind(BIND_MODULE);
  73. } elseif ($config['auto_bind_module']) {
  74. // 入口自动绑定
  75. $name = pathinfo($request->baseFile(), PATHINFO_FILENAME);
  76. if ($name && 'index' != $name && is_dir(APP_PATH . $name)) {
  77. Route::bind($name);
  78. }
  79. }
  80. $request->filter($config['default_filter']);
  81. // 默认语言
  82. Lang::range($config['default_lang']);
  83. // 开启多语言机制 检测当前语言
  84. $config['lang_switch_on'] && Lang::detect();
  85. $request->langset(Lang::range());
  86. // 加载系统语言包
  87. Lang::load([
  88. THINK_PATH . 'lang' . DS . $request->langset() . EXT,
  89. APP_PATH . 'lang' . DS . $request->langset() . EXT,
  90. ]);
  91. // 监听 app_dispatch
  92. Hook::listen('app_dispatch', self::$dispatch);
  93. // 获取应用调度信息
  94. $dispatch = self::$dispatch;
  95. // 未设置调度信息则进行 URL 路由检测
  96. if (empty($dispatch)) {
  97. $dispatch = self::routeCheck($request, $config);
  98. }
  99. // 记录当前调度信息
  100. $request->dispatch($dispatch);
  101. // 记录路由和请求信息
  102. if (self::$debug) {
  103. Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info');
  104. Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info');
  105. Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info');
  106. }
  107. // 监听 app_begin
  108. Hook::listen('app_begin', $dispatch);
  109. // 请求缓存检查
  110. $request->cache(
  111. $config['request_cache'],
  112. $config['request_cache_expire'],
  113. $config['request_cache_except']
  114. );
  115. $data = self::exec($dispatch, $config);
  116. } catch (HttpResponseException $exception) {
  117. $data = $exception->getResponse();
  118. }
  119. // 清空类的实例化
  120. Loader::clearInstance();
  121. // 输出数据到客户端
  122. if ($data instanceof Response) {
  123. $response = $data;
  124. } elseif (!is_null($data)) {
  125. // 默认自动识别响应输出类型
  126. $type = $request->isAjax() ?
  127. Config::get('default_ajax_return') :
  128. Config::get('default_return_type');
  129. $response = Response::create($data, $type);
  130. } else {
  131. $response = Response::create();
  132. }
  133. // 监听 app_end
  134. Hook::listen('app_end', $response);
  135. return $response;
  136. }
  137. /**
  138. * 初始化应用,并返回配置信息
  139. * @access public
  140. * @return array
  141. */
  142. public static function initCommon()
  143. {
  144. if (empty(self::$init)) {
  145. if (defined('APP_NAMESPACE')) {
  146. self::$namespace = APP_NAMESPACE;
  147. }
  148. Loader::addNamespace(self::$namespace, APP_PATH);
  149. // 初始化应用
  150. $config = self::init();
  151. self::$suffix = $config['class_suffix'];
  152. // 应用调试模式
  153. self::$debug = Env::get('app_debug', Config::get('app_debug'));
  154. if (!self::$debug) {
  155. ini_set('display_errors', 'Off');
  156. } elseif (!IS_CLI) {
  157. // 重新申请一块比较大的 buffer
  158. if (ob_get_level() > 0) {
  159. $output = ob_get_clean();
  160. }
  161. ob_start();
  162. if (!empty($output)) {
  163. echo $output;
  164. }
  165. }
  166. if (!empty($config['root_namespace'])) {
  167. Loader::addNamespace($config['root_namespace']);
  168. }
  169. // 加载额外文件
  170. if (!empty($config['extra_file_list'])) {
  171. foreach ($config['extra_file_list'] as $file) {
  172. $file = strpos($file, '.') ? $file : APP_PATH . $file . EXT;
  173. if (is_file($file) && !isset(self::$file[$file])) {
  174. include $file;
  175. self::$file[$file] = true;
  176. }
  177. }
  178. }
  179. // 设置系统时区
  180. date_default_timezone_set($config['default_timezone']);
  181. // 监听 app_init
  182. Hook::listen('app_init');
  183. self::$init = true;
  184. }
  185. return Config::get();
  186. }
  187. /**
  188. * 初始化应用或模块
  189. * @access public
  190. * @param string $module 模块名
  191. * @return array
  192. */
  193. private static function init($module = '')
  194. {
  195. // 定位模块目录
  196. $module = $module ? $module . DS : '';
  197. // 加载初始化文件
  198. if (is_file(APP_PATH . $module . 'init' . EXT)) {
  199. include APP_PATH . $module . 'init' . EXT;
  200. } elseif (is_file(RUNTIME_PATH . $module . 'init' . EXT)) {
  201. include RUNTIME_PATH . $module . 'init' . EXT;
  202. } else {
  203. // 加载模块配置
  204. $config = Config::load(CONF_PATH . $module . 'config' . CONF_EXT);
  205. // 读取数据库配置文件
  206. $filename = CONF_PATH . $module . 'database' . CONF_EXT;
  207. Config::load($filename, 'database');
  208. // 读取扩展配置文件
  209. if (is_dir(CONF_PATH . $module . 'extra')) {
  210. $dir = CONF_PATH . $module . 'extra';
  211. $files = scandir($dir);
  212. foreach ($files as $file) {
  213. if ('.' . pathinfo($file, PATHINFO_EXTENSION) === CONF_EXT) {
  214. $filename = $dir . DS . $file;
  215. Config::load($filename, pathinfo($file, PATHINFO_FILENAME));
  216. }
  217. }
  218. }
  219. // 加载应用状态配置
  220. if ($config['app_status']) {
  221. Config::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT);
  222. }
  223. // 加载行为扩展文件
  224. if (is_file(CONF_PATH . $module . 'tags' . EXT)) {
  225. Hook::import(include CONF_PATH . $module . 'tags' . EXT);
  226. }
  227. // 加载公共文件
  228. $path = APP_PATH . $module;
  229. if (is_file($path . 'common' . EXT)) {
  230. include $path . 'common' . EXT;
  231. }
  232. // 加载当前模块语言包
  233. if ($module) {
  234. Lang::load($path . 'lang' . DS . Request::instance()->langset() . EXT);
  235. }
  236. }
  237. return Config::get();
  238. }
  239. /**
  240. * 设置当前请求的调度信息
  241. * @access public
  242. * @param array|string $dispatch 调度信息
  243. * @param string $type 调度类型
  244. * @return void
  245. */
  246. public static function dispatch($dispatch, $type = 'module')
  247. {
  248. self::$dispatch = ['type' => $type, $type => $dispatch];
  249. }
  250. /**
  251. * 执行函数或者闭包方法 支持参数调用
  252. * @access public
  253. * @param string|array|\Closure $function 函数或者闭包
  254. * @param array $vars 变量
  255. * @return mixed
  256. */
  257. public static function invokeFunction($function, $vars = [])
  258. {
  259. $reflect = new \ReflectionFunction($function);
  260. $args = self::bindParams($reflect, $vars);
  261. // 记录执行信息
  262. self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info');
  263. return $reflect->invokeArgs($args);
  264. }
  265. /**
  266. * 调用反射执行类的方法 支持参数绑定
  267. * @access public
  268. * @param string|array $method 方法
  269. * @param array $vars 变量
  270. * @return mixed
  271. */
  272. public static function invokeMethod($method, $vars = [])
  273. {
  274. if (is_array($method)) {
  275. $class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]);
  276. $reflect = new \ReflectionMethod($class, $method[1]);
  277. } else {
  278. // 静态方法
  279. $reflect = new \ReflectionMethod($method);
  280. }
  281. $args = self::bindParams($reflect, $vars);
  282. self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info');
  283. return $reflect->invokeArgs(isset($class) ? $class : null, $args);
  284. }
  285. /**
  286. * 调用反射执行类的实例化 支持依赖注入
  287. * @access public
  288. * @param string $class 类名
  289. * @param array $vars 变量
  290. * @return mixed
  291. */
  292. public static function invokeClass($class, $vars = [])
  293. {
  294. $reflect = new \ReflectionClass($class);
  295. $constructor = $reflect->getConstructor();
  296. $args = $constructor ? self::bindParams($constructor, $vars) : [];
  297. return $reflect->newInstanceArgs($args);
  298. }
  299. /**
  300. * 绑定参数
  301. * @access private
  302. * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类
  303. * @param array $vars 变量
  304. * @return array
  305. */
  306. private static function bindParams($reflect, $vars = [])
  307. {
  308. // 自动获取请求变量
  309. if (empty($vars)) {
  310. $vars = Config::get('url_param_type') ?
  311. Request::instance()->route() :
  312. Request::instance()->param();
  313. }
  314. $args = [];
  315. if ($reflect->getNumberOfParameters() > 0) {
  316. // 判断数组类型 数字数组时按顺序绑定参数
  317. reset($vars);
  318. $type = key($vars) === 0 ? 1 : 0;
  319. foreach ($reflect->getParameters() as $param) {
  320. $args[] = self::getParamValue($param, $vars, $type);
  321. }
  322. }
  323. return $args;
  324. }
  325. /**
  326. * 获取参数值
  327. * @access private
  328. * @param \ReflectionParameter $param 参数
  329. * @param array $vars 变量
  330. * @param string $type 类别
  331. * @return array
  332. */
  333. private static function getParamValue($param, &$vars, $type)
  334. {
  335. $name = $param->getName();
  336. $class = $param->getClass();
  337. if ($class) {
  338. $className = $class->getName();
  339. $bind = Request::instance()->$name;
  340. if ($bind instanceof $className) {
  341. $result = $bind;
  342. } else {
  343. if (method_exists($className, 'invoke')) {
  344. $method = new \ReflectionMethod($className, 'invoke');
  345. if ($method->isPublic() && $method->isStatic()) {
  346. return $className::invoke(Request::instance());
  347. }
  348. }
  349. $result = method_exists($className, 'instance') ?
  350. $className::instance() :
  351. new $className;
  352. }
  353. } elseif (1 == $type && !empty($vars)) {
  354. $result = array_shift($vars);
  355. } elseif (0 == $type && isset($vars[$name])) {
  356. $result = $vars[$name];
  357. } elseif ($param->isDefaultValueAvailable()) {
  358. $result = $param->getDefaultValue();
  359. } else {
  360. throw new \InvalidArgumentException('method param miss:' . $name);
  361. }
  362. return $result;
  363. }
  364. /**
  365. * 执行调用分发
  366. * @access protected
  367. * @param array $dispatch 调用信息
  368. * @param array $config 配置信息
  369. * @return Response|mixed
  370. * @throws \InvalidArgumentException
  371. */
  372. protected static function exec($dispatch, $config)
  373. {
  374. switch ($dispatch['type']) {
  375. case 'redirect': // 重定向跳转
  376. $data = Response::create($dispatch['url'], 'redirect')
  377. ->code($dispatch['status']);
  378. break;
  379. case 'module': // 模块/控制器/操作
  380. $data = self::module(
  381. $dispatch['module'],
  382. $config,
  383. isset($dispatch['convert']) ? $dispatch['convert'] : null
  384. );
  385. break;
  386. case 'controller': // 执行控制器操作
  387. $vars = array_merge(Request::instance()->param(), $dispatch['var']);
  388. $data = Loader::action(
  389. $dispatch['controller'],
  390. $vars,
  391. $config['url_controller_layer'],
  392. $config['controller_suffix']
  393. );
  394. break;
  395. case 'method': // 回调方法
  396. $vars = array_merge(Request::instance()->param(), $dispatch['var']);
  397. $data = self::invokeMethod($dispatch['method'], $vars);
  398. break;
  399. case 'function': // 闭包
  400. $data = self::invokeFunction($dispatch['function']);
  401. break;
  402. case 'response': // Response 实例
  403. $data = $dispatch['response'];
  404. break;
  405. default:
  406. throw new \InvalidArgumentException('dispatch type not support');
  407. }
  408. return $data;
  409. }
  410. /**
  411. * 执行模块
  412. * @access public
  413. * @param array $result 模块/控制器/操作
  414. * @param array $config 配置参数
  415. * @param bool $convert 是否自动转换控制器和操作名
  416. * @return mixed
  417. * @throws HttpException
  418. */
  419. public static function module($result, $config, $convert = null)
  420. {
  421. if (is_string($result)) {
  422. $result = explode('/', $result);
  423. }
  424. $request = Request::instance();
  425. if ($config['app_multi_module']) {
  426. // 多模块部署
  427. $module = strip_tags(strtolower($result[0] ?: $config['default_module']));
  428. $bind = Route::getBind('module');
  429. $available = false;
  430. if ($bind) {
  431. // 绑定模块
  432. list($bindModule) = explode('/', $bind);
  433. if (empty($result[0])) {
  434. $module = $bindModule;
  435. $available = true;
  436. } elseif ($module == $bindModule) {
  437. $available = true;
  438. }
  439. } elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) {
  440. $available = true;
  441. }
  442. // 模块初始化
  443. if ($module && $available) {
  444. // 初始化模块
  445. $request->module($module);
  446. $config = self::init($module);
  447. // 模块请求缓存检查
  448. $request->cache(
  449. $config['request_cache'],
  450. $config['request_cache_expire'],
  451. $config['request_cache_except']
  452. );
  453. } else {
  454. throw new HttpException(404, 'module not exists:' . $module);
  455. }
  456. } else {
  457. // 单一模块部署
  458. $module = '';
  459. $request->module($module);
  460. }
  461. // 设置默认过滤机制
  462. $request->filter($config['default_filter']);
  463. // 当前模块路径
  464. App::$modulePath = APP_PATH . ($module ? $module . DS : '');
  465. // 是否自动转换控制器和操作名
  466. $convert = is_bool($convert) ? $convert : $config['url_convert'];
  467. // 获取控制器名
  468. $controller = strip_tags($result[1] ?: $config['default_controller']);
  469. if (!preg_match('/^[A-Za-z](\w|\.)*$/', $controller)) {
  470. throw new HttpException(404, 'controller not exists:' . $controller);
  471. }
  472. $controller = $convert ? strtolower($controller) : $controller;
  473. // 获取操作名
  474. $actionName = strip_tags($result[2] ?: $config['default_action']);
  475. if (!empty($config['action_convert'])) {
  476. $actionName = Loader::parseName($actionName, 1);
  477. } else {
  478. $actionName = $convert ? strtolower($actionName) : $actionName;
  479. }
  480. // 设置当前请求的控制器、操作
  481. $request->controller(Loader::parseName($controller, 1))->action($actionName);
  482. // 监听module_init
  483. Hook::listen('module_init', $request);
  484. try {
  485. $instance = Loader::controller(
  486. $controller,
  487. $config['url_controller_layer'],
  488. $config['controller_suffix'],
  489. $config['empty_controller']
  490. );
  491. } catch (ClassNotFoundException $e) {
  492. throw new HttpException(404, 'controller not exists:' . $e->getClass());
  493. }
  494. // 获取当前操作名
  495. $action = $actionName . $config['action_suffix'];
  496. $vars = [];
  497. if (is_callable([$instance, $action])) {
  498. // 执行操作方法
  499. $call = [$instance, $action];
  500. // 严格获取当前操作方法名
  501. $reflect = new \ReflectionMethod($instance, $action);
  502. $methodName = $reflect->getName();
  503. $suffix = $config['action_suffix'];
  504. $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName;
  505. $request->action($actionName);
  506. } elseif (is_callable([$instance, '_empty'])) {
  507. // 空操作
  508. $call = [$instance, '_empty'];
  509. $vars = [$actionName];
  510. } else {
  511. // 操作不存在
  512. throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
  513. }
  514. Hook::listen('action_begin', $call);
  515. return self::invokeMethod($call, $vars);
  516. }
  517. /**
  518. * URL路由检测(根据PATH_INFO)
  519. * @access public
  520. * @param \think\Request $request 请求实例
  521. * @param array $config 配置信息
  522. * @return array
  523. * @throws \think\Exception
  524. */
  525. public static function routeCheck($request, array $config)
  526. {
  527. $path = $request->path();
  528. $depr = $config['pathinfo_depr'];
  529. $result = false;
  530. // 路由检测
  531. $check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on'];
  532. if ($check) {
  533. // 开启路由
  534. if (is_file(RUNTIME_PATH . 'route.php')) {
  535. // 读取路由缓存
  536. $rules = include RUNTIME_PATH . 'route.php';
  537. is_array($rules) && Route::rules($rules);
  538. } else {
  539. $files = $config['route_config_file'];
  540. foreach ($files as $file) {
  541. if (is_file(CONF_PATH . $file . CONF_EXT)) {
  542. // 导入路由配置
  543. $rules = include CONF_PATH . $file . CONF_EXT;
  544. is_array($rules) && Route::import($rules);
  545. }
  546. }
  547. }
  548. // 路由检测(根据路由定义返回不同的URL调度)
  549. $result = Route::check($request, $path, $depr, $config['url_domain_deploy']);
  550. $must = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must'];
  551. if ($must && false === $result) {
  552. // 路由无效
  553. throw new RouteNotFoundException();
  554. }
  555. }
  556. // 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索
  557. if (false === $result) {
  558. $result = Route::parseUrl($path, $depr, $config['controller_auto_search']);
  559. }
  560. return $result;
  561. }
  562. /**
  563. * 设置应用的路由检测机制
  564. * @access public
  565. * @param bool $route 是否需要检测路由
  566. * @param bool $must 是否强制检测路由
  567. * @return void
  568. */
  569. public static function route($route, $must = false)
  570. {
  571. self::$routeCheck = $route;
  572. self::$routeMust = $must;
  573. }
  574. }