CodeIgniter框架源码笔记(6)——支持友好的URI地址:路由类Router.php

Router类:将URI映射到对应的控制器及方法
Router类大量代码处理的是自定义路由,该类要支撑以下几个功能点:
1、自定义路由规则
在 application/config/routes.php 文件中的 $route 的数组,利用它可以设置路由规则。 在路由规则中可以使用通配符或正则表达式。
使用通配符:$route['product/:num'] = 'catalog/product_lookup';
使用正则:$route['products/([a-z]+)/(\d+)'] = '$1/id_$2';
(:num) 匹配只含有数字的一段。 (:any) 匹配含有任意字符的一段。(除了 '/' 字符,因为它是段与段之间的分隔符)

通配符实际上是正则表达式的别名,:any 会被转换为 [^/]+ , :num 会被转换为 [0-9]+ 。
$key = str_replace(array(':any', ':num'), array('[^/]+', '[0-9]+'), $key);

2、支持回调函数
在路由规则中使用回调函数来处理逆向引用。 例如:
$route['products/([a-zA-Z]+)/edit/(\d+)'] = function ($product_type, $id)
{
    return 'catalog/product_edit/' . strtolower($product_type) . '/' . $id;
};


3、支持使用 HTTP 动词

在路由数组后面再加一个键,键名为 HTTP 动词。
可以使用标准的 HTTP 动词(GET、PUT、POST、DELETE、PATCH),也可以使用自定义的动词 (例如:PURGE),不区分大小写。
例如:
//发送 PUT 请求到 "products" 这个 URI 时,将会调用 Product::insert() 方法
$route['products']['put'] = 'product/insert';

//发送 DELETE 请求到第一段为 "products" ,第二段为数字这个 URL时,将会调用 Product::delete() 方法,并将数字作为第一个参数。
$route['products/(:num)']['DELETE'] = 'product/delete/$1';

这么牛B的功能是怎么实现的呢?先看一张Router工作的流程图:


CI在CodeIgniter.php中实例化路由时,就完成了解析,得到请求的控制器名及方法名了。

$RTR =& load_class('Router', 'core', isset($routing) ? $routing : NULL);
所以核心入口是 __construct()

一、构造函数

