Route.php 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2006~2017 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\HttpException;
  13. class Route
  14. {
  15. // 路由规则
  16. private static $rules = [
  17. 'get' => [],
  18. 'post' => [],
  19. 'put' => [],
  20. 'delete' => [],
  21. 'patch' => [],
  22. 'head' => [],
  23. 'options' => [],
  24. '*' => [],
  25. 'alias' => [],
  26. 'domain' => [],
  27. 'pattern' => [],
  28. 'name' => [],
  29. ];
  30. // REST路由操作方法定义
  31. private static $rest = [
  32. 'index' => ['get', '', 'index'],
  33. 'create' => ['get', '/create', 'create'],
  34. 'rules' => ['post','/rules','rules'],
  35. 'edit' => ['get', '/:id/edit', 'edit'],
  36. 'read' => ['get', '/:id', 'read'],
  37. 'save' => ['post', '', 'save'],
  38. 'update' => ['put', '/:id', 'update'],
  39. 'delete' => ['delete', '/:id', 'delete']
  40. ];
  41. // 不同请求类型的方法前缀
  42. private static $methodPrefix = [
  43. 'get' => 'get',
  44. 'post' => 'post',
  45. 'put' => 'put',
  46. 'delete' => 'delete',
  47. 'patch' => 'patch',
  48. ];
  49. // 子域名
  50. private static $subDomain = '';
  51. // 域名绑定
  52. private static $bind = [];
  53. // 当前分组信息
  54. private static $group = [];
  55. // 当前子域名绑定
  56. private static $domainBind;
  57. private static $domainRule;
  58. // 当前域名
  59. private static $domain;
  60. // 当前路由执行过程中的参数
  61. private static $option = [];
  62. /**
  63. * 注册变量规则
  64. * @access public
  65. * @param string|array $name 变量名
  66. * @param string $rule 变量规则
  67. * @return void
  68. */
  69. public static function pattern($name = null, $rule = '')
  70. {
  71. if (is_array($name)) {
  72. self::$rules['pattern'] = array_merge(self::$rules['pattern'], $name);
  73. } else {
  74. self::$rules['pattern'][$name] = $rule;
  75. }
  76. }
  77. /**
  78. * 注册子域名部署规则
  79. * @access public
  80. * @param string|array $domain 子域名
  81. * @param mixed $rule 路由规则
  82. * @param array $option 路由参数
  83. * @param array $pattern 变量规则
  84. * @return void
  85. */
  86. public static function domain($domain, $rule = '', $option = [], $pattern = [])
  87. {
  88. if (is_array($domain)) {
  89. foreach ($domain as $key => $item) {
  90. self::domain($key, $item, $option, $pattern);
  91. }
  92. } elseif ($rule instanceof \Closure) {
  93. // 执行闭包
  94. self::setDomain($domain);
  95. call_user_func_array($rule, []);
  96. self::setDomain(null);
  97. } elseif (is_array($rule)) {
  98. self::setDomain($domain);
  99. self::group('', function () use ($rule) {
  100. // 动态注册域名的路由规则
  101. self::registerRules($rule);
  102. }, $option, $pattern);
  103. self::setDomain(null);
  104. } else {
  105. self::$rules['domain'][$domain]['[bind]'] = [$rule, $option, $pattern];
  106. }
  107. }
  108. private static function setDomain($domain)
  109. {
  110. self::$domain = $domain;
  111. }
  112. /**
  113. * 设置路由绑定
  114. * @access public
  115. * @param mixed $bind 绑定信息
  116. * @param string $type 绑定类型 默认为module 支持 namespace class controller
  117. * @return mixed
  118. */
  119. public static function bind($bind, $type = 'module')
  120. {
  121. self::$bind = ['type' => $type, $type => $bind];
  122. }
  123. /**
  124. * 设置或者获取路由标识
  125. * @access public
  126. * @param string|array $name 路由命名标识 数组表示批量设置
  127. * @param array $value 路由地址及变量信息
  128. * @return array
  129. */
  130. public static function name($name = '', $value = null)
  131. {
  132. if (is_array($name)) {
  133. return self::$rules['name'] = $name;
  134. } elseif ('' === $name) {
  135. return self::$rules['name'];
  136. } elseif (!is_null($value)) {
  137. self::$rules['name'][strtolower($name)][] = $value;
  138. } else {
  139. $name = strtolower($name);
  140. return isset(self::$rules['name'][$name]) ? self::$rules['name'][$name] : null;
  141. }
  142. }
  143. /**
  144. * 读取路由绑定
  145. * @access public
  146. * @param string $type 绑定类型
  147. * @return mixed
  148. */
  149. public static function getBind($type)
  150. {
  151. return isset(self::$bind[$type]) ? self::$bind[$type] : null;
  152. }
  153. /**
  154. * 导入配置文件的路由规则
  155. * @access public
  156. * @param array $rule 路由规则
  157. * @param string $type 请求类型
  158. * @return void
  159. */
  160. public static function import(array $rule, $type = '*')
  161. {
  162. // 检查域名部署
  163. if (isset($rule['__domain__'])) {
  164. self::domain($rule['__domain__']);
  165. unset($rule['__domain__']);
  166. }
  167. // 检查变量规则
  168. if (isset($rule['__pattern__'])) {
  169. self::pattern($rule['__pattern__']);
  170. unset($rule['__pattern__']);
  171. }
  172. // 检查路由别名
  173. if (isset($rule['__alias__'])) {
  174. self::alias($rule['__alias__']);
  175. unset($rule['__alias__']);
  176. }
  177. // 检查资源路由
  178. if (isset($rule['__rest__'])) {
  179. self::resource($rule['__rest__']);
  180. unset($rule['__rest__']);
  181. }
  182. self::registerRules($rule, strtolower($type));
  183. }
  184. // 批量注册路由
  185. protected static function registerRules($rules, $type = '*')
  186. {
  187. foreach ($rules as $key => $val) {
  188. if (is_numeric($key)) {
  189. $key = array_shift($val);
  190. }
  191. if (empty($val)) {
  192. continue;
  193. }
  194. if (is_string($key) && 0 === strpos($key, '[')) {
  195. $key = substr($key, 1, -1);
  196. self::group($key, $val);
  197. } elseif (is_array($val)) {
  198. self::setRule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []);
  199. } else {
  200. self::setRule($key, $val, $type);
  201. }
  202. }
  203. }
  204. /**
  205. * 注册路由规则
  206. * @access public
  207. * @param string $rule 路由规则
  208. * @param string $route 路由地址
  209. * @param string $type 请求类型
  210. * @param array $option 路由参数
  211. * @param array $pattern 变量规则
  212. * @return void
  213. */
  214. public static function rule($rule, $route = '', $type = '*', $option = [], $pattern = [])
  215. {
  216. $group = self::getGroup('name');
  217. if (!is_null($group)) {
  218. // 路由分组
  219. $option = array_merge(self::getGroup('option'), $option);
  220. $pattern = array_merge(self::getGroup('pattern'), $pattern);
  221. }
  222. $type = strtolower($type);
  223. if (strpos($type, '|')) {
  224. $option['method'] = $type;
  225. $type = '*';
  226. }
  227. if (is_array($rule) && empty($route)) {
  228. foreach ($rule as $key => $val) {
  229. if (is_numeric($key)) {
  230. $key = array_shift($val);
  231. }
  232. if (is_array($val)) {
  233. $route = $val[0];
  234. $option1 = array_merge($option, $val[1]);
  235. $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []);
  236. } else {
  237. $option1 = null;
  238. $pattern1 = null;
  239. $route = $val;
  240. }
  241. self::setRule($key, $route, $type, !is_null($option1) ? $option1 : $option, !is_null($pattern1) ? $pattern1 : $pattern, $group);
  242. }
  243. } else {
  244. self::setRule($rule, $route, $type, $option, $pattern, $group);
  245. }
  246. }
  247. /**
  248. * 设置路由规则
  249. * @access public
  250. * @param string $rule 路由规则
  251. * @param string $route 路由地址
  252. * @param string $type 请求类型
  253. * @param array $option 路由参数
  254. * @param array $pattern 变量规则
  255. * @param string $group 所属分组
  256. * @return void
  257. */
  258. protected static function setRule($rule, $route, $type = '*', $option = [], $pattern = [], $group = '')
  259. {
  260. if (is_array($rule)) {
  261. $name = $rule[0];
  262. $rule = $rule[1];
  263. } elseif (is_string($route)) {
  264. $name = $route;
  265. }
  266. if (!isset($option['complete_match'])) {
  267. if (Config::get('route_complete_match')) {
  268. $option['complete_match'] = true;
  269. } elseif ('$' == substr($rule, -1, 1)) {
  270. // 是否完整匹配
  271. $option['complete_match'] = true;
  272. }
  273. } elseif (empty($option['complete_match']) && '$' == substr($rule, -1, 1)) {
  274. // 是否完整匹配
  275. $option['complete_match'] = true;
  276. }
  277. if ('$' == substr($rule, -1, 1)) {
  278. $rule = substr($rule, 0, -1);
  279. }
  280. if ('/' != $rule || $group) {
  281. $rule = trim($rule, '/');
  282. }
  283. $vars = self::parseVar($rule);
  284. if (isset($name)) {
  285. $key = $group ? $group . ($rule ? '/' . $rule : '') : $rule;
  286. $suffix = isset($option['ext']) ? $option['ext'] : null;
  287. self::name($name, [$key, $vars, self::$domain, $suffix]);
  288. }
  289. if (isset($option['modular'])) {
  290. $route = $option['modular'] . '/' . $route;
  291. }
  292. if ($group) {
  293. if ('*' != $type) {
  294. $option['method'] = $type;
  295. }
  296. if (self::$domain) {
  297. self::$rules['domain'][self::$domain]['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
  298. } else {
  299. self::$rules['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
  300. }
  301. } else {
  302. if ('*' != $type && isset(self::$rules['*'][$rule])) {
  303. unset(self::$rules['*'][$rule]);
  304. }
  305. if (self::$domain) {
  306. self::$rules['domain'][self::$domain][$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
  307. } else {
  308. self::$rules[$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
  309. }
  310. if ('*' == $type) {
  311. // 注册路由快捷方式
  312. foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) {
  313. if (self::$domain) {
  314. self::$rules['domain'][self::$domain][$method][$rule] = true;
  315. } else {
  316. self::$rules[$method][$rule] = true;
  317. }
  318. }
  319. }
  320. }
  321. }
  322. /**
  323. * 设置当前执行的参数信息
  324. * @access public
  325. * @param array $options 参数信息
  326. * @return mixed
  327. */
  328. protected static function setOption($options = [])
  329. {
  330. self::$option[] = $options;
  331. }
  332. /**
  333. * 获取当前执行的所有参数信息
  334. * @access public
  335. * @return array
  336. */
  337. public static function getOption()
  338. {
  339. return self::$option;
  340. }
  341. /**
  342. * 获取当前的分组信息
  343. * @access public
  344. * @param string $type 分组信息名称 name option pattern
  345. * @return mixed
  346. */
  347. public static function getGroup($type)
  348. {
  349. if (isset(self::$group[$type])) {
  350. return self::$group[$type];
  351. } else {
  352. return 'name' == $type ? null : [];
  353. }
  354. }
  355. /**
  356. * 设置当前的路由分组
  357. * @access public
  358. * @param string $name 分组名称
  359. * @param array $option 分组路由参数
  360. * @param array $pattern 分组变量规则
  361. * @return void
  362. */
  363. public static function setGroup($name, $option = [], $pattern = [])
  364. {
  365. self::$group['name'] = $name;
  366. self::$group['option'] = $option ?: [];
  367. self::$group['pattern'] = $pattern ?: [];
  368. }
  369. /**
  370. * 注册路由分组
  371. * @access public
  372. * @param string|array $name 分组名称或者参数
  373. * @param array|\Closure $routes 路由地址
  374. * @param array $option 路由参数
  375. * @param array $pattern 变量规则
  376. * @return void
  377. */
  378. public static function group($name, $routes, $option = [], $pattern = [])
  379. {
  380. if (is_array($name)) {
  381. $option = $name;
  382. $name = isset($option['name']) ? $option['name'] : '';
  383. }
  384. // 分组
  385. $currentGroup = self::getGroup('name');
  386. if ($currentGroup) {
  387. $name = $currentGroup . ($name ? '/' . ltrim($name, '/') : '');
  388. }
  389. if (!empty($name)) {
  390. if ($routes instanceof \Closure) {
  391. $currentOption = self::getGroup('option');
  392. $currentPattern = self::getGroup('pattern');
  393. self::setGroup($name, array_merge($currentOption, $option), array_merge($currentPattern, $pattern));
  394. call_user_func_array($routes, []);
  395. self::setGroup($currentGroup, $currentOption, $currentPattern);
  396. if ($currentGroup != $name) {
  397. self::$rules['*'][$name]['route'] = '';
  398. self::$rules['*'][$name]['var'] = self::parseVar($name);
  399. self::$rules['*'][$name]['option'] = $option;
  400. self::$rules['*'][$name]['pattern'] = $pattern;
  401. }
  402. } else {
  403. $item = [];
  404. $completeMatch = Config::get('route_complete_match');
  405. foreach ($routes as $key => $val) {
  406. if (is_numeric($key)) {
  407. $key = array_shift($val);
  408. }
  409. if (is_array($val)) {
  410. $route = $val[0];
  411. $option1 = array_merge($option, isset($val[1]) ? $val[1] : []);
  412. $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []);
  413. } else {
  414. $route = $val;
  415. }
  416. $options = isset($option1) ? $option1 : $option;
  417. $patterns = isset($pattern1) ? $pattern1 : $pattern;
  418. if ('$' == substr($key, -1, 1)) {
  419. // 是否完整匹配
  420. $options['complete_match'] = true;
  421. $key = substr($key, 0, -1);
  422. } elseif ($completeMatch) {
  423. $options['complete_match'] = true;
  424. }
  425. $key = trim($key, '/');
  426. $vars = self::parseVar($key);
  427. $item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns];
  428. // 设置路由标识
  429. $suffix = isset($options['ext']) ? $options['ext'] : null;
  430. self::name($route, [$name . ($key ? '/' . $key : ''), $vars, self::$domain, $suffix]);
  431. }
  432. self::$rules['*'][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern];
  433. }
  434. foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) {
  435. if (!isset(self::$rules[$method][$name])) {
  436. self::$rules[$method][$name] = true;
  437. } elseif (is_array(self::$rules[$method][$name])) {
  438. self::$rules[$method][$name] = array_merge(self::$rules['*'][$name], self::$rules[$method][$name]);
  439. }
  440. }
  441. } elseif ($routes instanceof \Closure) {
  442. // 闭包注册
  443. $currentOption = self::getGroup('option');
  444. $currentPattern = self::getGroup('pattern');
  445. self::setGroup('', array_merge($currentOption, $option), array_merge($currentPattern, $pattern));
  446. call_user_func_array($routes, []);
  447. self::setGroup($currentGroup, $currentOption, $currentPattern);
  448. } else {
  449. // 批量注册路由
  450. self::rule($routes, '', '*', $option, $pattern);
  451. }
  452. }
  453. /**
  454. * 注册路由
  455. * @access public
  456. * @param string $rule 路由规则
  457. * @param string $route 路由地址
  458. * @param array $option 路由参数
  459. * @param array $pattern 变量规则
  460. * @return void
  461. */
  462. public static function any($rule, $route = '', $option = [], $pattern = [])
  463. {
  464. self::rule($rule, $route, '*', $option, $pattern);
  465. }
  466. /**
  467. * 注册GET路由
  468. * @access public
  469. * @param string $rule 路由规则
  470. * @param string $route 路由地址
  471. * @param array $option 路由参数
  472. * @param array $pattern 变量规则
  473. * @return void
  474. */
  475. public static function get($rule, $route = '', $option = [], $pattern = [])
  476. {
  477. self::rule($rule, $route, 'GET', $option, $pattern);
  478. }
  479. /**
  480. * 注册POST路由
  481. * @access public
  482. * @param string $rule 路由规则
  483. * @param string $route 路由地址
  484. * @param array $option 路由参数
  485. * @param array $pattern 变量规则
  486. * @return void
  487. */
  488. public static function post($rule, $route = '', $option = [], $pattern = [])
  489. {
  490. self::rule($rule, $route, 'POST', $option, $pattern);
  491. }
  492. /**
  493. * 注册PUT路由
  494. * @access public
  495. * @param string $rule 路由规则
  496. * @param string $route 路由地址
  497. * @param array $option 路由参数
  498. * @param array $pattern 变量规则
  499. * @return void
  500. */
  501. public static function put($rule, $route = '', $option = [], $pattern = [])
  502. {
  503. self::rule($rule, $route, 'PUT', $option, $pattern);
  504. }
  505. /**
  506. * 注册DELETE路由
  507. * @access public
  508. * @param string $rule 路由规则
  509. * @param string $route 路由地址
  510. * @param array $option 路由参数
  511. * @param array $pattern 变量规则
  512. * @return void
  513. */
  514. public static function delete($rule, $route = '', $option = [], $pattern = [])
  515. {
  516. self::rule($rule, $route, 'DELETE', $option, $pattern);
  517. }
  518. /**
  519. * 注册PATCH路由
  520. * @access public
  521. * @param string $rule 路由规则
  522. * @param string $route 路由地址
  523. * @param array $option 路由参数
  524. * @param array $pattern 变量规则
  525. * @return void
  526. */
  527. public static function patch($rule, $route = '', $option = [], $pattern = [])
  528. {
  529. self::rule($rule, $route, 'PATCH', $option, $pattern);
  530. }
  531. /**
  532. * 注册资源路由
  533. * @access public
  534. * @param string $rule 路由规则
  535. * @param string $route 路由地址
  536. * @param array $option 路由参数
  537. * @param array $pattern 变量规则
  538. * @return void
  539. */
  540. public static function resource($rule, $route = '', $option = [], $pattern = [])
  541. {
  542. if (is_array($rule)) {
  543. foreach ($rule as $key => $val) {
  544. if (is_array($val)) {
  545. list($val, $option, $pattern) = array_pad($val, 3, []);
  546. }
  547. self::resource($key, $val, $option, $pattern);
  548. }
  549. } else {
  550. if (strpos($rule, '.')) {
  551. // 注册嵌套资源路由
  552. $array = explode('.', $rule);
  553. $last = array_pop($array);
  554. $item = [];
  555. foreach ($array as $val) {
  556. $item[] = $val . '/:' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id');
  557. }
  558. $rule = implode('/', $item) . '/' . $last;
  559. }
  560. // 注册资源路由
  561. foreach (self::$rest as $key => $val) {
  562. if ((isset($option['only']) && !in_array($key, $option['only']))
  563. || (isset($option['except']) && in_array($key, $option['except']))) {
  564. continue;
  565. }
  566. if (isset($last) && strpos($val[1], ':id') && isset($option['var'][$last])) {
  567. $val[1] = str_replace(':id', ':' . $option['var'][$last], $val[1]);
  568. } elseif (strpos($val[1], ':id') && isset($option['var'][$rule])) {
  569. $val[1] = str_replace(':id', ':' . $option['var'][$rule], $val[1]);
  570. }
  571. $item = ltrim($rule . $val[1], '/');
  572. $option['rest'] = $key;
  573. self::rule($item . '$', $route . '/' . $val[2], $val[0], $option, $pattern);
  574. }
  575. }
  576. }
  577. /**
  578. * 注册控制器路由 操作方法对应不同的请求后缀
  579. * @access public
  580. * @param string $rule 路由规则
  581. * @param string $route 路由地址
  582. * @param array $option 路由参数
  583. * @param array $pattern 变量规则
  584. * @return void
  585. */
  586. public static function controller($rule, $route = '', $option = [], $pattern = [])
  587. {
  588. foreach (self::$methodPrefix as $type => $val) {
  589. self::$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern);
  590. }
  591. }
  592. /**
  593. * 注册别名路由
  594. * @access public
  595. * @param string|array $rule 路由别名
  596. * @param string $route 路由地址
  597. * @param array $option 路由参数
  598. * @return void
  599. */
  600. public static function alias($rule = null, $route = '', $option = [])
  601. {
  602. if (is_array($rule)) {
  603. self::$rules['alias'] = array_merge(self::$rules['alias'], $rule);
  604. } else {
  605. self::$rules['alias'][$rule] = $option ? [$route, $option] : $route;
  606. }
  607. }
  608. /**
  609. * 设置不同请求类型下面的方法前缀
  610. * @access public
  611. * @param string $method 请求类型
  612. * @param string $prefix 类型前缀
  613. * @return void
  614. */
  615. public static function setMethodPrefix($method, $prefix = '')
  616. {
  617. if (is_array($method)) {
  618. self::$methodPrefix = array_merge(self::$methodPrefix, array_change_key_case($method));
  619. } else {
  620. self::$methodPrefix[strtolower($method)] = $prefix;
  621. }
  622. }
  623. /**
  624. * rest方法定义和修改
  625. * @access public
  626. * @param string $name 方法名称
  627. * @param array|bool $resource 资源
  628. * @return void
  629. */
  630. public static function rest($name, $resource = [])
  631. {
  632. if (is_array($name)) {
  633. self::$rest = $resource ? $name : array_merge(self::$rest, $name);
  634. } else {
  635. self::$rest[$name] = $resource;
  636. }
  637. }
  638. /**
  639. * 注册未匹配路由规则后的处理
  640. * @access public
  641. * @param string $route 路由地址
  642. * @param string $method 请求类型
  643. * @param array $option 路由参数
  644. * @return void
  645. */
  646. public static function miss($route, $method = '*', $option = [])
  647. {
  648. self::rule('__miss__', $route, $method, $option, []);
  649. }
  650. /**
  651. * 注册一个自动解析的URL路由
  652. * @access public
  653. * @param string $route 路由地址
  654. * @return void
  655. */
  656. public static function auto($route)
  657. {
  658. self::rule('__auto__', $route, '*', [], []);
  659. }
  660. /**
  661. * 获取或者批量设置路由定义
  662. * @access public
  663. * @param mixed $rules 请求类型或者路由定义数组
  664. * @return array
  665. */
  666. public static function rules($rules = '')
  667. {
  668. if (is_array($rules)) {
  669. self::$rules = $rules;
  670. } elseif ($rules) {
  671. return true === $rules ? self::$rules : self::$rules[strtolower($rules)];
  672. } else {
  673. $rules = self::$rules;
  674. unset($rules['pattern'], $rules['alias'], $rules['domain'], $rules['name']);
  675. return $rules;
  676. }
  677. }
  678. /**
  679. * 检测子域名部署
  680. * @access public
  681. * @param Request $request Request请求对象
  682. * @param array $currentRules 当前路由规则
  683. * @param string $method 请求类型
  684. * @return void
  685. */
  686. public static function checkDomain($request, &$currentRules, $method = 'get')
  687. {
  688. // 域名规则
  689. $rules = self::$rules['domain'];
  690. // 开启子域名部署 支持二级和三级域名
  691. if (!empty($rules)) {
  692. $host = $request->host();
  693. if (isset($rules[$host])) {
  694. // 完整域名或者IP配置
  695. $item = $rules[$host];
  696. } else {
  697. $rootDomain = Config::get('url_domain_root');
  698. if ($rootDomain) {
  699. // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置
  700. $domain = explode('.', rtrim(stristr($host, $rootDomain, true), '.'));
  701. } else {
  702. $domain = explode('.', $host, -2);
  703. }
  704. // 子域名配置
  705. if (!empty($domain)) {
  706. // 当前子域名
  707. $subDomain = implode('.', $domain);
  708. self::$subDomain = $subDomain;
  709. $domain2 = array_pop($domain);
  710. if ($domain) {
  711. // 存在三级域名
  712. $domain3 = array_pop($domain);
  713. }
  714. if ($subDomain && isset($rules[$subDomain])) {
  715. // 子域名配置
  716. $item = $rules[$subDomain];
  717. } elseif (isset($rules['*.' . $domain2]) && !empty($domain3)) {
  718. // 泛三级域名
  719. $item = $rules['*.' . $domain2];
  720. $panDomain = $domain3;
  721. } elseif (isset($rules['*']) && !empty($domain2)) {
  722. // 泛二级域名
  723. if ('www' != $domain2) {
  724. $item = $rules['*'];
  725. $panDomain = $domain2;
  726. }
  727. }
  728. }
  729. }
  730. if (!empty($item)) {
  731. if (isset($panDomain)) {
  732. // 保存当前泛域名
  733. $request->route(['__domain__' => $panDomain]);
  734. }
  735. if (isset($item['[bind]'])) {
  736. // 解析子域名部署规则
  737. list($rule, $option, $pattern) = $item['[bind]'];
  738. if (!empty($option['https']) && !$request->isSsl()) {
  739. // https检测
  740. throw new HttpException(404, 'must use https request:' . $host);
  741. }
  742. if (strpos($rule, '?')) {
  743. // 传入其它参数
  744. $array = parse_url($rule);
  745. $result = $array['path'];
  746. parse_str($array['query'], $params);
  747. if (isset($panDomain)) {
  748. $pos = array_search('*', $params);
  749. if (false !== $pos) {
  750. // 泛域名作为参数
  751. $params[$pos] = $panDomain;
  752. }
  753. }
  754. $_GET = array_merge($_GET, $params);
  755. } else {
  756. $result = $rule;
  757. }
  758. if (0 === strpos($result, '\\')) {
  759. // 绑定到命名空间 例如 \app\index\behavior
  760. self::$bind = ['type' => 'namespace', 'namespace' => $result];
  761. } elseif (0 === strpos($result, '@')) {
  762. // 绑定到类 例如 @app\index\controller\User
  763. self::$bind = ['type' => 'class', 'class' => substr($result, 1)];
  764. } else {
  765. // 绑定到模块/控制器 例如 index/user
  766. self::$bind = ['type' => 'module', 'module' => $result];
  767. }
  768. self::$domainBind = true;
  769. } else {
  770. self::$domainRule = $item;
  771. $currentRules = isset($item[$method]) ? $item[$method] : $item['*'];
  772. }
  773. }
  774. }
  775. }
  776. /**
  777. * 检测URL路由
  778. * @access public
  779. * @param Request $request Request请求对象
  780. * @param string $url URL地址
  781. * @param string $depr URL分隔符
  782. * @param bool $checkDomain 是否检测域名规则
  783. * @return false|array
  784. */
  785. public static function check($request, $url, $depr = '/', $checkDomain = false)
  786. {
  787. // 分隔符替换 确保路由定义使用统一的分隔符
  788. $url = str_replace($depr, '|', $url);
  789. if (isset(self::$rules['alias'][$url]) || isset(self::$rules['alias'][strstr($url, '|', true)])) {
  790. // 检测路由别名
  791. $result = self::checkRouteAlias($request, $url, $depr);
  792. if (false !== $result) {
  793. return $result;
  794. }
  795. }
  796. $method = strtolower($request->method());
  797. // 获取当前请求类型的路由规则
  798. $rules = isset(self::$rules[$method]) ? self::$rules[$method] : [];
  799. // 检测域名部署
  800. if ($checkDomain) {
  801. self::checkDomain($request, $rules, $method);
  802. }
  803. // 检测URL绑定
  804. $return = self::checkUrlBind($url, $rules, $depr);
  805. if (false !== $return) {
  806. return $return;
  807. }
  808. if ('|' != $url) {
  809. $url = rtrim($url, '|');
  810. }
  811. $item = str_replace('|', '/', $url);
  812. if (isset($rules[$item])) {
  813. // 静态路由规则检测
  814. $rule = $rules[$item];
  815. if (true === $rule) {
  816. $rule = self::getRouteExpress($item);
  817. }
  818. if (!empty($rule['route']) && self::checkOption($rule['option'], $request)) {
  819. self::setOption($rule['option']);
  820. return self::parseRule($item, $rule['route'], $url, $rule['option']);
  821. }
  822. }
  823. // 路由规则检测
  824. if (!empty($rules)) {
  825. return self::checkRoute($request, $rules, $url, $depr);
  826. }
  827. return false;
  828. }
  829. private static function getRouteExpress($key)
  830. {
  831. return self::$domainRule ? self::$domainRule['*'][$key] : self::$rules['*'][$key];
  832. }
  833. /**
  834. * 检测路由规则
  835. * @access private
  836. * @param Request $request
  837. * @param array $rules 路由规则
  838. * @param string $url URL地址
  839. * @param string $depr URL分割符
  840. * @param string $group 路由分组名
  841. * @param array $options 路由参数(分组)
  842. * @return mixed
  843. */
  844. private static function checkRoute($request, $rules, $url, $depr = '/', $group = '', $options = [])
  845. {
  846. foreach ($rules as $key => $item) {
  847. if (true === $item) {
  848. $item = self::getRouteExpress($key);
  849. }
  850. if (!isset($item['rule'])) {
  851. continue;
  852. }
  853. $rule = $item['rule'];
  854. $route = $item['route'];
  855. $vars = $item['var'];
  856. $option = $item['option'];
  857. $pattern = $item['pattern'];
  858. // 检查参数有效性
  859. if (!self::checkOption($option, $request)) {
  860. continue;
  861. }
  862. if (isset($option['ext'])) {
  863. // 路由ext参数 优先于系统配置的URL伪静态后缀参数
  864. $url = preg_replace('/\.' . $request->ext() . '$/i', '', $url);
  865. }
  866. if (is_array($rule)) {
  867. // 分组路由
  868. $pos = strpos(str_replace('<', ':', $key), ':');
  869. if (false !== $pos) {
  870. $str = substr($key, 0, $pos);
  871. } else {
  872. $str = $key;
  873. }
  874. if (is_string($str) && $str && 0 !== stripos(str_replace('|', '/', $url), $str)) {
  875. continue;
  876. }
  877. self::setOption($option);
  878. $result = self::checkRoute($request, $rule, $url, $depr, $key, $option);
  879. if (false !== $result) {
  880. return $result;
  881. }
  882. } elseif ($route) {
  883. if ('__miss__' == $rule || '__auto__' == $rule) {
  884. // 指定特殊路由
  885. $var = trim($rule, '__');
  886. ${$var} = $item;
  887. continue;
  888. }
  889. if ($group) {
  890. $rule = $group . ($rule ? '/' . ltrim($rule, '/') : '');
  891. }
  892. self::setOption($option);
  893. if (isset($options['bind_model']) && isset($option['bind_model'])) {
  894. $option['bind_model'] = array_merge($options['bind_model'], $option['bind_model']);
  895. }
  896. $result = self::checkRule($rule, $route, $url, $pattern, $option, $depr);
  897. if (false !== $result) {
  898. return $result;
  899. }
  900. }
  901. }
  902. if (isset($auto)) {
  903. // 自动解析URL地址
  904. return self::parseUrl($auto['route'] . '/' . $url, $depr);
  905. } elseif (isset($miss)) {
  906. // 未匹配所有路由的路由规则处理
  907. return self::parseRule('', $miss['route'], $url, $miss['option']);
  908. }
  909. return false;
  910. }
  911. /**
  912. * 检测路由别名
  913. * @access private
  914. * @param Request $request
  915. * @param string $url URL地址
  916. * @param string $depr URL分隔符
  917. * @return mixed
  918. */
  919. private static function checkRouteAlias($request, $url, $depr)
  920. {
  921. $array = explode('|', $url);
  922. $alias = array_shift($array);
  923. $item = self::$rules['alias'][$alias];
  924. if (is_array($item)) {
  925. list($rule, $option) = $item;
  926. $action = $array[0];
  927. if (isset($option['allow']) && !in_array($action, explode(',', $option['allow']))) {
  928. // 允许操作
  929. return false;
  930. } elseif (isset($option['except']) && in_array($action, explode(',', $option['except']))) {
  931. // 排除操作
  932. return false;
  933. }
  934. if (isset($option['method'][$action])) {
  935. $option['method'] = $option['method'][$action];
  936. }
  937. } else {
  938. $rule = $item;
  939. }
  940. $bind = implode('|', $array);
  941. // 参数有效性检查
  942. if (isset($option) && !self::checkOption($option, $request)) {
  943. // 路由不匹配
  944. return false;
  945. } elseif (0 === strpos($rule, '\\')) {
  946. // 路由到类
  947. return self::bindToClass($bind, substr($rule, 1), $depr);
  948. } elseif (0 === strpos($rule, '@')) {
  949. // 路由到控制器类
  950. return self::bindToController($bind, substr($rule, 1), $depr);
  951. } else {
  952. // 路由到模块/控制器
  953. return self::bindToModule($bind, $rule, $depr);
  954. }
  955. }
  956. /**
  957. * 检测URL绑定
  958. * @access private
  959. * @param string $url URL地址
  960. * @param array $rules 路由规则
  961. * @param string $depr URL分隔符
  962. * @return mixed
  963. */
  964. private static function checkUrlBind(&$url, &$rules, $depr = '/')
  965. {
  966. if (!empty(self::$bind)) {
  967. $type = self::$bind['type'];
  968. $bind = self::$bind[$type];
  969. // 记录绑定信息
  970. App::$debug && Log::record('[ BIND ] ' . var_export($bind, true), 'info');
  971. // 如果有URL绑定 则进行绑定检测
  972. switch ($type) {
  973. case 'class':
  974. // 绑定到类
  975. return self::bindToClass($url, $bind, $depr);
  976. case 'controller':
  977. // 绑定到控制器类
  978. return self::bindToController($url, $bind, $depr);
  979. case 'namespace':
  980. // 绑定到命名空间
  981. return self::bindToNamespace($url, $bind, $depr);
  982. }
  983. }
  984. return false;
  985. }
  986. /**
  987. * 绑定到类
  988. * @access public
  989. * @param string $url URL地址
  990. * @param string $class 类名(带命名空间)
  991. * @param string $depr URL分隔符
  992. * @return array
  993. */
  994. public static function bindToClass($url, $class, $depr = '/')
  995. {
  996. $url = str_replace($depr, '|', $url);
  997. $array = explode('|', $url, 2);
  998. $action = !empty($array[0]) ? $array[0] : Config::get('default_action');
  999. if (!empty($array[1])) {
  1000. self::parseUrlParams($array[1]);
  1001. }
  1002. return ['type' => 'method', 'method' => [$class, $action], 'var' => []];
  1003. }
  1004. /**
  1005. * 绑定到命名空间
  1006. * @access public
  1007. * @param string $url URL地址
  1008. * @param string $namespace 命名空间
  1009. * @param string $depr URL分隔符
  1010. * @return array
  1011. */
  1012. public static function bindToNamespace($url, $namespace, $depr = '/')
  1013. {
  1014. $url = str_replace($depr, '|', $url);
  1015. $array = explode('|', $url, 3);
  1016. $class = !empty($array[0]) ? $array[0] : Config::get('default_controller');
  1017. $method = !empty($array[1]) ? $array[1] : Config::get('default_action');
  1018. if (!empty($array[2])) {
  1019. self::parseUrlParams($array[2]);
  1020. }
  1021. return ['type' => 'method', 'method' => [$namespace . '\\' . Loader::parseName($class, 1), $method], 'var' => []];
  1022. }
  1023. /**
  1024. * 绑定到控制器类
  1025. * @access public
  1026. * @param string $url URL地址
  1027. * @param string $controller 控制器名 (支持带模块名 index/user )
  1028. * @param string $depr URL分隔符
  1029. * @return array
  1030. */
  1031. public static function bindToController($url, $controller, $depr = '/')
  1032. {
  1033. $url = str_replace($depr, '|', $url);
  1034. $array = explode('|', $url, 2);
  1035. $action = !empty($array[0]) ? $array[0] : Config::get('default_action');
  1036. if (!empty($array[1])) {
  1037. self::parseUrlParams($array[1]);
  1038. }
  1039. return ['type' => 'controller', 'controller' => $controller . '/' . $action, 'var' => []];
  1040. }
  1041. /**
  1042. * 绑定到模块/控制器
  1043. * @access public
  1044. * @param string $url URL地址
  1045. * @param string $controller 控制器类名(带命名空间)
  1046. * @param string $depr URL分隔符
  1047. * @return array
  1048. */
  1049. public static function bindToModule($url, $controller, $depr = '/')
  1050. {
  1051. $url = str_replace($depr, '|', $url);
  1052. $array = explode('|', $url, 2);
  1053. $action = !empty($array[0]) ? $array[0] : Config::get('default_action');
  1054. if (!empty($array[1])) {
  1055. self::parseUrlParams($array[1]);
  1056. }
  1057. return ['type' => 'module', 'module' => $controller . '/' . $action];
  1058. }
  1059. /**
  1060. * 路由参数有效性检查
  1061. * @access private
  1062. * @param array $option 路由参数
  1063. * @param Request $request Request对象
  1064. * @return bool
  1065. */
  1066. private static function checkOption($option, $request)
  1067. {
  1068. if ((isset($option['method']) && is_string($option['method']) && false === stripos($option['method'], $request->method()))
  1069. || (isset($option['ajax']) && $option['ajax'] && !$request->isAjax()) // Ajax检测
  1070. || (isset($option['ajax']) && !$option['ajax'] && $request->isAjax()) // 非Ajax检测
  1071. || (isset($option['pjax']) && $option['pjax'] && !$request->isPjax()) // Pjax检测
  1072. || (isset($option['pjax']) && !$option['pjax'] && $request->isPjax()) // 非Pjax检测
  1073. || (isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) // 伪静态后缀检测
  1074. || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|'))
  1075. || (isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], self::$subDomain])) // 域名检测
  1076. || (isset($option['https']) && $option['https'] && !$request->isSsl()) // https检测
  1077. || (isset($option['https']) && !$option['https'] && $request->isSsl()) // https检测
  1078. || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'])) // 行为检测
  1079. || (!empty($option['callback']) && is_callable($option['callback']) && false === call_user_func($option['callback'])) // 自定义检测
  1080. ) {
  1081. return false;
  1082. }
  1083. return true;
  1084. }
  1085. /**
  1086. * 检测路由规则
  1087. * @access private
  1088. * @param string $rule 路由规则
  1089. * @param string $route 路由地址
  1090. * @param string $url URL地址
  1091. * @param array $pattern 变量规则
  1092. * @param array $option 路由参数
  1093. * @param string $depr URL分隔符(全局)
  1094. * @return array|false
  1095. */
  1096. private static function checkRule($rule, $route, $url, $pattern, $option, $depr)
  1097. {
  1098. // 检查完整规则定义
  1099. if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) {
  1100. return false;
  1101. }
  1102. // 检查路由的参数分隔符
  1103. if (isset($option['param_depr'])) {
  1104. $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url);
  1105. }
  1106. $len1 = substr_count($url, '|');
  1107. $len2 = substr_count($rule, '/');
  1108. // 多余参数是否合并
  1109. $merge = !empty($option['merge_extra_vars']);
  1110. if ($merge && $len1 > $len2) {
  1111. $url = str_replace('|', $depr, $url);
  1112. $url = implode('|', explode($depr, $url, $len2 + 1));
  1113. }
  1114. if ($len1 >= $len2 || strpos($rule, '[')) {
  1115. if (!empty($option['complete_match'])) {
  1116. // 完整匹配
  1117. if (!$merge && $len1 != $len2 && (false === strpos($rule, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($rule, '['))) {
  1118. return false;
  1119. }
  1120. }
  1121. $pattern = array_merge(self::$rules['pattern'], $pattern);
  1122. if (false !== $match = self::match($url, $rule, $pattern)) {
  1123. // 匹配到路由规则
  1124. return self::parseRule($rule, $route, $url, $option, $match);
  1125. }
  1126. }
  1127. return false;
  1128. }
  1129. /**
  1130. * 解析模块的URL地址 [模块/控制器/操作?]参数1=值1&参数2=值2...
  1131. * @access public
  1132. * @param string $url URL地址
  1133. * @param string $depr URL分隔符
  1134. * @param bool $autoSearch 是否自动深度搜索控制器
  1135. * @return array
  1136. */
  1137. public static function parseUrl($url, $depr = '/', $autoSearch = false)
  1138. {
  1139. if (isset(self::$bind['module'])) {
  1140. $bind = str_replace('/', $depr, self::$bind['module']);
  1141. // 如果有模块/控制器绑定
  1142. $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr);
  1143. }
  1144. $url = str_replace($depr, '|', $url);
  1145. list($path, $var) = self::parseUrlPath($url);
  1146. $route = [null, null, null];
  1147. if (isset($path)) {
  1148. // 解析模块
  1149. $module = Config::get('app_multi_module') ? array_shift($path) : null;
  1150. if ($autoSearch) {
  1151. // 自动搜索控制器
  1152. $dir = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer');
  1153. $suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : '';
  1154. $item = [];
  1155. $find = false;
  1156. foreach ($path as $val) {
  1157. $item[] = $val;
  1158. $file = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT;
  1159. $file = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT;
  1160. if (is_file($file)) {
  1161. $find = true;
  1162. break;
  1163. } else {
  1164. $dir .= DS . Loader::parseName($val);
  1165. }
  1166. }
  1167. if ($find) {
  1168. $controller = implode('.', $item);
  1169. $path = array_slice($path, count($item));
  1170. } else {
  1171. $controller = array_shift($path);
  1172. }
  1173. } else {
  1174. // 解析控制器
  1175. $controller = !empty($path) ? array_shift($path) : null;
  1176. }
  1177. // 解析操作
  1178. $action = !empty($path) ? array_shift($path) : null;
  1179. // 解析额外参数
  1180. self::parseUrlParams(empty($path) ? '' : implode('|', $path));
  1181. // 封装路由
  1182. $route = [$module, $controller, $action];
  1183. // 检查地址是否被定义过路由
  1184. $name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action);
  1185. $name2 = '';
  1186. if (empty($module) || isset($bind) && $module == $bind) {
  1187. $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action);
  1188. }
  1189. if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) {
  1190. throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url));
  1191. }
  1192. }
  1193. return ['type' => 'module', 'module' => $route];
  1194. }
  1195. /**
  1196. * 解析URL的pathinfo参数和变量
  1197. * @access private
  1198. * @param string $url URL地址
  1199. * @return array
  1200. */
  1201. private static function parseUrlPath($url)
  1202. {
  1203. // 分隔符替换 确保路由定义使用统一的分隔符
  1204. $url = str_replace('|', '/', $url);
  1205. $url = trim($url, '/');
  1206. $var = [];
  1207. if (false !== strpos($url, '?')) {
  1208. // [模块/控制器/操作?]参数1=值1&参数2=值2...
  1209. $info = parse_url($url);
  1210. $path = explode('/', $info['path']);
  1211. parse_str($info['query'], $var);
  1212. } elseif (strpos($url, '/')) {
  1213. // [模块/控制器/操作]
  1214. $path = explode('/', $url);
  1215. } else {
  1216. $path = [$url];
  1217. }
  1218. return [$path, $var];
  1219. }
  1220. /**
  1221. * 检测URL和规则路由是否匹配
  1222. * @access private
  1223. * @param string $url URL地址
  1224. * @param string $rule 路由规则
  1225. * @param array $pattern 变量规则
  1226. * @return array|false
  1227. */
  1228. private static function match($url, $rule, $pattern)
  1229. {
  1230. $m2 = explode('/', $rule);
  1231. $m1 = explode('|', $url);
  1232. $var = [];
  1233. foreach ($m2 as $key => $val) {
  1234. // val中定义了多个变量 <id><name>
  1235. if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) {
  1236. $value = [];
  1237. $replace = [];
  1238. foreach ($matches[1] as $name) {
  1239. if (strpos($name, '?')) {
  1240. $name = substr($name, 0, -1);
  1241. $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')?';
  1242. } else {
  1243. $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')';
  1244. }
  1245. $value[] = $name;
  1246. }
  1247. $val = str_replace($matches[0], $replace, $val);
  1248. if (preg_match('/^' . $val . '$/', isset($m1[$key]) ? $m1[$key] : '', $match)) {
  1249. array_shift($match);
  1250. foreach ($value as $k => $name) {
  1251. if (isset($match[$k])) {
  1252. $var[$name] = $match[$k];
  1253. }
  1254. }
  1255. continue;
  1256. } else {
  1257. return false;
  1258. }
  1259. }
  1260. if (0 === strpos($val, '[:')) {
  1261. // 可选参数
  1262. $val = substr($val, 1, -1);
  1263. $optional = true;
  1264. } else {
  1265. $optional = false;
  1266. }
  1267. if (0 === strpos($val, ':')) {
  1268. // URL变量
  1269. $name = substr($val, 1);
  1270. if (!$optional && !isset($m1[$key])) {
  1271. return false;
  1272. }
  1273. if (isset($m1[$key]) && isset($pattern[$name])) {
  1274. // 检查变量规则
  1275. if ($pattern[$name] instanceof \Closure) {
  1276. $result = call_user_func_array($pattern[$name], [$m1[$key]]);
  1277. if (false === $result) {
  1278. return false;
  1279. }
  1280. } elseif (!preg_match(0 === strpos($pattern[$name], '/') ? $pattern[$name] : '/^' . $pattern[$name] . '$/', $m1[$key])) {
  1281. return false;
  1282. }
  1283. }
  1284. $var[$name] = isset($m1[$key]) ? $m1[$key] : '';
  1285. } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) {
  1286. return false;
  1287. }
  1288. }
  1289. // 成功匹配后返回URL中的动态变量数组
  1290. return $var;
  1291. }
  1292. /**
  1293. * 解析规则路由
  1294. * @access private
  1295. * @param string $rule 路由规则
  1296. * @param string $route 路由地址
  1297. * @param string $pathinfo URL地址
  1298. * @param array $option 路由参数
  1299. * @param array $matches 匹配的变量
  1300. * @return array
  1301. */
  1302. private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = [])
  1303. {
  1304. $request = Request::instance();
  1305. // 解析路由规则
  1306. if ($rule) {
  1307. $rule = explode('/', $rule);
  1308. // 获取URL地址中的参数
  1309. $paths = explode('|', $pathinfo);
  1310. foreach ($rule as $item) {
  1311. $fun = '';
  1312. if (0 === strpos($item, '[:')) {
  1313. $item = substr($item, 1, -1);
  1314. }
  1315. if (0 === strpos($item, ':')) {
  1316. $var = substr($item, 1);
  1317. $matches[$var] = array_shift($paths);
  1318. } else {
  1319. // 过滤URL中的静态变量
  1320. array_shift($paths);
  1321. }
  1322. }
  1323. } else {
  1324. $paths = explode('|', $pathinfo);
  1325. }
  1326. // 获取路由地址规则
  1327. if (is_string($route) && isset($option['prefix'])) {
  1328. // 路由地址前缀
  1329. $route = $option['prefix'] . $route;
  1330. }
  1331. // 替换路由地址中的变量
  1332. if (is_string($route) && !empty($matches)) {
  1333. foreach ($matches as $key => $val) {
  1334. if (false !== strpos($route, ':' . $key)) {
  1335. $route = str_replace(':' . $key, $val, $route);
  1336. }
  1337. }
  1338. }
  1339. // 绑定模型数据
  1340. if (isset($option['bind_model'])) {
  1341. $bind = [];
  1342. foreach ($option['bind_model'] as $key => $val) {
  1343. if ($val instanceof \Closure) {
  1344. $result = call_user_func_array($val, [$matches]);
  1345. } else {
  1346. if (is_array($val)) {
  1347. $fields = explode('&', $val[1]);
  1348. $model = $val[0];
  1349. $exception = isset($val[2]) ? $val[2] : true;
  1350. } else {
  1351. $fields = ['id'];
  1352. $model = $val;
  1353. $exception = true;
  1354. }
  1355. $where = [];
  1356. $match = true;
  1357. foreach ($fields as $field) {
  1358. if (!isset($matches[$field])) {
  1359. $match = false;
  1360. break;
  1361. } else {
  1362. $where[$field] = $matches[$field];
  1363. }
  1364. }
  1365. if ($match) {
  1366. $query = strpos($model, '\\') ? $model::where($where) : Loader::model($model)->where($where);
  1367. $result = $query->failException($exception)->find();
  1368. }
  1369. }
  1370. if (!empty($result)) {
  1371. $bind[$key] = $result;
  1372. }
  1373. }
  1374. $request->bind($bind);
  1375. }
  1376. if (!empty($option['response'])) {
  1377. Hook::add('response_send', $option['response']);
  1378. }
  1379. // 解析额外参数
  1380. self::parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches);
  1381. // 记录匹配的路由信息
  1382. $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]);
  1383. // 检测路由after行为
  1384. if (!empty($option['after_behavior'])) {
  1385. if ($option['after_behavior'] instanceof \Closure) {
  1386. $result = call_user_func_array($option['after_behavior'], []);
  1387. } else {
  1388. foreach ((array) $option['after_behavior'] as $behavior) {
  1389. $result = Hook::exec($behavior, '');
  1390. if (!is_null($result)) {
  1391. break;
  1392. }
  1393. }
  1394. }
  1395. // 路由规则重定向
  1396. if ($result instanceof Response) {
  1397. return ['type' => 'response', 'response' => $result];
  1398. } elseif (is_array($result)) {
  1399. return $result;
  1400. }
  1401. }
  1402. if ($route instanceof \Closure) {
  1403. // 执行闭包
  1404. $result = ['type' => 'function', 'function' => $route];
  1405. } elseif (0 === strpos($route, '/') || strpos($route, '://')) {
  1406. // 路由到重定向地址
  1407. $result = ['type' => 'redirect', 'url' => $route, 'status' => isset($option['status']) ? $option['status'] : 301];
  1408. } elseif (false !== strpos($route, '\\')) {
  1409. // 路由到方法
  1410. list($path, $var) = self::parseUrlPath($route);
  1411. $route = str_replace('/', '@', implode('/', $path));
  1412. $method = strpos($route, '@') ? explode('@', $route) : $route;
  1413. $result = ['type' => 'method', 'method' => $method, 'var' => $var];
  1414. } elseif (0 === strpos($route, '@')) {
  1415. // 路由到控制器
  1416. $route = substr($route, 1);
  1417. list($route, $var) = self::parseUrlPath($route);
  1418. $result = ['type' => 'controller', 'controller' => implode('/', $route), 'var' => $var];
  1419. $request->action(array_pop($route));
  1420. $request->controller($route ? array_pop($route) : Config::get('default_controller'));
  1421. $request->module($route ? array_pop($route) : Config::get('default_module'));
  1422. App::$modulePath = APP_PATH . (Config::get('app_multi_module') ? $request->module() . DS : '');
  1423. } else {
  1424. // 路由到模块/控制器/操作
  1425. $result = self::parseModule($route);
  1426. }
  1427. // 开启请求缓存
  1428. if ($request->isGet() && isset($option['cache'])) {
  1429. $cache = $option['cache'];
  1430. if (is_array($cache)) {
  1431. list($key, $expire, $tag) = array_pad($cache, 3, null);
  1432. } else {
  1433. $key = str_replace('|', '/', $pathinfo);
  1434. $expire = $cache;
  1435. $tag = null;
  1436. }
  1437. $request->cache($key, $expire, $tag);
  1438. }
  1439. return $result;
  1440. }
  1441. /**
  1442. * 解析URL地址为 模块/控制器/操作
  1443. * @access private
  1444. * @param string $url URL地址
  1445. * @return array
  1446. */
  1447. private static function parseModule($url)
  1448. {
  1449. list($path, $var) = self::parseUrlPath($url);
  1450. $action = array_pop($path);
  1451. $controller = !empty($path) ? array_pop($path) : null;
  1452. $module = Config::get('app_multi_module') && !empty($path) ? array_pop($path) : null;
  1453. $method = Request::instance()->method();
  1454. if (Config::get('use_action_prefix') && !empty(self::$methodPrefix[$method])) {
  1455. // 操作方法前缀支持
  1456. $action = 0 !== strpos($action, self::$methodPrefix[$method]) ? self::$methodPrefix[$method] . $action : $action;
  1457. }
  1458. // 设置当前请求的路由变量
  1459. Request::instance()->route($var);
  1460. // 路由到模块/控制器/操作
  1461. return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => false];
  1462. }
  1463. /**
  1464. * 解析URL地址中的参数Request对象
  1465. * @access private
  1466. * @param string $url 路由规则
  1467. * @param array $var 变量
  1468. * @return void
  1469. */
  1470. private static function parseUrlParams($url, &$var = [])
  1471. {
  1472. if ($url) {
  1473. if (Config::get('url_param_type')) {
  1474. $var += explode('|', $url);
  1475. } else {
  1476. preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) {
  1477. $var[$match[1]] = strip_tags($match[2]);
  1478. }, $url);
  1479. }
  1480. }
  1481. // 设置当前请求的参数
  1482. Request::instance()->route($var);
  1483. }
  1484. // 分析路由规则中的变量
  1485. private static function parseVar($rule)
  1486. {
  1487. // 提取路由规则中的变量
  1488. $var = [];
  1489. foreach (explode('/', $rule) as $val) {
  1490. $optional = false;
  1491. if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) {
  1492. foreach ($matches[1] as $name) {
  1493. if (strpos($name, '?')) {
  1494. $name = substr($name, 0, -1);
  1495. $optional = true;
  1496. } else {
  1497. $optional = false;
  1498. }
  1499. $var[$name] = $optional ? 2 : 1;
  1500. }
  1501. }
  1502. if (0 === strpos($val, '[:')) {
  1503. // 可选参数
  1504. $optional = true;
  1505. $val = substr($val, 1, -1);
  1506. }
  1507. if (0 === strpos($val, ':')) {
  1508. // URL变量
  1509. $name = substr($val, 1);
  1510. $var[$name] = $optional ? 2 : 1;
  1511. }
  1512. }
  1513. return $var;
  1514. }
  1515. }