| 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' => [],        ];    }}
 |