public function __construct($routing = NULL)
{
    //加载类内部的类
    $this->config =& load_class('Config', 'core');
    $this->uri =& load_class('URI', 'core');

    //确认是否开启querystirng模式,如果这个模式开启,那就用index.php?c=mall&a=list这样去访问控制器和方法了
    $this->enable_query_strings = ( ! is_cli() && $this->config->item('enable_query_strings') === TRUE);

    // If a directory override is configured, it has to be set before any dynamic routing logic
    //如果在index.php里指定控制器目录,那么在动态路由之前都将这个设置作为控制器的目录
    //通俗的说就是路由器在找控制器和方法时,会在“contrlloer/设置的目录/”下找
    //而且这个设置会覆盖URI(三段)的目录
    is_array($routing) && isset($routing['directory']) && $this->set_directory($routing['directory']);
    
    //核心:解析URI到$this->directory、$this->class、$this->method
    $this->_set_routing();

    // Set any routing overrides that may exist in the main index file
    //如果在index.php中设置了控制器和方法,则覆盖
    //比如服务器维护时,设置一个方法用来显示“维护中”的静态页面,就可以让任何URI的请求都进入到该个方法中显示静态页面
    //我在想:应该把上面的$this->_set_routing();放到这个else块中就完美了
    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()

protected function _set_routing()
{
    // Load the routes.php file. It would be great if we could
    // skip this for enable_query_strings = TRUE, but then
    // default_controller would be empty ...
    //加载路由配置文件routes.php
    if (file_exists(APPPATH.'config/routes.php'))
    {
        include(APPPATH.'config/routes.php');
    }
    //如果有环境对应的配置文件,则加载并覆盖原配置文件routes.php
    if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/routes.php'))
    {
        include(APPPATH.'config/'.ENVIRONMENT.'/routes.php');
    }

    // Validate & get reserved routes
    //读取默认控制器设置$route['default_controller']
    //读取$route['translate_uri_dashes']。如果设置为TRUE,则可将URI中的破折号-转换成类名的下划线_
    //如my-controller/index    -> my_controller/index
    //读取所有自定义路由策略赋值给$this->routes
    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;
    }

    // Are query strings enabled in the config file? Normally CI doesn't utilize query strings
    // since URI segments are more search-engine friendly, but they can optionally be used.
    // If this feature is enabled, we will gather the directory/class/method a little differently
    //在querystring模式下获取directory/class/method
    //index.php?d=admin&c=mall&m=list
    //$config['controller_trigger'] = 'c';//控制器变量
    //$config['function_trigger'] = 'm';//方法变量
    //$config['directory_trigger'] = 'd';//目录变量
    if ($this->enable_query_strings)
    {
        // If the directory is set at this time, it means an override exists, so skip the checks
        //获取$this->directory。配置文件中的'directory_trigger'代表在$_GET中用什么变量名作为传递directory的键值
        //同样的还有设置控制器的传递参数键名controller_trigger,方法的传递参数键名function_trigger
        if ( ! isset($this->directory))
        {
            $_d = $this->config->item('directory_trigger');
            $_d = isset($_GET[$_d]) ? trim($_GET[$_d], " \t\n\r\0\x0B/") : '';

            if ($_d !== '')
            {
                //filter_uri是验证uri的组成字符是否在白名单(配置文件中permitted_uri_chars设置)中
                $this->uri->filter_uri($_d);
                $this->set_directory($_d);
            }
        }
        //获取控制器和方法,并设置$this->uri->rsegments
        $_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]);
            }

            $this->uri->rsegments = array(
                1 => $this->class,
                2 => $this->method
            );
        }
        else
        {
            //方法没有可以允许,如果控制器都没有,就调用默认控制器和方法代替了
            $this->_set_default_controller();
        }

        // Routing rules don't apply to query strings and we don't need to detect
        // directories, so we're done here
        return;
    }

    // Is there anything to parse?
    // 非querystring模式的程序可以走到这里
    if ($this->uri->uri_string !== '')
    {
        //解析自定义路由规则,并调用_set_request函数设置目录、控制器、方法
        $this->_parse_routes();
    }
    else
    {
        //uri_string为空,一般情况下就是域名后面没有任何字符,调用默认控制器
        $this->_set_default_controller();
    }
}


三、自定义路由解析函数

protected function _parse_routes()
{
    // Turn the segment array into a URI string
    //先将uri对像中的segments数组还原成uri路径。
    $uri = implode('/', $this->uri->segments);
    
    // Get HTTP verb
    //获取http请求动作。
    $http_verb = isset($_SERVER['REQUEST_METHOD']) ? strtolower($_SERVER['REQUEST_METHOD']) : 'cli';
    
    // Loop through the route array looking for wildcards
    //循环自定义路由规则,看是否能命中当前uri地址。
    foreach ($this->routes as $key => $val)
    {
        // Check if route format is using HTTP verbs
        //(功能3、支持使用 HTTP 动词) 处理HTTP 动词

        //可以在你的路由规则中使用 HTTP 动词(请求方法),就是在路由数组后面再加一个键,键名为 HTTP 动词。
        //标准的 HTTP 动词(GET、PUT、POST、DELETE、PATCH)
        //比如定义了:$route['admin/pages']['get'] = 'admin/pages/view/about';
        //那么当命中这条时:key:'admin/pages', val:array ('get' => 'admin/pages/view/about', )
        if (is_array($val))
        {
            $val = array_change_key_case($val, CASE_LOWER);
            if (isset($val[$http_verb]))
            {
                $val = $val[$http_verb];
            }
            else
            {
                continue;
            }
        }

        // Convert wildcards to RegEx
        //把:any和:num转成正则
        $key = str_replace(array(':any', ':num'), array('[^/]+', '[0-9]+'), $key);

        // Does the RegEx match?
        匹配路径信息
        if (preg_match('#^'.$key.'$#', $uri, $matches))
        {
            // Are we using callbacks to process back-references?
            //(功能2、支持回调函数) 处理回调函数这种使用方法
            if ( ! is_string($val) && is_callable($val))
            {
                // Remove the original string from the matches array.
                //matches数组的第一个元素是能匹配上的完整字符串,所以先要把这个去掉,剩下的就是匹配上的括号中间表达式
                array_shift($matches);

                // Execute the callback using the values in matches as its parameters.
                $val = call_user_func_array($val, $matches);
            }
            // Are we using the default routing method for back-references?

            //(功能1、自定义路由规则) 处理通常的自定义路由
            elseif (strpos($val, '$') !== FALSE && strpos($key, '(') !== FALSE)
            {
                //最核心的就是preg_replace这里了,不得不佩服正则函数的强大
                $val = preg_replace('#^'.$key.'$#', $val, $uri);
            }
            //调用_set_request设置$this->directory、$this->class、$this->method
            //参数是地址经过路由解析后再用'/'分割的数组
            $this->_set_request(explode('/', $val));
            return;
        }
    }
    // If we got this far it means we didn't encounter a
    // matching route so we'll set the site default route
    //如果程序执行到这里,说明没有匹配任何路由规则
    //调用_set_request设置$this->directory、$this->class、$this->method
    $this->_set_request(array_values($this->uri->segments));
}

