ci框架理解流程(转载)

入口文件 入口文件主要完成下列工作: 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 带来的好处就很少了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
大多数PHPer都想写出运行状态良好的应用程序,而且希望尽可能做得简单且不费事。这篇文章是有关 CodeIgniter 的(以下简称 CI),CI 是一个达成以上目标的框架。 如果你只是要达成一个最终的结果,而把中间所有的编码细节和复杂统统丢给一个框架CI 是你最好的朋友。 CI 有很多优点:免费,轻量级,容易安装,它能使你的编程生涯变得很轻松。 这一章我们会告诉你: CI 能为你做什么? 什么是“框架”?CI 为什么能被称为框架? “开源”商业模式。 CI 的某些不足(是的,它并不完美)。 1.1 CodeIgniter 能为你做什么? 如果你已经是一位 PHPer,开发过 PHP 应用,CodeIgniter 将会帮助你做得更好,更容易达成目标。CI 会减少你的代码量。你的脚本可读性也会更好,更容易升级。它会使你的网站结构更紧凑,代码更强健,如果没有很好地研究 CI 的源代码,你可能还无法察觉到它的强健。 对大多数兄弟来讲,你可能已经花了不少时间,系统地学习了 PHP、HTML 和 CSS,当然还有 MySQL 什么的,不过如果使用 CI,你只需要一些基本的 LAMP(WAMP)知识,你没有必要先成为一个专家才能使用 CI。你完全可以先借助于 CI 或别的什么框架软件,成为一个有生产力的 PHP 程序员,拿着高薪然后优雅地进一步学习 PHP 的中高级知识,直至成为一位真正的 PHP 骨灰级的人物。 下述情形,你最好不要使用CI: 你没有一点PHP和HTML的基本知识。 你想用最少的代码,快速简便的写一个基本的内容管理系统(CMS)(可以看看Expression Engine)。 你想写一个只有几个标准特性的简单的网站。 1.1.1 节省时间 CI 学习周期短,见效快。让我们试着评估一下相关的要素: CI 如何减少代码量? 你真的可以减少很多工作量:敲击键盘的次数减少了,代码错误减少了,你只需要很少的时间调试代码。代码量减少还意味着你只需要较少的空间来存放应用程序。 举两个例子(稍后它们会被进一步分析,因此不用担心如何了解它们的工作原理!) 想象你正在写一个 MySQL 数据库查询。可能的代码如下: 复制代码到剪贴板PHP 代码$connection = mysql_connect("localhost","fred","12345"); mysql_select_db("websites", $connection); $result = mysql_query ("SELECT * FROM sites", $connection); while ($row = mysql_fetch_array($result, MYSQL_NUM)) { foreach ($row as $attribute) print "{$attribute[1]} "; }现在看看 CI 如何处理同一个问题: 复制代码到剪贴板PHP 代码$this->load->database('websites'); $query = $this->db->get('sites'); foreach ($query->result() as $row) { print $row->url; }比较字符数:前者 336,后者 112。 第二个例子,现在让我们想象你正在用 HTML 写一个数据输入,你想要一个下拉输入。下拉中有三个选项。代码如下: 复制代码到剪贴板HTML 代码<select name="type"> <option value="1">www.this.com</option> <option value="2">www.that.com</option> <option value="3" selected>www.theother.com</option> </select>CI 的写法和前例一样,因为它把相关内容放入一个数组,更容易由 PHP 进行处理: 复制代码到剪贴板PHP 代码$urlarray = array( '1' => 'www.this.com', '2' => 'www.that.com', '3' => 'www.theother.com', ); $variable .= form_dropdown('url', $urlarray, '3');在 HTML 中,你需要输入 154 个字符;在 CI 中,只需要 128 个字符。 1.1.2 使你的网站更健壮 你不需要写很多代码,是因为 CI 提供了许多标准的功能,这些经过仔细推敲的框架内的代码,对安全性和输入进行了有效的校验和考虑。初学者往往没有足够的能力全兼顾功能和安全。(这也是中高级程序员与新手之间能力差异的一个方) 1.1.2.1 确保你的链接自动更新 设想你正在编写一个菜单页,有许多超链接可重定向到其他页。他们全部以传统的 HTML 格式编写: 复制代码到剪贴板HTML 代码<a href="http://www.mysite.com/index.php/start/hello/fred">say hello to Fred</a>后来,你决定转移网站到其他 URL。这意谓你必须仔细地去查找并修改代码中的每一处 URL,否则它们将无法正常工作。 CI 给你一个简单的函数,可以这样编写超链接: 复制代码到剪贴板PHP 代码echo anchor('start/hello/fred', 'Say hello to Fred');CI 推荐你把你的 URL 放入一个配置文件中供你的脚本读取。CI 的 anchor 函数会自动从配置文件中提取相关 URL。因此,当你修改一个 URL 时,你只需要修改配置文件中的对应链接,然后所有超链接将自动更新。 1.1.2.2 防止对数据库的攻击:对表单输入的数据进行校验和处理 数据输入可能引发许多问题。因为 HTML 和数据库的限制,数据中总包含特定的符号—举例来说,省略符号和引号—可能导致你的数据库遭到攻击,最终得到你无法预料的结果。 解决方案是在把这些数据存入数据库前对这些数据进行相关处理。这样做会浪费一些系统时间,增加一些额外编码。 CI 的表单辅助函数会自动地完成这些工作。因此,当你编写一个输入时: 复制代码到剪贴板PHP 代码echo form_input('username', 'johndoe');CI 也隐式地执行下列校验函数: 复制代码到剪贴板PHP 代码function form_prep($str = '') { if ($str === '') { return ''; } $temp = '__TEMP_AMPERSANDS__'; // Replace entities to temporary markers so that // htmlspecialchars won't mess them up $str = preg_replace("/&#(\d+);/", "$temp\\1;", $str); $str = preg_replace("/&(\w+);/", "$temp\\1;", $str); $str = htmlspecialchars($str); // In case htmlspecialchars misses these. $str = str_replace(array("'", '"'), array("'", """), $str); // Decode the temp markers back to entities $str = preg_replace("/$temp(\d+);/","&#\\1;",$str); $str = preg_replace("/$temp(\w+);/","&\\1;",$str); return $str; }上述函数捕获像“&”这样的特殊字符,以便在你的页提交时不会造成混乱。你应该知道,有些字符会引起问题。 并不是所有的用户都会中规中矩的输入符合要求的信息,你也不可能知道使用浏览器输入信息的是什么人,他们在想什么,做什么。你可以使用 CI 来防止输入不符合要求的信息。当然,你大可不必知道 CI 是如何在幕后为你做到这一切的,你只需要简单地输入如下代码: 复制代码到剪贴板PHP 代码echo form_input('username', 'johndoe');1.1.3 增强你的代码 CI 使你写代码更容易了。不像有些类库如 PEAR 等,集成比较困难,(有时候你会找不到支持 PEAR 的空间),CI 很容易集成,只要把它放入一个目录,它就能很好地工作。CI 所有代码的可读性好,也很健壮,推出前经过社区用户的认真测试,所以在你可以使用时,这些代码已经经历了很多考验。 让我们看两个例子。 1.1.3.1 发送 Email 和附件很简单 发送 Email 的功能实现起来比较复杂,但是,使用 CI 将使这件事变得很简单: 复制代码到剪贴板PHP 代码$this->load->library('email'); $this->email->from('[email protected]', 'Your Name'); $this->email->subject('Email Test'); $this->email->message('Testing the email class.'); $this->email->send();实现发送 Email 的功能中有一些不容易解决的技术问题:比如设置文本自动换行(取消设置的则可以保持长 URL 地址不被换行或截断)或发送附件。标准的 PHP 实现起来比较复杂,CI 简化了这些工作,它的 Email 类使得发送附件很简单: 复制代码到剪贴板PHP 代码$this->email->attach('/path/to/photo1.jpg');CI 把内部的复杂部分悄悄地完成了,举例来说,实现了列举近百种不同附件的 MIME 类型的功能。所以它知道你的相片 photo1.jpg 是一个“image/jpeg”MIME 类型。因此它在你附件的适当位置填写必要的限制符号,它处理文本的换行,让你轻松标记出不希望出现换行的文本块。 1.1.3.2 压缩用户要下载的文件以加快下载速度 为了加快下载速度,常见的做法是在下载之前压缩下载文件。你可能不知道如何处理。但 CI 可以方便地让你用 4 行代码完成此功能: 复制代码到剪贴板PHP 代码$name = 'mydata1.txt'; $data = 'the contents of my file..........'; $this->zip->add_data($name, $data); $this->zip->archive('c:/my_backup.zip');运行这些代码,你会在你的 C 盘根目录下找到一个压缩文件,解压后即为原始文件。 你网站的用户并不清楚你是如何简便实现这个功能的,但他们能体会到你的网站的下载速度很快,而你只用了数分种(而不是数小时)就实现了这个功能。 1.2 CodeIgniter 是什么?框架又是什么? 当发明计算机编程不久之后,便有人发现,这其中涉及到了太多的重复操作。之后,也许是 Ada Lovelace(人类历史上的首位程序员),又或许是 Alan Turning,决定将计算机程序模块化,从而使得片段程序代码可以重复使用。PHP 程序员们早已习惯了将需要重复使用的代码写在函数中,并将这些函数放在 include 文件里。 同样的,框架是为重用而发明的,存放在独立的文件中,用来简化重复操作的代码。 上例子中连接数据库和编写 HTML 表单元素的工作都可以调用相关的 CI 函数来进行简化。 它超越了这一点。有很多种方法实现同样的功能;大多数的框架会让你按照它实现的方法来做。他们选择了一种方式来解决问题,所以你也必须要遵循这种方式。如果方式得当,编程便会轻松许多,反之则会事倍功半。 好的框架设计能实现需要的功能,而且尽可能地不互相牵连。一个好框架为你做出各种功能的实现,并且给你提供一步一步的编程指导。 提到框架时,就不能不提到著名的框架:Ruby on Rails。 Rails 做得相当成功,因为它籍由最少量的编码,提供简便快速的网站开发。本质上,它是一个结构和一组工具,专为使用 Ruby 语言的用户开发,允许你快速建立 Ruby 系统原型。它不是 Ruby 语言中唯一的框架,但它一定是最有开发效率和最有名的。另一方,如果你已经花了很大功夫学习 PHP,那么从 Ruby 重新开始又要重头学起。 为 PHP 开发的框架有很多个(大约 40 个),CI 只是其中之一。其它的还包括 Zend Framework、Cake、Trax 等。下列网址可以找到一个针对十种框架的简明图表分析:http://www.phpit.net/article/ten-different-php-frameworks/。 如果你访问上述网址中相关产品的官方网站,你将会注意到,每个论坛都有一个共同的热点,就是到底哪一个框架是最好的?事实似乎是每个都有它的长处,而且又都有自己的弱点。我的评估标准是:我很忙;因此框架应该节省我的时间,从中选择一个后,就坚持使用下去,因此就有了这本介绍 CI 的书。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值