CodeIgniter 框架分析

入口文件 入口文件主要完成下列工做: 1)   指定 CodeIgniter 框架所在目录; 2)   定义 APPPATH 常量,指示应用程序文件根目录; 3)   载入 codeigniter/CodeIgniter.php 文件,启动框架。 codeigniter/CodeIgniter.php 文件 这个文件是 CodeIgniter 的基本文件,主要完成初始化 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 注册表)。这种简单的封装能够强制应用程序按照固定的规范访问设置,同时又不将设置保存为全局变量,避免无心中遭到破坏或篡改。 CI_Router CI_Router 功能很单一。CI_Router 首先分析出应用程序当前使用的 URL 模式:PATHINFO 或普通模式。接下来从 URL 地址中分析出控制器名字、动做名以及参数名和参数值。分析的结果保存为 CI_Router 对象实例的成员变量。 这里比较有特色的是,CI_Router 能够根据开发者在应用程序设置里面定义的模式来分析 URL,而不是使用某种固定的模式。 CI_Output 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、CakePHP、FleaPHP 等框架具备的数据表关联自动处理能力。和本身写 SQL 相比,没什么优点。惟一的好处就是做者所说的可让 ActiveRecord 来生成这些简单的 SQL 语句,而不用本身写,提升应用程序在不一样数据库之间移植的能力。 “脚手架”功能 CodeIgniter 中提供了基本的“脚手架”功能,能够用几行代码即实现一个对某个数据表进行 CRUD 的界面。这和 phpMyAdmin 中的数据浏览、编辑页面相似,固然功能要简单得多。 “脚手架”有什么实用价值,众说纷纭。但广泛认同的一点就是“脚手架”功能为处于开发初期的应用程序提供了管理数据的界面。开发者能够在后期替换掉“脚手架”的界面。 可是,CodeIgniter 也太简单了,就只有 CRUD 操做,还不如 phpMyAdmin 好用。 其余 CodeIgniter 还有许多其余的类和助手。这些类基本上都属于提供各类辅助服务的范畴。有些类很不错,像图片操做。但大部分类和助手实在太简单,缺少实用价值。像数据验证助手,只能作很基本的验证,在绝大多数应用程序里面都不能知足要求。 总结 咳——咳——,总结时间到了。 再次郑重申明:本文全部文字均为做者我的理解和感想。做者尽可能作到客观,但人非圣贤,不免参杂我的好恶在其中。因此若是你看到不爽的文字,请自动无视,谢谢合做!  CodeIgniter 是一个:简单不简洁、好用但可能不够用的工具。 几个步骤就可让你的应用程序跑起来,因此简单。由于简单,因此好用。但糟糕的设计增长了复杂度,简单的表面下是错综复杂的对象关系。由于过于简单,因此可能不够用。 若是你只是开发很简单的应用程序,那么 CodeIgniter 彻底能够知足你的需求。并且你也会得到愉快的体验。 但若是应用程序具备必定的复杂度,CodeIgniter 就可能起到副作用。由于 CodeIgniter 在几个主要类上的糟糕设计,你的应用程序最终也会受到牵连。并且 CodeIgniter 缺少许多必须的服务,例如访问控制、用户管理、自动化的数据表关联处理、复杂缓存等。 这些服务对于一个较为复杂的应用程序来讲都是必须的。若是用 CodeIgniter 做为应用程序框架,那么这些服务都须要本身实现。这时 CodeIgniter 带来的好处就不多了。
相关文章
相关标签/搜索