CodeIgniter源码阅读笔记(7)——路由类Router.php

CI框架能根据URL地址路由到正确的控制器和方法,主要是靠URI和Router这两个组件,URI组件对URL地址所携带的参数进行分段,Router组件再根据URI解析出来的分段参数找到对应的控制器,方法,及调用方法所需的参数,并且Router还能通过正则表达式自定义路由方式。
在阅读路由组件的源码时,我们需要思考以下几个问题:
1.Router是怎样根据URI的分段参数找到对应的类和方法?
2.自定义路由是怎样实现的?
带着以上几个问题我们开始阅读Router的源码:

1.构造函数

构造函数中有比较重要的就是$this->_set_routing();这句代码,set_routing这个函数才是真正进行实现路由的函数,在构造函数中调用它,即代表在路由组件初始化的时候路由就开始了。
$this->_set_routing();之前的代码都是在为这个方法准备需要的参数
$this->_set_routing();之后的代码则是将路由参数存储到外部可访问的变量中去

    public function __construct($routing = NULL)
    {
        //加载Config配置文件
        $this->config =& load_class('Config', 'core');
        //加载URI组件,因为路由过程中需要使用URI处理好的分段参数
        $this->uri =& load_class('URI', 'core');
        //是否通过query_string来路由的标识符
        $this->enable_query_strings = ( ! is_cli() && 
                    $this->config->item('enable_query_strings') === TRUE);
        //加载routes.php的路由配置文件
        is_array($routing) && isset($routing['directory']) && 
                    $this->set_directory($routing['directory']);
        //调用路由方法
        $this->_set_routing();
        //保存路由参数
        if (is_array($routing))
        {
            empty($routing['controller']) OR 
                    $this->set_class($routing['controller']);
            empty($routing['function'])   OR 
                    $this->set_method($routing['function']);
        }

        log_message('info', 'Router Class Initialized');
    }

_set_routing()函数才是真正实现路由的地方,那么接下来我们具体看看这个函数。

2._set_routing()

_set_routing()这个函数是主要实现路由的地方,它可以根据uri请求确定路由的内容,并且实现在路由配置文件中设置的路由方式
这个函数的代码可以分为三部分来阅读:
1.加载routes配置文件
2.query_string路由方式
3.非query_string路由方式

    protected function _set_routing()
    {
        //加载routes.php配置文件
        if (file_exists(APPPATH.'config/routes.php'))
        {
            include(APPPATH.'config/routes.php');
        }

        if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/routes.php'))
        {
            include(APPPATH.'config/'.ENVIRONMENT.'/routes.php');
        }

        //加载成功,保存对应参数到变量,这里将default_controller和translate_uri_dashes参数提取出来保存,我想应该是因为这两个参数使用较频繁
        if (isset($route) && is_array($route))
        {
            isset($route['default_controller']) && 
                $this->default_controller = $route['default_controller'];
            isset($route['translate_uri_dashes']) && 
                $this->translate_uri_dashes = $route['translate_uri_dashes'];
            unset($route['default_controller'],
                                    $route['translate_uri_dashes']);
            $this->routes = $route;
        }
        //当通过query_string来路由的时候
        if ($this->enable_query_strings)
        {
            //config配置文件中的配置项directory_trigger,controller_trigger,
            //function_trigger分别代表query_string 目录参数名称,控制器参数称,方法参数名称
            //获取目录参数
            if ( ! isset($this->directory))
            {
                $_d = $this->config->item('directory_trigger');
                $_d = isset($_GET[$_d]) ? trim($_GET[$_d], " \t\n\r\0\x0B/") : '';

                if ($_d !== '')
                {
                    $this->uri->filter_uri($_d);
                    $this->set_directory($_d);
                }
            }
            //获取控制器参数,如果控制器参数为空,则使用默认控制器参数
            $_c = trim($this->config->item('controller_trigger'));
            if ( ! empty($_GET[$_c]))
            {
                $this->uri->filter_uri($_GET[$_c]);
                $this->set_class($_GET[$_c]);
                //获取方法参数
                $_f = trim($this->config->item('function_trigger'));
                if ( ! empty($_GET[$_f]))
                {
                    $this->uri->filter_uri($_GET[$_f]);
                    $this->set_method($_GET[$_f]);
                }
                //将获取到的路由参数保存到rsegments变量中
                $this->uri->rsegments = array(
                    1 => $this->class,
                    2 => $this->method
                );
            }
            else
            {
                //如果query_string中没有控制器参数,则使用默认控制器
                $this->_set_default_controller();
            }

            return;
        }

        //非query_string路由,uri为空,使用默认控制器
        if ($this->uri->uri_string !== '')
        {
            $this->_parse_routes();
        }
        else
        {
            $this->_set_default_controller();
        }
    }

