RuleItem.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  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\route;
  12. use think\Container;
  13. use think\Exception;
  14. use think\Route;
  15. class RuleItem extends Rule
  16. {
  17. protected $hasSetRule;
  18. /**
  19. * 架构函数
  20. * @access public
  21. * @param Route $router 路由实例
  22. * @param RuleGroup $parent 上级对象
  23. * @param string $name 路由标识
  24. * @param string|array $rule 路由规则
  25. * @param string|\Closure $route 路由地址
  26. * @param string $method 请求类型
  27. * @param array $option 路由参数
  28. * @param array $pattern 变量规则
  29. */
  30. public function __construct(Route $router, RuleGroup $parent, $name, $rule, $route, $method = '*', $option = [], $pattern = [])
  31. {
  32. $this->router = $router;
  33. $this->parent = $parent;
  34. $this->name = $name;
  35. $this->route = $route;
  36. $this->method = $method;
  37. $this->option = $option;
  38. $this->pattern = $pattern;
  39. $this->setRule($rule);
  40. if (!empty($option['cross_domain'])) {
  41. $this->router->setCrossDomainRule($this, $method);
  42. }
  43. }
  44. /**
  45. * 路由规则预处理
  46. * @access public
  47. * @param string $rule 路由规则
  48. * @return void
  49. */
  50. public function setRule($rule)
  51. {
  52. if ('$' == substr($rule, -1, 1)) {
  53. // 是否完整匹配
  54. $rule = substr($rule, 0, -1);
  55. $this->option['complete_match'] = true;
  56. }
  57. $rule = '/' != $rule ? ltrim($rule, '/') : '';
  58. if ($this->parent && $prefix = $this->parent->getFullName()) {
  59. $rule = $prefix . ($rule ? '/' . ltrim($rule, '/') : '');
  60. }
  61. if (false !== strpos($rule, ':')) {
  62. $this->rule = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $rule);
  63. } else {
  64. $this->rule = $rule;
  65. }
  66. // 生成路由标识的快捷访问
  67. $this->setRuleName();
  68. }
  69. /**
  70. * 检查后缀
  71. * @access public
  72. * @param string $ext
  73. * @return $this
  74. */
  75. public function ext($ext = '')
  76. {
  77. $this->option('ext', $ext);
  78. $this->setRuleName(true);
  79. return $this;
  80. }
  81. /**
  82. * 设置别名
  83. * @access public
  84. * @param string $name
  85. * @return $this
  86. */
  87. public function name($name)
  88. {
  89. $this->name = $name;
  90. $this->setRuleName(true);
  91. return $this;
  92. }
  93. /**
  94. * 设置路由标识 用于URL反解生成
  95. * @access protected
  96. * @param bool $first 是否插入开头
  97. * @return void
  98. */
  99. protected function setRuleName($first = false)
  100. {
  101. if ($this->name) {
  102. $vars = $this->parseVar($this->rule);
  103. $name = strtolower($this->name);
  104. if (isset($this->option['ext'])) {
  105. $suffix = $this->option['ext'];
  106. } elseif ($this->parent->getOption('ext')) {
  107. $suffix = $this->parent->getOption('ext');
  108. } else {
  109. $suffix = null;
  110. }
  111. $value = [$this->rule, $vars, $this->parent->getDomain(), $suffix, $this->method];
  112. Container::get('rule_name')->set($name, $value, $first);
  113. }
  114. if (!$this->hasSetRule) {
  115. Container::get('rule_name')->setRule($this->rule, $this);
  116. $this->hasSetRule = true;
  117. }
  118. }
  119. /**
  120. * 检测路由
  121. * @access public
  122. * @param Request $request 请求对象
  123. * @param string $url 访问地址
  124. * @param array $match 匹配路由变量
  125. * @param bool $completeMatch 路由是否完全匹配
  126. * @return Dispatch|false
  127. */
  128. public function checkRule($request, $url, $match = null, $completeMatch = false)
  129. {
  130. // 检查参数有效性
  131. if (!$this->checkOption($this->option, $request)) {
  132. return false;
  133. }
  134. // 合并分组参数
  135. $option = $this->mergeGroupOptions();
  136. $url = $this->urlSuffixCheck($request, $url, $option);
  137. if (is_null($match)) {
  138. $match = $this->match($url, $option, $completeMatch);
  139. }
  140. if (false !== $match) {
  141. if (!empty($option['cross_domain'])) {
  142. if ($dispatch = $this->checkCrossDomain($request)) {
  143. // 允许跨域
  144. return $dispatch;
  145. }
  146. $option['header'] = $this->option['header'];
  147. }
  148. // 检查前置行为
  149. if (isset($option['before']) && false === $this->checkBefore($option['before'])) {
  150. return false;
  151. }
  152. return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match);
  153. }
  154. return false;
  155. }
  156. /**
  157. * 检测路由(含路由匹配)
  158. * @access public
  159. * @param Request $request 请求对象
  160. * @param string $url 访问地址
  161. * @param string $depr 路径分隔符
  162. * @param bool $completeMatch 路由是否完全匹配
  163. * @return Dispatch|false
  164. */
  165. public function check($request, $url, $completeMatch = false)
  166. {
  167. return $this->checkRule($request, $url, null, $completeMatch);
  168. }
  169. /**
  170. * URL后缀及Slash检查
  171. * @access protected
  172. * @param Request $request 请求对象
  173. * @param string $url 访问地址
  174. * @param array $option 路由参数
  175. * @return string
  176. */
  177. protected function urlSuffixCheck($request, $url, $option = [])
  178. {
  179. // 是否区分 / 地址访问
  180. if (!empty($option['remove_slash']) && '/' != $this->rule) {
  181. $this->rule = rtrim($this->rule, '/');
  182. $url = rtrim($url, '|');
  183. }
  184. if (isset($option['ext'])) {
  185. // 路由ext参数 优先于系统配置的URL伪静态后缀参数
  186. $url = preg_replace('/\.(' . $request->ext() . ')$/i', '', $url);
  187. }
  188. return $url;
  189. }
  190. /**
  191. * 检测URL和规则路由是否匹配
  192. * @access private
  193. * @param string $url URL地址
  194. * @param array $option 路由参数
  195. * @param bool $completeMatch 路由是否完全匹配
  196. * @return array|false
  197. */
  198. private function match($url, $option, $completeMatch)
  199. {
  200. if (isset($option['complete_match'])) {
  201. $completeMatch = $option['complete_match'];
  202. }
  203. $pattern = array_merge($this->parent->getPattern(), $this->pattern);
  204. $depr = $this->router->config('pathinfo_depr');
  205. // 检查完整规则定义
  206. if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) {
  207. return false;
  208. }
  209. $var = [];
  210. $url = $depr . str_replace('|', $depr, $url);
  211. $rule = $depr . str_replace('/', $depr, $this->rule);
  212. if ($depr == $rule && $depr != $url) {
  213. return false;
  214. }
  215. if (false === strpos($rule, '<')) {
  216. if (0 === strcasecmp($rule, $url) || (!$completeMatch && 0 === strncasecmp($rule . $depr, $url . $depr, strlen($rule . $depr)))) {
  217. return $var;
  218. }
  219. return false;
  220. }
  221. $slash = preg_quote('/-' . $depr, '/');
  222. if ($matchRule = preg_split('/[' . $slash . ']?<\w+\??>/', $rule, 2)) {
  223. if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) {
  224. return false;
  225. }
  226. }
  227. if (preg_match_all('/[' . $slash . ']?<?\w+\??>?/', $rule, $matches)) {
  228. $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch);
  229. try {
  230. if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/u', $url, $match)) {
  231. return false;
  232. }
  233. } catch (\Exception $e) {
  234. throw new Exception('route pattern error');
  235. }
  236. foreach ($match as $key => $val) {
  237. if (is_string($key)) {
  238. $var[$key] = $val;
  239. }
  240. }
  241. }
  242. // 成功匹配后返回URL中的动态变量数组
  243. return $var;
  244. }
  245. }