TL;DR
CodeIgniter3是一个相当轻量、简便的并且上手难度低的PHP应用开发框架。在CodeIgnitor2时代曾经接触并开发了一些项目。目前最新版本是3.1.5
。优点个人认为有:
- 轻量
- 对MySQL查询有较为友好的代码编写方式
- 功能扩展较为简便
- 可以支持较低版本的PHP(5.4.8+)
- 可以不使用模板引擎
同时,个人也认为以下功能还可以有所变化:
- 内置日志功能不够强大
- 内置读写分离
- RESTful API开发支持
文章的剩余内容将会针对以上的各个方面详细说明。
优点
轻量
文件数量
首先通过cloc统计以下代码数量:
1 | → cloc . |
仅仅198个php文件,对于一个拥有完整的数据库查询支持,表单验证,Session支持,XSS/CSRF防护的框架来说,并不算臃肿。
文件数量一定程度上反映了层次划分的粒度,如果文件数量过多,带来的首先是源码阅读的难度和改造的难度。
基本业务功能实现
对于一个号称轻量的框架来说,实现一个数据库的CRUD操作的Hello World的代码量应该最能反映问题。
CI3实现这样的一个Controller,需要多少代码量呢?答案因人而异。但是步骤上却是相当简单的。考虑如下数据表:
1 | CREATE TABLE `ci3_test1` ( |
解压缩CI3之后,在application
中的config
目录下的database.php
中配置好数据库配置:
1 | $db['default'] = array( |
在application
中的config
目录下的routes.php
中配置好路由规则:
1 | $route['test1/(\S+)']['POST'] = '/test1/create/$1'; |
只需要编写少许几行代码即可完成上述操作。
1 | class Test1 extends CI_Controller { |
如果在Model层做一些封装,相信对于这类基本的CRUD操作的编写会更加的便捷。
友好的MySQL查询代码编写方式
日常开发中,个人更倾向于使用纯SQL进行数据库的查询工作,但是纯SQL的可读性不一定比精心设计的查询工具类更好,同时实用查询记录构造工具,可以规避一些错误的SQL编写方式(如不转义用户输入,直接拼接参数到SQL语句之中)。
如手册中的例子:
1 | $this->db->select('*')->from('my_table') |
CI3的查询构造器在调用get()
方法前可以声明各个查询条件的组合。通过链式方法,让代码拥有的了一定的可读性。
CI3数据库查询过程简述(PDO)
虽然在上述例子中使用的是mysqli
作为示例,但是PDO在工作中更为常用,简要的描述一下CI3使用PDO作为driver进行一次查询过程的流程。
- CI3中,在
database.php
中配置了数据库之后,通过load
对象中的database()
方法加载数据库对象。 database()
方法实际上会将数据库配置传入DB()
这一方法,如果传入参数为空,则会查找应用目录下的database.php
配置文件并引入。如果配置了DSN或者配置组的名字,则会解析或者查找配置文件中是否存在对应的配置组。所以,假设要编写一个新的DB Driver,需要一些特殊的配置,可以在项目的database.php
配置中增加一个特定的配置组,增加自己独有的字段。- 根据配置的
dbdriver
字段,到框架目录下查找是否存在对应的Driver,之后实例化并连接。由于PDO可以支持多种数据库,所以对于PDO来说,还会根据DSN查找对应的数据库的Driver类型(即在subdrivers
目录下找到对应的类,mysql则是CI_DB_pdo_mysql_driver
)完成实例化。 - 通过
CI_DB_query_builder
的各个方法(select()
/where()
)构造出SQL语句之后,通过类中的get()/insert()/update()/replace()/delete()
等方法对数据库进行操作,实际上都是交由query()
方法对对应的SQL语句进行执行。 query()
方法会通过simple_query()
这一个方法执行SQL语句,simple_query()
这一方法首先会检测是否已经初始化数据库连接对象,即成员变量conn_id
,如果没有则通过initialize()
方法进行连接。初始化完成后,执行上就需要各个DB Driver实现一个_execute
方法了。- PDO的driver实现的
_execute
方法则是直接调用内部成员属性conn_id
的query()
方法,即我们熟知的PHP MySQL PDO的query()
方法,这一方法会返回一个 PDOStatement 对象。SQL语句的执行结果会存储到result_id
成员变量之中。 - 针对不同的数据库,会对加载不同的结果集Driver,即我们调用
get()/get_where()
等等操作之后返回的对象,我们通过这一个Driver对象,调用result()/result_array()/row()/unbufferred_row()
等方法得到期望查询的结果。
多说一点,result()/result_array()/result_object()/custom_result_object()
等方法会把所有匹配的记录都读出,存储到当前结果对象的一个成员变量数组之中,在大结果集的情况下,可能会带来内存的大量使用,这里也是手册中提及过的,同样的使用row()/row_array()/row_object()/first_row()/last_row()/next_row()/previous_row()
等方法也会调用上述方法之一,所以调用了这些方法,实际上也会触发大量的内存使用的情况。而unbufferred_row()
这个方法实际上是通过逐个获取PDOStatement
对象的每一个结果数据,在内存占用上会有较好的表现。编写实例测试:
数据库中存在2条记录。
Controller:
1 | class Test1 extends CI_Controller { |
routes.php
1 | $route['test1/fetch_one']['GET'] = '/test1/fetch_one'; |
/test1/fetch_one
1 | fetch_one:2104664 |
/test1/fetch_all
1 | fetch_all:2106256 |
显然fetch_all
操作占用的内存2106256 > 2104920。
便捷的扩展功能的方式
application/libraries
目录中编写一个同名类即可替换掉系统提供的非数据库类库。通过get_instance()
方法获得CI超级对象之后可以在扩展的类库中获得原有类库的支持。
同样的,对内置的部分类库进行扩展也不一定需要在system目录下修改。
譬如希望对Session
的Driver类进行扩展,Session在加载Driver类时会先在APPPATH
目录的libraries/Session/drivers/
下寻找声明的Driver文件。
Hook机制的提供,可以在一个请求的几个阶段上对请求进行干预。比如身份验证可以在自定义的Controller基类中完成,也可以通过钩子在pre_controller
阶段进行。
可以支持低版本的PHP
现在PHP已进入7时代,但是如果要在老环境上做开发,CI3是个好选择,可以支持5.4.8+
的版本。从代码上看,数组仍然使用了传统的array()
方法进行声明,显然是PHP 5.4之前的编写方法。
不强制使用模板引擎
没看错,这个也是我个人认为的优点。模板引擎的使用对于PHPer来说并没有太大意义,徒增了转换回PHP代码的成本,如果说会引起页面可读性降低,这个方面保留意见,对于PHPer来说,有什么会比PHP代码更容易读懂和控制呢?
当然Twig之类的新兴模板引擎自然有可取之处,但是作为PHPer,首选当然是直接编写模板。
可以有所变化的方面
日志功能
CI3的日志功能比较简单,简单带来的好处是易用,然而在定制化和输出丰富程度方面,就稍微欠缺一些。
CI3的日志只能设定保存位置($config['log_path']
)、扩展名($config['log_file_extension']
)、时间戳格式($config['log_date_format']
)、日志级别等配置。存储形式是文件。这里多提一句,CI3写入文件的方式很有意思,先上锁(flock($fp, LOCK_EX)
)再写入,最后解锁。由于使用了LOCK_EX
标记位,多个进程同时写文件时会阻塞住。
这个应该与CI轻量级的设计思想有关,好在CI设计成可以支持Composer autoload,可以考虑引入强大的 monolog 扩展日志功能。
内置数据库读写分离
小项目读写分离可能并没有显得那么重要,但是对于访问量级千万级别甚至更高,或者是有访问峰值的项目来说,单库读写意味着可能处在服务挂掉的边缘。项目一般读写比10比1的经验值也告诉我们,可以利用数据库(主要指MySQL)多从库的方式,对服务进行一定程度的性能扩展。
然而CI3并没有看到内置的读写分离功能,在query()
方法中倒是对操作通过is_write_type()
方法进行了判断,但是这里只是为检查是否返回查询结果缓存而服务的(写操作通过缓存返回结果不合理)。
可以考虑改造CI3框架实现这一部分功能,这样可以做到对业务透明。
如果不改造,也可以在Model的基类中实现一些方法,如基类中有获取DB对象的共用方法,可以在这里进行一些处理。
RESTful API开发支持
Laravel中的artisan
工具提供了很多实用的工具入口。其中一个我认为很实用的是RESTful api单指令生成。
1 | php artisan make:controller PhotoController --resource --model=Photo |
这一指令执行之后可以帮助生成从Controller到Model的大部分代码,对于简单对象的CRUD操作来说,这样的代码编写方式很是方便。
相比之下CI3的这类开发工具支持稍微逊色,但是考虑到两个框架出现时间的差别,也是可以接受的。
附上一个可以参考的CI3 RESTful API解决方案chriskacerguis/codeigniter-restserver
最后
CodeIgniter4目前已经放出alpha版本,通过命名空间组织代码,日志的种类也有所丰富,并且较为激进的只支持PHP7及以上版本。不过遗憾的是现在还是没有看到数据库读写分离,代码量也有所涨幅:
1 | → cloc . |
数据库Driver目前只有mysqli和postgre,只能期待后续的版本中能够有更强大的CodeIgniter了。