小凡 发表于 2011-8-13 03:40:05

学习STBlog的小笔记

看了STBlog的程序,明白些了CI的数据流处理过程,不过有些地方还是有疑问,先把这几天的笔记记录下来,希望大家也帮忙看看我的理解是不是正确,哪里有错误的地方,还有几个地方有些疑问,希望大家帮我解答,3Q。

首先说下URL,CI根据用户的URL,来判断具体执行什么操作。就STBlog来说,允许用户的URL可以是类似这样的几种形式:
1、http://localhost/stblog/index.php?c=home&m=index(config.php文件中的$config['enable_query_strings'] = TRUE;才允许这样的URL)
2、http://localhost/stblog/index.php/home/index或 http://localhost/stblog/index.php/home或 http://localhost/stblog/index.php/home/
3、http://localhost/stblog/index.php 或 http://localhost/stblog/index.php/
4、http://localhost/stblog/index.php/admin/$class/$method或 http://localhost/stblog/index.php/admin/$class/$method(这是后台的URL,控制器类在admin子目录里面)


URL中的index.php是整个程序的入口文件,在index.php文件中做了一些常量和基本路径的设置,然后引入CodeIgniter.php文件(这个文件的从头到尾就是整个系统的执行过程),在CodeIgniter.php文件中,开始引入一些系统需要的公共函数、兼容性函数和系统常量的定义文件。公共函数中的function &load_class($class, $instantiate = TRUE)非常重要,该函数的作用是:载入并实例化类,该函数有两个参数,第一个是类名,第二个可选,默认是TRUE,如果第二个参数是FALSE,那么该函数只是载入类,并不实例化。该函数先检查application/libraries下有没有该类的扩展类,如果有就载入并实例化扩展类,没有就载入该类,应该在system/libraries下有该类。还有该类定义了一个静态数组 static $objects = array(); 定义静态数组,作用是把已经实例化的类,放进来,下次在实例化某个类的时候,先检查该类是否在这个静态数组里,如果在则说明该类已经被实例化过了,那么直接返回,起到缓存作用吧。


接下来,第一个载入并实例化的是基准测试类(Benchmark),然后在此处做了两个标记,以便记录时间。
第二个载入并实例化Hook类,该Hook类的构造函数执行了该初始化函数_initialize(),该函数的主要作用就是载入application/config/hooks.php文件,并把该文件的$hook数组的地址赋给hook类的变量$hooks:$this->hooks =& $hook;并把变量$enabled赋值为TRUE,以便提供给该类的其他函数使用。
然后第三个载入并实例化的类是配置类(Config),在配置类的构造函数中调用了公共函数& get_config(),取得了config配置文件中$config数组并赋值给了该类的变量$config,以便该类的其他函数使用。
然后接着载入了URI类,在application/libraris中有该类的扩展类MY_URI类,所以实例化了MY_URI类,但是该类并没有进行什么操作,只是在URI类的构造函数中实例化了Config类,并赋值给$this->config;


接下来载入的是路由类Router,这个类是重点:这个类中定义的变量有:$class,代表URL中的控制器;变量 $method = 'index';代表URL中的控制器的方法;变量 $directory= ''; 代表子目录,用来判断控制器是否在子目录中,例如后台的控制器都是在admin/目录下的。所以访问后台的URL这个变量都会被赋值为admin;var $default_controller; 代表默认控制器,当URL中没有指定控制器时就执行这个默认的控制器和默认函数(index)。

然后执行路由类的构造函数,载入并实例化了 Config和URI类,这里说下:CodeIgniter.php中载入并实例化的Config、URI等类是给全局用的,而在具体的类中实例化是给该类用的。然后接下来构造函数执行了_set_routing()函数,下面看下这个函数:_set_routing(),这个函数调用了其他的一些函数,总之这些函数都执行完之后,就是为变量$class,$method,赋值,决定哪个控制器的方法来处理用户请求:上面谈到了,我们的URL,只可能是上面出现的那四种:


当用户的URL为第一种时(当然config配置文件总的$config['enable_query_strings'] = TRUE 才行),看这个函数:第一个if($this->config->item('enable_query_strings') === TRUE AND isset($_GET[$this->config->item('controller_trigger')]))判断,
此时第一种URL的情况下,该判断为TRUE,执行该if里面的语句:$this->set_class(trim($this->uri->_filter_uri($_GET[$this->config->item('controller_trigger')]))); 拆开看,$this->config->item('controller_trigger')=c,$_GET['C']就是取得URL中的控制器home,函数_filter_uri(),安全检查过滤非法字符。然后set_class();就是为该路由类的变量$class赋值,此时就把 home 赋值给变量$class了,下面的方法是一样的,所以该if()块执行完,$class,$method 分别被赋值成 home和method了,记住这两个变量的值。第一种情况URL结束。


