123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601 |
- <?php
- // +----------------------------------------------------------------------
- // | ThinkPHP [ WE CAN DO IT JUST THINK ]
- // +----------------------------------------------------------------------
- // | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
- // +----------------------------------------------------------------------
- // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
- // +----------------------------------------------------------------------
- // | Author: liu21st <liu21st@gmail.com>
- // +----------------------------------------------------------------------
- namespace think\route;
- use think\Container;
- use think\Exception;
- use think\Request;
- use think\Response;
- use think\Route;
- use think\route\dispatch\Response as ResponseDispatch;
- use think\route\dispatch\Url as UrlDispatch;
- class RuleGroup extends Rule
- {
- // 分组路由(包括子分组)
- protected $rules = [
- '*' => [],
- 'get' => [],
- 'post' => [],
- 'put' => [],
- 'patch' => [],
- 'delete' => [],
- 'head' => [],
- 'options' => [],
- ];
- // MISS路由
- protected $miss;
- // 自动路由
- protected $auto;
- // 完整名称
- protected $fullName;
- // 所在域名
- protected $domain;
- /**
- * 架构函数
- * @access public
- * @param Route $router 路由对象
- * @param RuleGroup $parent 上级对象
- * @param string $name 分组名称
- * @param mixed $rule 分组路由
- * @param array $option 路由参数
- * @param array $pattern 变量规则
- */
- public function __construct(Route $router, RuleGroup $parent = null, $name = '', $rule = [], $option = [], $pattern = [])
- {
- $this->router = $router;
- $this->parent = $parent;
- $this->rule = $rule;
- $this->name = trim($name, '/');
- $this->option = $option;
- $this->pattern = $pattern;
- $this->setFullName();
- if ($this->parent) {
- $this->domain = $this->parent->getDomain();
- $this->parent->addRuleItem($this);
- }
- if (!empty($option['cross_domain'])) {
- $this->router->setCrossDomainRule($this);
- }
- if ($router->isTest()) {
- $this->lazy(false);
- }
- }
- /**
- * 设置分组的路由规则
- * @access public
- * @return void
- */
- protected function setFullName()
- {
- if (false !== strpos($this->name, ':')) {
- $this->name = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $this->name);
- }
- if ($this->parent && $this->parent->getFullName()) {
- $this->fullName = $this->parent->getFullName() . ($this->name ? '/' . $this->name : '');
- } else {
- $this->fullName = $this->name;
- }
- }
- /**
- * 获取所属域名
- * @access public
- * @return string
- */
- public function getDomain()
- {
- return $this->domain;
- }
- /**
- * 检测分组路由
- * @access public
- * @param Request $request 请求对象
- * @param string $url 访问地址
- * @param bool $completeMatch 路由是否完全匹配
- * @return Dispatch|false
- */
- public function check($request, $url, $completeMatch = false)
- {
- // 跨域OPTIONS请求
- if ($dispatch = $this->checkCrossDomain($request)) {
- return $dispatch;
- }
- // 检查分组有效性
- if (!$this->checkOption($this->option, $request) || !$this->checkUrl($url)) {
- return false;
- }
- // 检查前置行为
- if (isset($this->option['before'])) {
- if (false === $this->checkBefore($this->option['before'])) {
- return false;
- }
- unset($this->option['before']);
- }
- // 解析分组路由
- if ($this instanceof Resource) {
- $this->buildResourceRule();
- } elseif ($this->rule) {
- if ($this->rule instanceof Response) {
- return new ResponseDispatch($request, $this, $this->rule);
- }
- $this->parseGroupRule($this->rule);
- }
- // 获取当前路由规则
- $method = strtolower($request->method());
- $rules = $this->getMethodRules($method);
- if ($this->parent) {
- // 合并分组参数
- $this->mergeGroupOptions();
- // 合并分组变量规则
- $this->pattern = array_merge($this->parent->getPattern(), $this->pattern);
- }
- if (isset($this->option['complete_match'])) {
- $completeMatch = $this->option['complete_match'];
- }
- if (!empty($this->option['merge_rule_regex'])) {
- // 合并路由正则规则进行路由匹配检查
- $result = $this->checkMergeRuleRegex($request, $rules, $url, $completeMatch);
- if (false !== $result) {
- return $result;
- }
- }
- // 检查分组路由
- foreach ($rules as $key => $item) {
- $result = $item->check($request, $url, $completeMatch);
- if (false !== $result) {
- return $result;
- }
- }
- if ($this->auto) {
- // 自动解析URL地址
- $result = new UrlDispatch($request, $this, $this->auto . '/' . $url, ['auto_search' => false]);
- } elseif ($this->miss && in_array($this->miss->getMethod(), ['*', $method])) {
- // 未匹配所有路由的路由规则处理
- $result = $this->miss->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->mergeGroupOptions());
- } else {
- $result = false;
- }
- return $result;
- }
- /**
- * 获取当前请求的路由规则(包括子分组、资源路由)
- * @access protected
- * @param string $method
- * @return array
- */
- protected function getMethodRules($method)
- {
- return array_merge($this->rules[$method], $this->rules['*']);
- }
- /**
- * 分组URL匹配检查
- * @access protected
- * @param string $url
- * @return bool
- */
- protected function checkUrl($url)
- {
- if ($this->fullName) {
- $pos = strpos($this->fullName, '<');
- if (false !== $pos) {
- $str = substr($this->fullName, 0, $pos);
- } else {
- $str = $this->fullName;
- }
- if ($str && 0 !== stripos(str_replace('|', '/', $url), $str)) {
- return false;
- }
- }
- return true;
- }
- /**
- * 延迟解析分组的路由规则
- * @access public
- * @param bool $lazy 路由是否延迟解析
- * @return $this
- */
- public function lazy($lazy = true)
- {
- if (!$lazy) {
- $this->parseGroupRule($this->rule);
- $this->rule = null;
- }
- return $this;
- }
- /**
- * 解析分组和域名的路由规则及绑定
- * @access public
- * @param mixed $rule 路由规则
- * @return void
- */
- public function parseGroupRule($rule)
- {
- $origin = $this->router->getGroup();
- $this->router->setGroup($this);
- if ($rule instanceof \Closure) {
- Container::getInstance()->invokeFunction($rule);
- } elseif (is_array($rule)) {
- $this->addRules($rule);
- } elseif (is_string($rule) && $rule) {
- $this->router->bind($rule, $this->domain);
- }
- $this->router->setGroup($origin);
- }
- /**
- * 检测分组路由
- * @access public
- * @param Request $request 请求对象
- * @param array $rules 路由规则
- * @param string $url 访问地址
- * @param bool $completeMatch 路由是否完全匹配
- * @return Dispatch|false
- */
- protected function checkMergeRuleRegex($request, &$rules, $url, $completeMatch)
- {
- $depr = $this->router->config('pathinfo_depr');
- $url = $depr . str_replace('|', $depr, $url);
- foreach ($rules as $key => $item) {
- if ($item instanceof RuleItem) {
- $rule = $depr . str_replace('/', $depr, $item->getRule());
- if ($depr == $rule && $depr != $url) {
- unset($rules[$key]);
- continue;
- }
- $complete = null !== $item->getOption('complete_match') ? $item->getOption('complete_match') : $completeMatch;
- if (false === strpos($rule, '<')) {
- if (0 === strcasecmp($rule, $url) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) {
- return $item->checkRule($request, $url, []);
- }
- unset($rules[$key]);
- continue;
- }
- $slash = preg_quote('/-' . $depr, '/');
- if ($matchRule = preg_split('/[' . $slash . ']<\w+\??>/', $rule, 2)) {
- if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) {
- unset($rules[$key]);
- continue;
- }
- }
- if (preg_match_all('/[' . $slash . ']?<?\w+\??>?/', $rule, $matches)) {
- unset($rules[$key]);
- $pattern = array_merge($this->getPattern(), $item->getPattern());
- $option = array_merge($this->getOption(), $item->getOption());
- $regex[$key] = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $complete, '_THINK_' . $key);
- $items[$key] = $item;
- }
- }
- }
- if (empty($regex)) {
- return false;
- }
- try {
- $result = preg_match('/^(?:' . implode('|', $regex) . ')/u', $url, $match);
- } catch (\Exception $e) {
- throw new Exception('route pattern error');
- }
- if ($result) {
- $var = [];
- foreach ($match as $key => $val) {
- if (is_string($key) && '' !== $val) {
- list($name, $pos) = explode('_THINK_', $key);
- $var[$name] = $val;
- }
- }
- if (!isset($pos)) {
- foreach ($regex as $key => $item) {
- if (0 === strpos(str_replace(['\/', '\-', '\\' . $depr], ['/', '-', $depr], $item), $match[0])) {
- $pos = $key;
- break;
- }
- }
- }
- $rule = $items[$pos]->getRule();
- $array = $this->router->getRule($rule);
- foreach ($array as $item) {
- if (in_array($item->getMethod(), ['*', strtolower($request->method())])) {
- $result = $item->checkRule($request, $url, $var);
- if (false !== $result) {
- return $result;
- }
- }
- }
- }
- return false;
- }
- /**
- * 获取分组的MISS路由
- * @access public
- * @return RuleItem|null
- */
- public function getMissRule()
- {
- return $this->miss;
- }
- /**
- * 获取分组的自动路由
- * @access public
- * @return string
- */
- public function getAutoRule()
- {
- return $this->auto;
- }
- /**
- * 注册自动路由
- * @access public
- * @param string $route 路由规则
- * @return void
- */
- public function addAutoRule($route)
- {
- $this->auto = $route;
- }
- /**
- * 注册MISS路由
- * @access public
- * @param string $route 路由地址
- * @param string $method 请求类型
- * @param array $option 路由参数
- * @return RuleItem
- */
- public function addMissRule($route, $method = '*', $option = [])
- {
- // 创建路由规则实例
- $ruleItem = new RuleItem($this->router, $this, null, '', $route, strtolower($method), $option);
- $this->miss = $ruleItem;
- return $ruleItem;
- }
- /**
- * 添加分组下的路由规则或者子分组
- * @access public
- * @param string $rule 路由规则
- * @param string $route 路由地址
- * @param string $method 请求类型
- * @param array $option 路由参数
- * @param array $pattern 变量规则
- * @return $this
- */
- public function addRule($rule, $route, $method = '*', $option = [], $pattern = [])
- {
- // 读取路由标识
- if (is_array($rule)) {
- $name = $rule[0];
- $rule = $rule[1];
- } elseif (is_string($route)) {
- $name = $route;
- } else {
- $name = null;
- }
- $method = strtolower($method);
- if ('/' === $rule || '' === $rule) {
- // 首页自动完整匹配
- $rule .= '$';
- }
- // 创建路由规则实例
- $ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method, $option, $pattern);
- if (!empty($option['cross_domain'])) {
- $this->router->setCrossDomainRule($ruleItem, $method);
- }
- $this->addRuleItem($ruleItem, $method);
- return $ruleItem;
- }
- /**
- * 批量注册路由规则
- * @access public
- * @param array $rules 路由规则
- * @param string $method 请求类型
- * @param array $option 路由参数
- * @param array $pattern 变量规则
- * @return void
- */
- public function addRules($rules, $method = '*', $option = [], $pattern = [])
- {
- foreach ($rules as $key => $val) {
- if (is_numeric($key)) {
- $key = array_shift($val);
- }
- if (is_array($val)) {
- $route = array_shift($val);
- $option = $val ? array_shift($val) : [];
- $pattern = $val ? array_shift($val) : [];
- } else {
- $route = $val;
- }
- $this->addRule($key, $route, $method, $option, $pattern);
- }
- }
- public function addRuleItem($rule, $method = '*')
- {
- if (strpos($method, '|')) {
- $rule->method($method);
- $method = '*';
- }
- $this->rules[$method][] = $rule;
- return $this;
- }
- /**
- * 设置分组的路由前缀
- * @access public
- * @param string $prefix
- * @return $this
- */
- public function prefix($prefix)
- {
- if ($this->parent && $this->parent->getOption('prefix')) {
- $prefix = $this->parent->getOption('prefix') . $prefix;
- }
- return $this->option('prefix', $prefix);
- }
- /**
- * 设置资源允许
- * @access public
- * @param array $only
- * @return $this
- */
- public function only($only)
- {
- return $this->option('only', $only);
- }
- /**
- * 设置资源排除
- * @access public
- * @param array $except
- * @return $this
- */
- public function except($except)
- {
- return $this->option('except', $except);
- }
- /**
- * 设置资源路由的变量
- * @access public
- * @param array $vars
- * @return $this
- */
- public function vars($vars)
- {
- return $this->option('var', $vars);
- }
- /**
- * 合并分组的路由规则正则
- * @access public
- * @param bool $merge
- * @return $this
- */
- public function mergeRuleRegex($merge = true)
- {
- return $this->option('merge_rule_regex', $merge);
- }
- /**
- * 获取完整分组Name
- * @access public
- * @return string
- */
- public function getFullName()
- {
- return $this->fullName;
- }
- /**
- * 获取分组的路由规则
- * @access public
- * @param string $method
- * @return array
- */
- public function getRules($method = '')
- {
- if ('' === $method) {
- return $this->rules;
- }
- return isset($this->rules[strtolower($method)]) ? $this->rules[strtolower($method)] : [];
- }
- /**
- * 清空分组下的路由规则
- * @access public
- * @return void
- */
- public function clear()
- {
- $this->rules = [
- '*' => [],
- 'get' => [],
- 'post' => [],
- 'put' => [],
- 'patch' => [],
- 'delete' => [],
- 'head' => [],
- 'options' => [],
- ];
- }
- }
|