四、设置目录、控制器和方法名

protected function _set_request($segments = array())
{
    //从$segments中提取Directory信息,设置$this->directory
    $segments = $this->_validate_request($segments);
    // If we don't have any segments left - try the default controller;
    // WARNING: Directories get shifted out of the segments array!
    //如果$segments在目录被提取走后,没有剩下任何东西,那么就用默认路由
    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]);
        }
    }
    //设置控制器类
    $this->set_class($segments[0]);
    
    if (isset($segments[1]))
    {
        //设置控制器类方法
        $this->set_method($segments[1]);
    }
    else
    {
        //如果不存在方法片段,则默认方法名为index
        $segments[1] = 'index';
    }
    //将整个数组元素往后推一格,保持和没有shift掉目录时的数组原素存放序列一致,
    //如array ( 0 => 'news', 1 => 'view', 2 => 'crm', )经过这两行后变成array ( 1 => 'news', 2 => 'view', 3 => 'crm', )
    //不过要是多级目录的话,这样推有什么用呢?
    array_unshift($segments, NULL);
    unset($segments[0]);
    
    //RTR->uri->rsegments用来存放路由转换后的片段,不含目录
    $this->uri->rsegments = $segments;
}

五、从uri片段中抽取目录

protected function _validate_request($segments)
{
    $c = count($segments);
    $directory_override = isset($this->directory);
    
    // Loop through our segments and return as soon as a controller
    // is found or when such a directory doesn't exist
    //支持多级目录
    while ($c-- > 0)
    {
        $test = $this->directory
            .ucfirst($this->translate_uri_dashes === TRUE ? str_replace('-', '_', $segments[0]) : $segments[0]);
        
        if ( ! file_exists(APPPATH.'controllers/'.$test.'.php')
            && $directory_override === FALSE
            && is_dir(APPPATH.'controllers/'.$this->directory.$segments[0])
        )
        {
            $this->set_directory(array_shift($segments), TRUE);
            continue;
        }

        return $segments;
    }

    // This means that all segments were actually directories
    return $segments;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于寻找信息分网站源码带数据,并且希望仿照58同城门户网站模板进行信息发布的需求,我推荐您使用PHP源码PHP是一种流行的服务器端脚本语言,适用于开发动态网站和应用程序。 首先,您可以通过搜索引擎或开源项目平台,如GitHub,找到一些开源的信息分网站源码。在找到合适的源码后,您可以下载并根据自己的需求进行修改。 源码应该包含网站的核心功能,如用户注册和登录、信息分和搜索、信息发布和管理、用户留言和反馈等。您可以根据58同城门户网站的模板进行设计和布局,以保证用户体验的一致性。 此外,找到带有数据的源码可能会有一定的难度,因为不同的网站会有不同的数据结构和内容。您可以考虑通过网站爬虫来抓取一部分数据,并将其导入到您的分网站中。当然,您还可以手动添加一些测试数据来完善您的网站功能。 在开发过程中,您可以使用一些流行的PHP框架,如Laravel或CodeIgniter,来加快开发速度和提高代码质量。这些框架提供了一系列的工具和函数,使您能够更轻松地完成网站的开发和维护。 总之,通过寻找适合的信息分网站源码并结合58同城门户网站的模板进行设计,然后使用PHP进行开发和调试,您将能够拥有一个功能齐全的信息发布网站。希望我的回答对您有所帮助,祝您开发顺利!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值