下面看下其他几种URL:那么当URL为下面几种时,这个if()判断返回的是FALSE,直接跳过这个if()往下看:
                @include(APPPATH.'config/routes'.EXT); //载入路由配置文件
                $this->routes = ( ! isset($route) OR ! is_array($route)) ? array() : $route;
                  //routes.php配置文件中的$route数组赋值给$this->routes 变量
                unset($route);

$this->default_controller = ( ! isset($this->routes['default_controller']) OR $this->routes['default_controller'] == '') ? FALSE : strtolower($this->routes['default_controller']); //路由配置文件中的默认控制器赋值给变量default_controller。接下来调用uri类中的函数:$this->uri->_fetch_uri_string(); 先说下该函数作用就是为URI类中的变量uri_string赋值:下面去看下这个函数:

function _fetch_uri_string() {
                if (strtoupper($this->config->item('uri_protocol')) == 'AUTO')
                {               
                     //$this->config->item('uri_protocol') 等于PATH_INFO,所以if()块里面的不看了
                }
                else
                {
                        $uri = strtoupper($this->config->item('uri_protocol')); //$uri=PATH_INFO

                        if ($uri == 'REQUEST_URI')
                        {
                                //这里也不会执行
                        }

                        $this->uri_string = (isset($_SERVER[$uri])) ? $_SERVER[$uri] : @getenv($uri); //getenv()获取环境变量
                        /*这里举个例子来说假如URL为 http://www.test.com/index.php/foo/bar.html?c=index&m=search

                        那么 $_SERVER['PATH_INFO'] = ‘/foo/bar.html’,而此时 $_SERVER['QUERY_STRING'] = 'c=index&m=search';
                        所以此处的$this->uri_string 应该为 /home/index 或 /home (第二种URL形式),或是 ''或'/'(第三种形式),再就是/admin/class/method 或 /admin/class (第四种形式了)
                        */
                }
               
                if ($this->uri_string == '/')//这里如果 $this->uri_string == '/' ,则从新给它赋值为空 ''
                {
                        $this->uri_string = '';
                }
        }
所以程序执行到此处时,$this->uri_string 只有这几种形式吧:/home/index 或 /home 或 '' 或 /admin/class/method或 /admin/class 应该还有 但我只想出这几种

小凡 发表于 2011-8-13 03:56:14

然后现在回到 Router类中继续往下看:
if ($this->uri->uri_string == '') //假如此处为TRUE
                {                                 
                        if ($this->default_controller === FALSE)
                        {
                                上面的程序中以定义默认控制器为 home 所以此处肯定不执行错误提示
                        }
                       
                        if (strpos($this->default_controller, '/') !== FALSE)
                        {
                                // 此处判断默认的控制器是否在子目录里面显然不是,跳过这个if,不过自己还是最好看看里面的程序
                        }
                        else
                        {
                                $this->set_class($this->default_controller);//给变量$class 赋值
                                $this->set_method('index');         //给变量 $method 赋值
//到这里为止控制器和方法已经被赋值了,但后面又有如下两个函数,我看了一下他们的作用,但是不知道写在这里的作用是什么,是为了给 数组 $segments 和 数组 $rsegments 赋值么,然后给其他函数用(又是哪个函数用了呢?),这里留个疑问,程序还没看完。
                                $this->_set_request(array($this->default_controller, 'index'));
                        }
这个函数_set_request他的作用就是给URI类下的 rsegments 数组赋值为 URL中被请求的类名和方法名
例如 URL为 index.php/home/index 则数组rsegments为 array('home','index')                       
                        $this->uri->_reindex_segments();//该函数的作用是给 URI类中 数组 $segments 和 数组 $rsegments 的开头插入一个NULL元素,但是不知道为什么要插入一个NULL,有什么作用啊?又是一个疑问               
                        return; //然后返回,不再往下执行。
                }
如果当uri_string != '' 时 ,执行下面的程序:四个函数                $this->uri->_remove_url_suffix(); //移除URL的后缀 即URL类中的 $this->uri_string 字串的后缀,当然这里我们的URL没有后缀,在config配置文件中没有添加默认后缀                $this->uri->_explode_segments(); //给URI变量segments[]数组赋值。然后来看下RUI中的这个函数_explode_segments():function _explode_segments()   //此时 $this->uri_string 应该为 /home/index 或 /admin/class/method        {                                            foreach(explode("/", preg_replace("|/*(.+?)/*$|", "\\1", $this->uri_string)) as $val)                {   //此处的正则是为了去掉 /home/index 或 /admin/class/method 中的第一个/,之前在论坛提问过这里,感谢jeongee大哥为我解答,3Q                                        $val = trim($this->_filter_uri($val));// 过滤片段用于安全                        if ($val != '')                        {                                $this->segments[] = $val; //把index.php后面的段(即 控制器名和方法名)放入该数组中                        }                }        }
$this->_parse_routes();        //经过这个函数,应该已经给 控制器和方法 赋值了,下面具体看下这个方法吧:

