TP5.1 源码窥探之路由

在上一节中,了解到项目初始化的大致步骤,加载Env环境变量,加载惯例配置,加载公共文件,加载助手函数,加载配置文件,注册类库别名,设置时区,设置语言包等一系列操作,再然后的操作就是路由初始化,现在就看一下路由是怎么初始化的。

路由初始化

thinkphp/library/think/App.php

public function routeInit()
{
    // 路由检测
    $files = scandir($this->routePath);
    foreach ($files as $file) {
        if (strpos($file, '.php')) {
            $filename = $this->routePath . $file;
            // 导入路由配置
            $rules = include $filename;
            if (is_array($rules)) {
                $this->route->import($rules);
            }
        }
    }

    if ($this->route->config('route_annotation')) {
        // 自动生成路由定义
        if ($this->appDebug) {
            $suffix = $this->route->config('controller_suffix') || $this->route->config('class_suffix');
            $this->build->buildRoute($suffix);
        }

        $filename = $this->runtimePath . 'build_route.php';

        if (is_file($filename)) {
            include $filename;
        }
    }
}

由上面的代码可以看出,系统是遍历引入项目根目录下的route目录的php文件,如果引入路由文件后返回的数组,则执行import方法。接着如果项目中配置了注解路由,自动生成路由定义,如果runtime文件夹下存在文件则引入。

路由解析

路由的解析过程:

  • 路由定义:完成路由规则的定义和参数设置(5.1的路由定义采用了对象化的思维,相对5.0而言更直观);
  • 路由检测:检查当前的URL请求是否有匹配的路由;
  • 路由解析:解析当前路由实际对应的操作(方法或闭包);
  • 路由调度:执行路由解析的结果调度(主业务逻辑);

路由定义

最基础的路由定义方法是: Route::rule('路由表达式','路由地址','请求方式','路由参数','变量规则');

另外两个比较有意思的定义是:

  • 动态路由:Route::get('hello/:name', 'index/:name/hello');
  • 路由地址多级控制器:Route::get('blog/:id','index/group.blog/read');

就以route/route.php文件里面的路由定义为例

Route::get('hello/:name', 'index/hello');

系统类文件自动加载,会定位到thinkphp/library/think/Route.php文件,执行get方法

public function get($rule, $route = '', array $option = [], array $pattern = [])
{
    return $this->rule($rule, $route, 'GET', $option, $pattern);
}

然后又会调用rule方法

public function rule($rule, $route, $method = '*', array $option = [], array $pattern = [])
{
    return $this->group->addRule($rule, $route, $method, $option, $pattern);
}

然而$this->group是哪里来的,它是Route在初始化的时候定义的

public function __construct(App $app, array $config = [])
{
    $this->app     = $app;
    $this->request = $app['request'];
    $this->config  = $config;

    $this->host = $this->request->host(true) ?: $config['app_host'];

    $this->setDefaultDomain();
}

protected function setDefaultDomain()
{
    // 默认域名
    $this->domain = $this->host;

    // 注册默认域名
    $domain = new Domain($this, $this->host);

    $this->domains[$this->host] = $domain;

    //var_dump($domain);exit;
    // 默认分组
    $this->group = $domain;
}

由此可见$this->group是一个Domain实例对象。然而Domain类又继承RuleGroup类,然后再执行addRule方法。
thinkphp/library/think/route/RuleGroup.php

public function addRule($rule, $route, $method = '*', $option = [], $pattern = [])
{
    //var_dump($rule, $route, $method, $option, $pattern);exit;hello/:name,index/hello,get,[],[]
    // 读取路由标识
    if (is_array($rule)) {
        $name = $rule[0];
        $rule = $rule[1];
    } elseif (is_string($route)) {
        $name = $route;
    } else {
        $name = null;
    }
    //var_dump($name);exit;
    $method = strtolower($method);

    if ('/' === $rule || '' === $rule) {
        // 首页自动完整匹配
        $rule .= '$';
    }

    // 创建路由规则实例
    $ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method, $option, $pattern);
    //var_dump($ruleItem);exit;

    if (!empty($option['cross_domain'])) {
        $this->router->setCrossDomainRule($ruleItem, $method);
    }

    $this->addRuleItem($ruleItem, $method);

    return $ruleItem;
}

可见最终返回的是RuleItem对象实例,在RuleItem对象实例化的时候,有两个重要的步骤,一个是路由规则预处理,另外一个是设置路由标识,用于URL反解生成。
thinkphp/library/think/route/RuleItem.php

public function setRule($rule)
{
    //var_dump($rule);exit;        hello/:name
    if ('$' == substr($rule, -1, 1)) {
        // 是否完整匹配
        $rule = substr($rule, 0, -1);

        $this->option['complete_match'] = true;
    }

    $rule = '/' != $rule ? ltrim($rule, '/') : '';
    //var_dump($rule);exit;hello/:name
    if ($this->parent && $prefix = $this->parent->getFullName()) {
        $rule = $prefix . ($rule ? '/' . ltrim($rule, '/') : '');
    }
    //var_dump(111,$rule);exit;hello/:name
    if (false !== strpos($rule, ':')) {
        $this->rule = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $rule);// hello/<name>
    } else {
        $this->rule = $rule;
    }
    //var_dump($this->rule);exit;
    // 生成路由标识的快捷访问
    $this->setRuleName();
}

protected function setRuleName($first = false)
{
    //var_dump($this->name);exit; index/hello
    if ($this->name) {
        $vars = $this->parseVar($this->rule);
        $name = strtolower($this->name);

        if (isset($this->option['ext'])) {
            $suffix = $this->option['ext'];
        } elseif ($this->parent->getOption('ext')) {
            $suffix = $this->parent->getOption('ext');
        } else {
            $suffix = null;
        }

        $value = [$this->rule, $vars, $this->parent->getDomain(), $suffix, $this->method];
        //var_dump($name, $value, $first);exit;
        Container::get('rule_name')->set($name, $value, $first);
    }
    //var_dump(!$this->hasSetRule);exit; true
    if (!$this->hasSetRule) {
        Container::get('rule_name')->setRule($this->rule, $this);
        $this->hasSetRule = true;
    }
}

 protected function parseVar($rule)
{
    //var_dump($rule);exit; hello/<name>
    // 提取路由规则中的变量
    $var = [];

    if (preg_match_all('/<\w+\??>/', $rule, $matches)) {
        //var_dump($matches[0]);exit;
        foreach ($matches[0] as $name) {
            $optional = false;
            //var_dump($name);exit;<name>
            if (strpos($name, '?')) {
                $name     = substr($name, 1, -2);
                $optional = true;
            } else {
                $name = substr($name, 1, -1);
            }
            //var_dump($name);exit;
            $var[$name] = $optional ? 2 : 1;
        }
    }

    return $var;
}

注册路由规则
thinkphp/library/think/route/RuleName.php

public function setRule($rule, $route)
{
    $this->rule[$route->getDomain()][$rule][$route->getMethod()] = $route;
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
今年不学习,明天惨唧唧。
zs4336
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!