codeIgniter框架基本结构分析【中级教程】【求精】
1、index.php入口文件:
1)指定CodeIgniter框架所在目录;
2)定义APPPATH常量,指示应用程序文件根目录;
3)载入codeigniter/CodeIgniter.php文件,启动框架。
2、codeigniter/CodeIgniter.php文件:主要完成初始化CodeIgniter框架和启动应用程序两项工作。
1)实例化CI_Benchmark,这个类用于标记应用程序执行消耗的时间;
2)载入应用程序的配置文件require(APPPATH.'config/config'.EXT);
3)实例化CI_Config,这个类用于将数组封装为可以操作的配置服务;
4)实例化CI_Router,这个类用于分析URL请求,确定要执行的控制器和动作;
5)实例化CI_Output,这个类提供输出内容的缓存和检查服务;
6)通过$OUT->_display_cache($CFG,$RTR)尝试输出缓存内容,如果成功,则结束程序运行;
7)判断控制器类定义文件是否存在。如果不存在则通过show_404()显示错误信息;
8)实例化CI_Input,这个类提供对$_GET、$_POST的访问手段,并封装了一些过滤方法;
9)实例化CI_URI,这个类提供对URL的分析、构造服务;
10)实例化CI_Language,这个类提供多语言字符串映射服务;
11)载入codeigniter/Base4.php或者codeigniter/Base5.php;
12)载入libraries/Controller.php;
13)载入控制器类定义文件;
14)实例化控制器类;
15)如果控制器使用了scaffolding功能,则调用控制器的
_ci_scaffolding()方法,否则调用控制器动作方法;
16)通过$OUT->_display();输出内容($OUT是CI_Output的实例)。
详细:
CI_Benchmark
这个类很简单,就是用microtime()函数记录时间点,并提供elapsed_time()方法来计算两个时间点之间消耗的时间。这个类功能不多,但是很实用。CodeIgniter中大部分类都是这种设计思想,值得称赞!
CI_Config
这个类其实是在内部维护了一个数组,用来记录应用程序的设置(类似Windows 注册表)。这种简单的封装可以强制应用程序按照固定的规范访问设置,同时又不将设置保存为全局变量,避免无意中遭到破坏或篡改。
php实例代码教程CI_Router
CI_Router功能很单一。CI_Router首先分析出应用程序当前使用的URL模式:PATHINFO或普通模式。接下来从URL地址中分析出控制器名字、动作名以及参数名和参数值。分析的结果保存为CI_Router对象实例的成员变量。这里比较有特点的是,CI_Router可以根据开发者在应用程序设置里面定义的模式来分析URL,而不是使用某种固定的模式。
CI_Output有两个主要功能:获得应用程序执行的所有输出内容和输出缓存服务。
应用程序执行的输出结果都会保存为CI_Output的成员变量。然后根据应用程序设置,CI_Output::_display()方法会调用CI_Output::_write_cache()方法将输出内容缓存起来。下一次当使用CI_Output::_display_cache()时如果缓存已经建立了,并且没有过期,则会直接输出缓存内容。
在CI_Output::_write_cache()中,是根据URL地址和URL参数来确定缓存ID的。因此即便是同一个控制器和动作,只要使用不同的URL参数,也会缓存不同的内容。
这个类的功能很简单,因此在许多动态页面是无法使用的。例如用户登录前和登录后,访问同一个控制器和动作并使用相同的URL参数,页面内容也有可能是不同的。这时,CI_Output的缓存就不能使用。因为从本质上来说,CI_Output 提供的缓存是在应用程序之外的,所以应用程序无法根据当前状态来决定是否缓存页面。当一个页面被缓存后,对该页面的访问实际上根本就不会执行应用程序代码,而是由CI_Output取出缓存内容直接就输出了。
CI_Input
CI_Input是类似过滤器,并且提供了对$_GET、$_POST的封装服务。例如用
CI_Input::post()方法来访问$_POST。由于多了这层封装,CI_Input可以在post()方法中对数据进行更多
的过滤。
这种封装从出发点上看,是很不错的。但是这也会造成一些问题。例如CI_Input 只有在调用post()方法时才能进行过滤。如果应用程序使用$_POST直接获取数据,那么实际上就绕过了安全屏障。如果应用程序使用了第三方库,那么这种风险更大,因为第三方库很可能会直接使用$_POST等全局变量。
因此有些开发者认为过滤应该是全局的,即在框架初始化时,就对所有输入数据进行过滤。但初始化时的全局过滤灵活性很差,要么全过滤,要么都不过滤,没法做到对个别数据的单独过滤。
CI_Input的另一个问题,就是没有处理magic_quotes。不管magic_quotes设置为什么,CI_Input都没有对数据进行相关的处理。这样一来,如果服务器的magic_quotes设置不同,那么应用程序得到的数据也是不一致的。后来查看数据库驱动的代码,发现CI_Input将对magic_quotes的处理放到了数据库驱动中。
这种设计是有很大缺陷的!如果应用程序取得数据后,并不是存入数据库(例如直接显示或存入文件),那么就必须自行判断magic_quotes的状态。这种判断不但烦琐,而且容易遗忘。所以框架有责任将所有数据库整理为一致的格式,要么是应用addslashes()转义过后的数据,要么是没有转义的数据。
奇怪的是CI_Input却对输入数据的字段名进行了magic_quotes检查,并应用了addslashes()。这是为了
让数据库字段名不会成为SQL注入攻击的根源。甚至,CI_Input还会将\n\r\n\r替换为\n。这种随意篡改原始数据的做法,非常不可取。
总之,我个人认为CodeIgniter在这部分的设计是很糟糕的。不过要改善也很简单,几行代码就可以了。然后修改一下数据库驱动。但是由于已经有许多采用CodeIgniter开发的应用程序,所以这样的升级改动,影响是非常大的。
CI_URI
由于CodeIgniter允许应用程序定义URL映射模式,所以需要专门的工具来生成URL地址。CI_URI就是完成这些工作的。
CI_Language
这个类可以载入不同的语言文件。然后应用程序就可以用CI_Language::line()方法取出某个项目的对应翻译。每个语言文件就是一个名值对数组。所以
CI_Language::line()以项目名做为键名,就可以查询到对应的翻译。
codeigniter/Base
codeigniter/Base4.php和codeigniter/Base5.php功能一样,只不过分别适用于PHP4和PHP5而已。其中定义了CI_Base类和一个非常重要的
get_instance()函数。
get_instance()函数返回一个CI_Base类在整个应用程序中的唯一实例。
这里有一个有趣的发现。Base4.php和Base5.php中的CI_Base和
get_instance()有这完全不同的实现。
在Base4.php(对应PHP4)中,CI_Base直接继承自CI_Loader。CI_Base实例化时,将自身的引用保存到了CI_Base::$load中。也就是说CI_Base实例的$load实际上指向自己。然后$load被复制到一个名为$OBJ的全局变量。
在PHP4版的get_instance()函数中,如果检查到$CI(这是CI_Base的实例,也就是控制器的实例)存在,就返回$CI,否则返回全局变量$OBJ->load。但由于在PHP4中,$OBJ->load实际上就是一个CI_Base的实例。所以。。。。
所以。。。。。。还是返回了一个CI_Base的实例。真搞不懂作者为什么这样写,简直要让人发疯。
不管怎么样,应用程序其他地方调用get_instance()都会获得一个CI_Base
的实例。
在Base5.php(对应PHP5)中,用一个singleton模式来解决了这个问题。因此CI_Base也不再需要从CI_Loader继承了。不过这也留下了隐患(CI_Loader 实例要什么时候获取呢?),所以在CI_Base的继承类Controller中,只好通过判断是否是运行PHP5来决定是不是要实例化一个CI_Loader。
真的很无语啊,这种设计虽然可以用,但是很糟糕。在PHP4种,CI_Loader的方法和成员变量暴露在了CI_Base中。如果应用程序不小心调用了这些方法或使用了这些成员变量。那么应用程序在PHP5中运行就会出错。
Controller
Controller类是所有控制器的基础类。Controller实例化时会将CI_Input、CI_Benchmark、CI_Config、CI_URI、CI_Output、CI_Language的实例复制到Controller实例的成员变量中。然后根据应用程序设置,自动载入文件。
但是这里作者显然没有处理好,所以不得不用`global$IN,$BM,$CFG,$URI, $LANG,$OUT;`这样的全局变量来传递几个重要的对象实例。
Controller本身并没提供model、helper的载入服务。这些都由CI_Loader来提供。但是,CI_Loader的各种载入服务,却又用get_instance()获取控制器的实例,然后调用Controller(控制器都是Controller的继承类哦)的
_ci_initialize()、_ci_init_database()等方法来做初始化。
神啊!救救我吧!这种错综复杂的关系,真的要人命啊!
Controller的$ci_is_loaded成员变量用于保存已经载入的对象实例。所以每次用Controller::_ci_load_model()载入模块后,都要将该模块登记到
$ci_is_loaded,以避免重复载入。
Controller里面大部分是一些初始化各种服务的方法,例如初始化数据库、Model的方法。还有就是用_ci_scaffolding()调用CodeIgniter的“脚手架”功能。
对Controller的设计,没什么好说的,一个字:烂!
CI_Loader
CI_Loader提供各种载入服务,例如载入Model、Helper、View等。但是(我
真的很痛恨“但是”这个词),CI_Loader却需要Controller来完成初始化。那么又是谁来调用CI_Loader呢?答案是Controller。
这种紧密的耦合,完全是没有必要的!
控制器开始执行
分析到这里,终于进入应用程序的代码了。应用程序控制器中,可以用
$this->load来载入各种服务,然后就可以调用这些载入的服务了。
虽然CodeIgniter在CI_Base、Controller和CI_Loader上设计很糟糕,但开发者如果不在乎这些,那么开发过程还是很愉快的。
下面我们再来看看CodeIgniter主要服务的特点。
数据库访问
与大部分框架不同,CodeIgniter的Model类没有提供数据库访问功能。所有数据库操作都是通过数据库驱动程序来进行的。
所有数据库驱动均继承自CI_DB类。等等,我怎么不到CI_DB类的定义呢?因为CI_DB类是在Controller中用`eval('class CI_DB extends
CI_DB_driver{}');`这行代码来定义的。定义这样一个空壳,估计是作者为以后扩充数据库驱动留下的伏笔。
CodeIgniter的数据库驱动,功能都很简单,和AdoDB Lite类似,但是缺乏AdoDB Lite那么多的扩展库。我个人认为反倒不如用AdoDB Lite来替换这部分。当然了,CodeIgniter目前已经有不少数据库驱动了,所以替换成AdoDB Lite好处不多。
CodeIgniter也提供了一个ActiveRecord实现,不过这个ActiveRecord可没有一点半点的“ORM”能力。但是CodeIgniter的ActiveRecord不需要为每一个数据表都构造一个实例。通常一个实例就可以处理多个数据表的操作。例如`$query=$this->db->get('mytable');`和`$query=
$this->db->get('mytable2');`就可以分别取得mytable和mytable2的数据。
说实话,作者可能用错了名字。CodeIgniter中的“ActiveRecord”实际上是表数据入口模式——TableDataGateway。
CodeIgniter中的ActiveRecord基本上只是一个对数据表进行CRUD操作的公共接口。没有提供RoR、Ca
kePHP、FleaPHP等框架具有的数据表关联自动处理能力。和自己写SQL相比,没什么优势。唯一的好处就是作者所说的可以让ActiveRecord来生成这些简单的SQL语句,而不用自己写,提高应用程序在不同数据库之间移植的能力。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论