function _parse_routes()
        {       
                if (count($this->routes) == 1)//这里判断$routes数组的元素个数是否为1,显然这里不为1,但是我不明白为什么要判断这个,有什么作用,这里又是一个疑问??
                {
                        $this->_set_request($this->uri->segments);
                        return;
                }
                //经过上面这个函数_explode_segments()之后,数组segment中存放的是安全的字符,home,index 或是 admin classmethod ,现在把他们组合成字符串
                $uri = implode('/', $this->uri->segments); // $uri=home/index 或 admin/class/method并且是安全的字符串
                // 逐字匹配,看看路由配置数组中是否有$uri对应的值
                if (isset($this->routes[$uri])) //$this->routes[$uri]=$routes[$uri] 是否有类似这种的 $routes="home/index"
                {
                        $this->_set_request(explode('/', $this->routes[$uri]));        //这里是调用该函数给 $class 和 $method 赋值,先继续往下看,一会在看看这个函数吧        (函数在下面)
                        return; //这里赋值完之后 returne 退出 后面的程序不再执行。
                }       
                foreach ($this->routes as $key => $val)//此循环是为了判断$uri,符合路由配置规则中的那项,然后将对应的值用/分解为数组,作为这个_set_request函数的参数
                {                                                                       
                        $key = str_replace(':any', '.+', str_replace(':num', '+', $key)); //:num 替换为 +将 :any 替换为 .+
                        if (preg_match('#^'.$key.'$#', $uri))
                        {                       
                                // Do we have a back-reference?
                                if (strpos($val, '$') !== FALSE AND strpos($key, '(') !== FALSE)
                                {
                                        $val = preg_replace('#^'.$key.'$#', $val, $uri);
                                }                       
                                $this->_set_request(explode('/', $val));//此处又调用了那个函数_set_request               
                                return;
                        }
                }               
                $this->_set_request($this->uri->segments);
        }

现在来看下这个函数,记住传过来的参数可能是array(0=>'home')或array(0=>'home',1=>'index') 或是 array(0=>'admin',1=>'XXX',2=>'xxx')或 array(0=>'admin',1=>'XXX')下面看函数:


function _set_request($segments = array())
        {
                $segments = $this->_validate_request($segments);
                //先说下该 _validate_request 的作用:就是判断 无论 控制器类 是否是在子目录中都返回 该控制器名称,并给变量$directory 赋值 下面在仔细看该函数。
                if (count($segments) == 0)//显然不会执行
                {
                        return;
                }                                       
                $this->set_class($segments); //设置控制器类名,但是经过上面那个函数,此时的segments数组可能已经变化了。一会在看那个函数(只有控制器在子目录的时候变化)
               
                if (isset($segments)) //判断默认方法是否存在
                {
                        //判断默认路由配置文件下(app/cnf/routes.php)的脚手架索引scaffolding_trigger的配置,不明白这有什么作用,这里也是个疑问??
                        if ($this->routes['scaffolding_trigger'] == $segments AND $segments != '_ci_scaffolding')
                        {
                                $this->scaffolding_request = TRUE;
                                unset($this->routes['scaffolding_trigger']);
                        }
                        else
                        {
                                $this->set_method($segments); // 设置URL中被请求的方法 给$method 赋值
                        }
                }
                else
                {
                        $segments = 'index'; //如果 $segments 不存在,即第二、四种URL的 后两个样子
                }               
                $this->uri->rsegments = $segments; //给URI类下的 rsegments 数组赋值为 $rsegments
        }

此时也设置了控制器类和方法,然后看下这个函数中调用的这个函数_validate_request传过来的参数和上面一样:array(0=>'home')或array(0=>'home',1=>'index') 或是 array(0=>'admin',1=>'XXX',2=>'xxx')或 array(0=>'admin',1=>'XXX')


    function _set_request($segments = array())
        {
                $segments = $this->_validate_request($segments);
                //先说下该 _validate_request 的作用:就是判断 无论 控制器类 是否是在子目录中都返回 该控制器名称,并给变量$directory 赋值
                if (count($segments) == 0)//显然不会执行
                {
                        return;
                }
                                               
                $this->set_class($segments); //设置控制器类名,但是经过上面那个函数,此时的segments数组可能已经变化了。一会在看那个函数(只有控制器在子目录的时候变化)
               
                if (isset($segments)) //判断默认方法是否存在
                {
                        //判断默认路由配置文件下(app/cnf/routes.php)的脚手架索引scaffolding_trigger的配置,不明白这有什么作用,这里也是个疑问??
                        if ($this->routes['scaffolding_trigger'] == $segments AND $segments != '_ci_scaffolding')
                        {
                                $this->scaffolding_request = TRUE;
                                unset($this->routes['scaffolding_trigger']);
                        }
                        else
                        {
                                $this->set_method($segments); // 设置URL中被请求的方法 给$method 赋值
                        }
                }
                else
                {
                        $segments = 'index'; //如果 $segments 不存在,即第二、四种URL的 后两个样子
                }
               
               
                $this->uri->rsegments = $segments; //给URI类下的 rsegments 数组赋值为 $rsegments
        }