在这个函数中我们可以看到通过query_string参数路由的实现有以下几个步骤:
1.取出config配置中的directory_trigger,controller_trigger,function_trigger三个参数
2.取出query_string中参数名为以上三个配置项参数的值
3.将对应的query_string参数存储到对应的变量中
4.将路由参数以数组形式赋值给uri->rsegments变量中,路由完成

接下来我们再看一下_parse_toutes()函数,看其他情况是如何实现路由的

3._parse_routes()

这个函数实现的路由方式就是我们平常常见的ci路由地址,例如:http://dump.com/test/t_fun,会路由到test控制器的t_fun方法。
先看源码:

    protected function _parse_routes()
    {
        //将uri的分段参数用'/'拼接起来
        $uri = implode('/', $this->uri->segments);
        //请求类型
        $http_verb = isset($_SERVER['REQUEST_METHOD']) ?strtolower($_SERVER['REQUEST_METHOD']) : 'cli';

        //遍历routes数组,解析自定义路由格式
        foreach ($this->routes as $key => $val)
        {
            //检查路由格式中是否使用http请求方法
            if (is_array($val))
            {
                $val = array_change_key_case($val, CASE_LOWER);
                if (isset($val[$http_verb]))
                {
                    $val = $val[$http_verb];
                }
                else
                {
                    continue;
                }
            }

            //将自定义路由格式的:any和:num替换成相应的正则表达式
            $key = str_replace(array(':any', ':num'), array('[^/]+', '[0-9]+'), $key);
            //正则表达式匹配uri
            if (preg_match('#^'.$key.'$#', $uri, $matches))
            {
                //若这个路由格式的值是数组且函数可以调用,则回调指定函数,由指定函数返回uri
                if ( ! is_string($val) && is_callable($val))
                {
                    array_shift($matches);
                    $val = call_user_func_array($val, $matches);
                }
                //使用正则替换指定参数
                elseif (strpos($val, '$') !== FALSE && strpos($key, '(') !== FALSE)
                {
                    $val = preg_replace('#^'.$key.'$#', $val, $uri);
                }
                //路由请求
                $this->_set_request(explode('/', $val));
                return;
            }
        }

        //根据URI组件解析出来的分段参数路由请求
        $this->_set_request(array_values($this->uri->segments));
    }

到这里我们可以回答第二个问题了,Router用routes配置的key匹配uri,用key对应的值指定处理方式,可以调用函数处理,也可以直接用正则替换,以此实现了自定义路由格式。
自定义路由格式最终还是要被转换成和URI中分段参数一样的格式,再由set_request函数处理
接下来我们看一下set_request函数的源码

4._set_request

set_request这个函数接收uri分段数组设置类和方法。
先看源码

    protected function _set_request($segments = array())
    {
        //效验uri分段数组是否合法
        $segments = $this->_validate_request($segments);
        //如果uri分段数组为空,则使用默认路由参数
        if (empty($segments))
        {
            $this->_set_default_controller();
            return;
        }
        //处理破折号,将破折号替换成下划线
        if ($this->translate_uri_dashes === TRUE)
        {
            $segments[0] = str_replace('-', '_', $segments[0]);
            if (isset($segments[1]))
            {
                $segments[1] = str_replace('-', '_', $segments[1]);
            }
        }
        //segments第一个参数,作为控制器参数
        $this->set_class($segments[0]);
        //第二个参数作为方法参数,默认为index
        if (isset($segments[1]))
        {
            $this->set_method($segments[1]);
        }
        else
        {
            $segments[1] = 'index';
        }
        //在rsegments头部插入一个空元素,将下标为0的元素删除,将路由参数保存到URI的rsegments变量中
        array_unshift($segments, NULL);
        unset($segments[0]);
        $this->uri->rsegments = $segments;
    }

到这里整个路由都实现了,segments第一个参数为控制器,第二个参数为方法
这里有几需要注意的地方:
rsegments的数组下标也是从1开始的,和URI的segments一样。

总结:

开头的两个问题,在阅读源码的过程中都已经进行了回答。
这里记录一个小tips:
empty($routing['controller']) OR $this->set_class($routing['controller']);这段代码,这种短路的写法和使用if语句的写法效果是一样的,不过我觉得这种写法更优雅一点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值