最近再给一个APP写API,同时还要写相应的后台管理网站。为了便于开发和代码组织与管理,我决定采用一个现有的框架。Codeigniter由于其轻量容易自定制的特点吸引了我,一路开发过来也有大半年时间了,写下一些自己在开发过程中的一些体会来与大家共勉。
开发工具
开发工具有好多,每个人的习惯也不同。Eclipse一直是我的最爱,那么怎么让Eclipse支持codeigniter呢?
首先这是个PHP项目,所以得让Eclipse支持PHP。假设你的计算机已经安装了PHP,那么最简单的做法就是在Eclipse的market space中搜索PDT并安装。具体操作:Help -> Eclipse Marketplace。在输入框中输入PDT,点击find,如图:
点击右下方的install,等待几分钟即可。
现在可以在Eclipse中写PHP了,不过对于codeigniter来说,经常用到的语句类似如下:
$this->load->model ( 'user_model' );
$this->input->post ('phone');
每次都要手动输入这么长的类似的语句对于追求高效的人来说显然是不可取的,所以得让Eclipse支持codeigniter特定的代码提示,包括Controller和Model。把如下代码添加到
system/core/ 目录下的Controller文件中CI_Controller类的构造函数之前,这其实就是进行
变量声明,Model文件也是同样操作。
/**
@var CI_Config
*/
var $config;
/**
@var CI_DB_active_record
*/
var $db;
/**
@var CI_Email
*/
var $email;
/**
@var CI_Form_validation
*/
var $form_validation;
/**
@var CI_Input
*/
var $input;
/**
@var CI_Loader
*/
var $load;
/**
@var CI_Router
*/
var $router;
/**
@var CI_Session
*/
var $session;
/**
@var CI_Table
*/
var $table;
/**
@var CI_Unit_test
*/
var $unit;
/**
@var CI_URI
*/
var $uri;
/**
@var CI_Pagination
*/
var $pagination;
现在效果如下:
只需输入$this 即可产生代码提示,效率提高不少。
自定义辅助函数和库
一般一个项目中会有许多相同的逻辑,会使用到相同的代码。如果每次都去复制粘贴不仅麻烦而且不利于后续修改,想到需要修改一个逻辑就要到每个文件里修改代码就觉得可怕(涉及到设计模式这一块了,值得我们学学)。所以你要善于利用那些自定义辅助函数(helpers)和自定义的库(libraries)。把相同的逻辑封装成函数或者类,每次遇到相同的逻辑之时只需调用现成的helper或者library即可,修改也只需要在helper或者library中一处修改,不需要到处找代码。
举一个我项目中的例子。API需要给APP发送的请求返回json格式数据,那么在PHP中的一种实现方法就是:
echo json_encode ( $data);//$data 是array类型的数据
但是如果$data中包含中文字符的话,输出的json数据会把中文变成Unicode编码,为了避免客户端需要再次处理数据,需要加入一个参数,即:
echo json_encode ( $data , JSON_UNESCAPED_UNICODE);//$data 是array类型的数据
这样就能输出原始的中文了。但是问题又来了,每次都要写这么长一段代码实在是麻烦,所以封装成一个辅助函数。在application/helpers 文件夹中新建一个文件render_helper.php。其中代码如下:
那么每次调用之时只需先在类中的构造函数载入该辅助函数,如下:
$this->load->helper ( 'render' ); // 载入打印json的自定义辅助函数
就可以在本类的所有成员函数中使用该方法:
echo_json ( $data);
这样省时省力(加上Eclipse的代码提示,你只需要输入echo,这个函数就可以出来了)。以后需要修改成编码后中文,或者转换成英文等等(奇怪的需求~)你都可以只修改一行代码,效率提高不少。
善用缓存
如果一个APP的活跃用户不是很多,那么传统的LAMP架构就可以应付了。但是随着用户的逐渐增多,要想提高并发量,最好还是加一个缓存。在memcached和redis中,我选择了redis,主要因为它数据类型更丰富(不仅支持key-value型的数据,同时还提供list,set,zset,hash等数据结构的存储),能帮你实现一部分逻辑(避免重复造轮子)。与Eclipse配置同理,首先得让计算机上的PHP能够和redis通讯(假设你的计算机已经安装了redis),那么首选的是phpredis扩展,这里介绍一下我在Debain服务器上的配置过程,执行的命令如下:
wget https://github.com/nicolasff/phpredis/archive/master.tar.gz #下载扩展
tar xvf master.tar.gz #解压目录
cd phpredis-master/ #进入解压后的目录
phpize
./configure --enable-redis
make && make install #安装
#但是报错-bash: phpize: command not found(在使用apt-get install php5安装php时,默认是没有安装phpize的,我们安装phpredis时,需要用到phpize,因此,需要先安装#phpize。我们通过安装php开发者工具来获取phpize。执行如下命令即可: apt-get install php5-dev)
ls /usr/lib/php5/20100525/#根据安装提示的文件,结果:curl.so gd.so redis.so
vim /etc/php5/apache2/php.ini #打开PHP配置文件
# Dynamic Extensions 后面添加extension=redis.so,因为上面命令结果显示有redis.so
/etc/init.d/apache2 restart #重启Apache服务器
现在PHP就能和redis通讯了,测试如下:
connect('127.0.0.1',6379);
$redis->auth('你的密码');//为了安全,要给redis设置密码
$redis->set('tom','hanks');
echo ' tom:'.$redis->get('tom'). '';// tom:hanks
echo 'will:'.$redis->get('will'); //will:
?>
好了,现在来到配置codeigniter使其能够使用redis的步骤了。Codeigniter3.0.0本身具有redis支持,但是它实现的功能过于单一,不能够满足我的业务需求,所以必须要修改。但是最好不要直接在/system/libraries/Cache/drivers/Cache_redis.php 中修改,因为以后codeigniter升级采用直接覆盖system文件夹的形式,会覆盖掉你的代码。所以最明智的做法还是自己创建一个库,具体步骤如下:
1、在application/config/redis.php 中加入配置信息:
2、在application/libraries中建立如下图的文件结构
Rediscli.php 代码如下:
CI = & get_instance ();
$this->valid_drivers = array (
'default'
);
}
}
Rediscli_default.php 代码从/system/libraries/Cache/drivers/Cache_redis.php 直接拷贝过来,唯一的不同是把类名从CI_Cache_redis改为Rediscli_default,这样就能按照官方的使用方法使用我们自己写的库。
3、现在可以在Rediscli_default.php中添加或者修改方法,具体操作取决于你自己的应用的需求。比如在我的应用中,需要保持一个一定长度的队列,所以在Rediscli_default类中添加如下两个方法:
/**
弹出链表头元素
@param unknown $key,链表名
*/
public function lpop($key) {
return $this->_redis->lPop ( $key );
}
/**
插入元素到表尾
@param unknown $key,链表名
@param unknown $value,待插入值
*/
public function rpush($key, $value) {
return $this->_redis->rPush ( $key, $value );
}
某个类需要调用该库时只需在其构造函数中添加如下代码:
$this->load->driver ( 'rediscli' ); // 加载redis自定义库
$this->rediscli->default->is_supported ();//判断是否支持redis并打开连接
则该类的每个函数就都可以使用Rediscli_default类中的任一方法,示例如下:
$this->rediscli->default->lpop('delnews');//弹出名为delnews的链表头元素
$this->rediscli->default->rpush('delnews',$nid);//该链表加入尾元素$nid
登陆逻辑的实现
对于我们的APP来说,每个请求都要判断用户是否已登陆,对于登陆的合法用户正常显示请求,对于未登录的用户提示“请先登录”。一般的web应用采用cookie-session机制,一般的session都是以文件形式保存在服务器上,考虑到文件访问慢于内存访问,我们可以配置codeigniter的session保存于redis中。我这里模仿这种机制:用户正常login后返回一个特定的id和特定的token(你可以自定义产生算法),服务器以id为key、token为value直接保存在redis中。以后的每次访问APP都必须带上用appsecret(预定义)加密后的参数id和token,服务器端校验成功即正常返回数据,否则提示“请先登录”。
那么是否每个类都需要写一遍检查登陆的代码呢?当然不能这么干,我们可以自定义一个基类,在该基类的构造函数中检查登陆状态(还可以在这里加载常用的辅助函数和库,比如上面的render_helper 和 rediscli)。然后其他的业务逻辑的类就继承该基类,并完成其自身逻辑即可。
具体操作就是在 application/core 中新建文件 MY_Controller.php,该文件对应MY_Controller类(继承CI_Controller类)。该类只需要一个构造函数完成上述功能即可。
然后其它逻辑类只需继承MY_Controller类就不必再次校验登陆状态,而只需完成自身逻辑。
总结
Codeigniter是我最喜欢的一个PHP框架(相比于thinkPHP,Zend Framework等等),主要在于其易安装,轻量,易自定制的优点。把握好这些优点,我们就能写出不逊于那些重量级框架写出来的应用,并且省时省力。
作为一个学生,在摸索学习的路上还有很多问题需要解决,本文的目的在于和大家分享,更重要的是大家有什么建议或者批评请一定不吝赐教哈