此时也设置了控制器类和方法,然后看下这个函数中调用的这个函数_validate_request
传过来的参数和上面一样:array(0=>'home')或array(0=>'home',1=>'index') 或是 array(0=>'admin',1=>'XXX',2=>'xxx')或 array(0=>'admin',1=>'XXX')

function _validate_request($segments)
        {               
                if (file_exists(APPPATH.'controllers/'.$segments.EXT)) //判断application/controllers/下是否有 路由类 $segments
                {
                        return $segments; //如果有说明控制器类没有在子目录里面
                }       
                if (is_dir(APPPATH.'controllers/'.$segments))//判断数组APPPATH.'controllers/'.$segments是否为路径
                {                                       
                        $this->set_directory($segments); //此处给变量$directory赋值 为:$segments."/"
                        $segments = array_slice($segments, 1); //array_slice() 函数在数组中根据条件取出一段值,并返回。例:array_slice(array('aa','ss'), 1)为ss
//那么此处取出 (/目录名/类名)中的类名称,并从新赋值给 $segments 数组
//此处有个疑问:为什么只把类名赋给数组$segments了呢?方法名呢怎么不赋值过去啊。要是传过来的参数为:array('admin','post','manage')
//那么此时不是只把post赋值给$segments了么,方法manage怎么办?不能总是用默认方法吧。是不是我哪里没有理解到或是程序哪里没看明白呢??求解!!!                       
                        if (count($segments) > 0) //此处如果小于0,说明url中可能没有控制器名和方法名,所以此种情况 调用默认控制器 默认方法名
                        {
                               
                                if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments.EXT))
                                {
                                        show_404($this->fetch_directory().$segments);
                                }
                        }
                        else
                        {
                                $this->set_class($this->default_controller);
                                $this->set_method('index');                       
                                if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.EXT))
                                {
                                        $this->directory = '';
                                        return array();
                                }
                       
                        }

                        return $segments;
                }

                // 如果程序运行到这里依然没有找到默认路由类 方法 则返回404错误
                show_404($segments);
        }




小凡 发表于 2011-8-13 04:00:41

$this->uri->_reindex_segments();//此函数在数组开头插入一个NULL元素,不明白有什么作用 求解??这是路由类Router, _set_routing()函数最后一行代码。

好啦,经过这个过程,无论那种URL,类URI中的,$class,$method,$directory 三个变量已经被赋值了。下面返回到CodeIgnite.php文件中去吧。

回到CodeIgniter.php中继续载入并实例化了:输出类(Output)、输入类(Input)、语言类(Language),看手册把很清晰的。
然后接下来判断PHP的环境是不是PHP5,如果是PHP5则载入system/codeigniter/Base5.php文件,然后接着载入Controller.php类但是因为第二个参数为FALSE并没有实例化,因为后面的程序实例化任何一个控制器类(控制器类都是继承controller类的)都相当于实例化了controller类了.
然后接下来这条语句include(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().EXT),用到了上面一系列程序执行后赋值了的变量$directory和$class
这里引入了URL中请求的控制器类。然后接下来$class= $RTR->fetch_class(); $method = $RTR->fetch_method(); 把控制器和方法分别赋值给变量。接下来的那个if()判断用到了。

然后下面终于出现了$CI = new $class();实例化了控制器类。前台的控制器继承了ST_Controller类,后台的控制器继承了ST_Auth_Controller类,但最终都会继承Controller类
在控制器类的构造函数中,调用了父类controller类的构造函数。最终在Controller类的构造函数中,载入并实例化了装载器Loader类,并调用了Loader类中自动装载函数_ci_autoloader(),该函数自动载入配置文件autoload.php文件中的辅助函数、插件、类等。其中载入并实例化了数据库类,然后与数据库产生联系,经过一系列的处理,连接上数据库,并返回了数据库的实例化对象。

暂时笔记先记到这里,后面的思路还不清晰。。。。。。。。。。。。


希望大家帮我解决里面的疑问啊   3Q。。。。。。。。。。。。。。



1qaz2wsx 发表于 2012-7-3 22:03:12

真得不错,跟你学习////

★♂翼☆ 发表于 2012-12-2 09:57:34

看看 lz12年后就没下文了?

smallhe 发表于 2013-3-1 16:43:39

不错不错
页: [1]
查看完整版本: 学习STBlog的小笔记