CI(CodeIgniter)框架入门教程——第五课 数据库类的使用

今天来介绍一下CI框架的数据库类,在CI框架内有一个非常强大的数据库类,一般来说,如果使用CI框架进行开发的话,我们都建议使用CI框架内置的数据库类进行数据库相关操作。CI框架提供的数据库类是一个中间抽象层,可以通过配置,适配多种数据库,同时它还支持传统架构以及 Active Record 架构。接下来,就开始今天的学习,今天我们以一个题库管理的功能为学习内容进行讲解。

首先,使用CI自带的数据库类不需要你自己编写代码链接数据库,但是需要你进行配置,这些配置信息统一放在application/config/database.php文件下,打开这个文件,可以看到如下这样一个二维数组:

// 数据库服务器地址

$db['default']['hostname'] = 'localhost';

// 数据库用户名

$db['default']['username'] = '';

// 数据库密码

$db['default']['password'] = '';

// 数据库名

$db['default']['database'] = '';

// 数据库服务器类型

$db['default']['dbdriver'] = 'mysql';

// 当运行Active Record查询时数据表的前缀

$db['default']['dbprefix'] = '';

// 是否为长连接

$db['default']['pconnect'] = TRUE;

// 是否显示错误信息

$db['default']['db_debug'] = TRUE;

// 是否开启数据库查询缓存

$db['default']['cache_on'] = FALSE;

// 数据库查询缓存目录所在的服务器绝对路径

$db['default']['cachedir'] = '';

// 与数据库通信时所使用的字符集

$db['default']['char_set'] = 'utf8';

// 与数据库通信时所使用的字符规则

$db['default']['dbcollat'] = 'utf8_general_ci';

// 替换默认的dbprefix表前缀,该项设置对于分布式应用是非常有用的,你可以在查询中使用由最终用户定制的表前缀

$db['default']['swap_pre'] = '';

// 当数据库类库被载入的时候是否需要自动连接数据库

$db['default']['autoinit'] = TRUE;

// 是否强制使用 "Strict Mode" 连接

$db['default']['stricton'] = FALSE;

在上面的代码中,我已经针对每个变量都给出了解释,在这里再针对几个比较特殊的配置信息进行进一步说明:

(1)数据库服务器类型:这个配置项取决于服务器端的数据库类型,比如你用的是MySQL,那这里就写mysql,如果你用的是PostgreSQL,那这里就写postgres,其他的数据库可以参考CI框架官方文档,在这节课开头就说了,CI的数据库类是一个中间抽象层,所以你可以根据自己使用的数据库服务器不同来配置不同的信息;

(2)是否显示错误信息:这个在开发过程中是非常实用的,大家平时在各种数据库客户端里写SQL语句的时候都会遇到一种情况,就是当你的SQL语句出错时,客户端会给出错误提示信息,这样能够方便你快速定位错误并解决,同样,CI的数据库类也是提供这样的功能,在项目开发过程中,强烈推荐打开此配置项,当你的SQL语句出错时,CI框架会打印你的SQL语句,并显示数据库服务器提供的错误信息,这样有利于debug;

(3)与数据库通信时所使用的字符集:这个配置项其实很多程序员都不陌生,而且是很多程序员在初学数据库编程时都会遇到的问题,如果你是用PHP去连接MySQL数据库,一般在连接数据库之后,你还需要做的一个动作就是设置当前数据库链接的字符集,即:

mysql_query("set name utf8", $db_connect);

(4)与数据库通信时所使用的字符规则:这个配置项在原生的PHP代码中很少见到,这个配置项的值取决于你在创建数据库时,为数据库选择的编码字符集,要与其保持一直,目前较为常用的数据库编码字符集多是utf8_general_ci,因为这个字符集兼容性较好,对数据库中需要同时存储数字、英文、中文、二进制的数据库,推荐设置为该字符集;

(5)数据库名:最后再说一下数据库名,在原生的PHP连接MySQL数据库时,在创建链接、设置编码之后,需要做的就是选择数据库,一般的代码为:

mysql_select_db(“database_name”, $db_connect);

在CI框架中,该配置项的作用和上面这句代码的作用相同。

对这些针对数据库信息的配置项的说明就到这里,但是肯定大家会好奇,为什么数据库的配置项都是二维数组呢,而且大家还看到了一个很熟悉的单词:default。其实这是CI数据库类的一个特性,那就是他的数据库配置信息支持多个数据库连接,因为在真实项目的开发中,一般都是开发使用一个数据库,发布使用另外一个数据库,为了切换方便,你可以一次性将这两个数据库的信息都写入该配置文件,它俩唯一的区别在于第一维数组的名字,比如开发用到的数据库信息可以写为:

$db['development']['hostname'] = "localhost";

$db['development']['username'] = "";

$db['development']['password'] = "";

$db['development']['database'] = "";

$db['development']['dbdriver'] = "mysql";

$db['development']['dbprefix'] = "";

$db['development']['pconnect'] = TRUE;

$db['development']['db_debug'] = TRUE;

$db['development']['cache_on'] = FALSE;

$db['development']['cachedir'] = "";

$db['development']['char_set'] = "utf8";

$db['development']['dbcollat'] = "utf8_general_ci";

$db['development']['swap_pre'] = "";

$db['development']['autoinit'] = TRUE;

$db['development']['stricton'] = FALSE;

而发布用到的数据库信息可以写为:

$db['release']['hostname'] = "localhost";

$db['release']['username'] = "";

$db['release']['password'] = "";

$db['release']['database'] = "";

$db['release']['dbdriver'] = "mysql";

$db['release']['dbprefix'] = "";

$db['release']['pconnect'] = TRUE;

$db['release']['db_debug'] = FALSE;

$db['release']['cache_on'] = FALSE;

$db['release']['cachedir'] = "";

$db['release']['char_set'] = "utf8";

$db['release']['dbcollat'] = "utf8_general_ci";

$db['release']['swap_pre'] = "";

$db['release']['autoinit'] = TRUE;

$db['release']['stricton'] = FALSE;

这样你就会有两个数据库信息,接下来的问题就是如何切换,细心的人应该会发现在配置文件的开始部分,有一个配置项是:

$active_group = 'default';

其实,这个配置项是用来设置当前要使用的数据库信息,而其值,则为下面二维数组的一维名称,也就是说,假如你要使用开发数据库时,只需要将其值改为development即可,如果你要使用发布数据库,就将其值改为release即可。

还要简单的提一下刚才没有讲到的一个配置项,就是:

$active_record = TRUE;

这个配置项用于设置是否允许使用Active Record类,这是一个全局的配置,如果你不用这个类Active Record,可以将其值设置为FALSE,但是我的建议是在项目开发中,如果能够使用Active Record进行的操作,都最好用Active Record实现,尽量少用自己写的SQL代码,除非你非常熟悉数据库优化方面的知识,保证自己写的SQL语句在运行效率、安全性等方面都比Active Record好,你就可以不用Active Record类。

以上就是CI中数据库类的配置文件内容,下面开始讲解如何使用CI自带的数据库类。

我们以一个最简单的题库管理功能来讲解CI框架数据库类的使用,首先需要创建一个数据库和两张表,需要说明一下,本人使用MySQL数据库较多,所以在这里我也就以MySQL数据库为例进行讲解,如果你使用其他数据库是一样的用法,只要在配置文件中修改相应信息即可。我要建立的数据库名称为question,两张表分别为choice(选择题题干表)和option(选择题选项表),下面给出这两张表的结构:

选择题题干表
字段名 注释 数据类型 备注
id   int 主键
content 题干内容 varchar(500) 非空
answer_id 答案id int 外键
选择题选项表
字段名 注释 数据类型 备注
id   int 主键
choice_id 选择题题干id int 外键,非空
content 选项内容 varchar(500) 非空

然后根据上表所示的表结构在数据库中创建相应的表,为了方便,我在这里为大家提供了建表所需的SQL语句,大家可以直接将该SQL语句复制到你的数据库中执行即可:

-- phpMyAdmin SQL Dump

-- version 4.1.12

-- http://www.phpmyadmin.net

--

-- Host: 127.0.0.1

-- Generation Time: 2015-04-22 07:37:06

-- 服务器版本: 5.6.16

-- PHP Version: 5.5.11

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";

SET time_zone = "+00:00";

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;

/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;

/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;

/*!40101 SET NAMES utf8 */;

--

-- Database: `question`

--

CREATE DATABASE IF NOT EXISTS `test` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

USE `test`;

-- --------------------------------------------------------

--

-- 表的结构 `choice`

--

-- 创建时间: 2015-04-22 05:23:35

--

DROP TABLE IF EXISTS `choice`;

CREATE TABLE IF NOT EXISTS `choice` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`content` varchar(500) NOT NULL COMMENT '题干内容',

`answer_id` int(11) DEFAULT NULL COMMENT '答案id',

PRIMARY KEY (`id`),

KEY `answer_id` (`answer_id`)

) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COMMENT='选择题题干表' AUTO_INCREMENT=3 ;

--

-- 表的关联 `choice`:

--   `answer_id`

--       `option` -> `id`

--

-- --------------------------------------------------------

--

-- 表的结构 `option`

--

-- 创建时间: 2015-04-22 05:31:23

--

DROP TABLE IF EXISTS `option`;

CREATE TABLE IF NOT EXISTS `option` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`choice_id` int(11) NOT NULL COMMENT '选择题题干id',

`content` varchar(500) NOT NULL COMMENT '选项内容',

PRIMARY KEY (`id`),

KEY `choice_id` (`choice_id`)

) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COMMENT='选择题选项表' AUTO_INCREMENT=9 ;

--

-- 表的关联 `option`:

--   `choice_id`

--       `choice` -> `id`

--

--

-- 限制导出的表

--

--

-- 限制表 `choice`

--

ALTER TABLE `choice`

ADD CONSTRAINT `选择题题干关联答案` FOREIGN KEY (`answer_id`) REFERENCES `option` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;

--

-- 限制表 `option`

--

ALTER TABLE `option`

ADD CONSTRAINT `选择题选项关联题干` FOREIGN KEY (`choice_id`) REFERENCES `choice` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;

/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;

/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

这里需要注意几个问题:第一,这个数据库中的两个表是互相外键关联的,选择题肯定要有正确答案选项,同时任何选项都一定是属于某一个选择题的,总结来说就是题干和选项的关系是1:n的关系;第二,选项表通过外键关联选择题时,无论是更新还是删除,都采用CASCADE(级联)方式,因为如果题目的ID变了,选项中的choice_id肯定也要跟着变,题目删除了,那这个题目下的选项就没有存在的意思了,也就需要跟着被删除;第三,题干表通过外键关联选项时,也就是为题干设置答案时,只有在更新时采用CASCADE方式,因为加入一个选项被设置为某题的答案,那么这个选项的ID发生变化时,对应题干的答案id也应该发生变化,但是如果该选项被删除时,题干不能同时被删除,否则容易引起误删。

关于数据库就说明以上几点,至于数据库的基础知识,并不是本教程所要讲解的内容,关于数据库设计的相关知识在这里推荐一本书《数据库系统概论》,作者是王珊和萨师煊,该书目前已经发布到第五版了,但楼主学习的时候用的第四版,这本书讲的比较细致,而且楼主感觉理解起来也不是很难,所以在这推荐一下。

下面开始编写具体的业务逻辑代码,这节课要实现的是对刚才所建的数据库的增、删、改、查,也就是人们常说的数据库的CURD(创建、更新、读取、删除)操作,最后会介绍一下如何用CI提供的数据库类实现事务操作。打开帮助文档的数据库类,可以看到在CI提供的数据库类并不是一个面向对象意义上的单一的类,它包含了很多子类,例如:Active Record 类、数据库缓存类、数据库工具类等等,除此之外,还有一些辅助函数和配置信息,这些都是我们在开发中会用到的。在下面的学习中我会着重讲解Active Record 类、生成查询记录集、事务、查询的大部分内容以及查询辅助函数、表数据等模块的少部分内容,这些函数、类都是在实际项目中经常用到的类,至于我没有介绍的部分,大家可以在看懂本节教程的基础上自行研究,他们的用法都是一样的。

接下来我就按照增、删、改、查的顺序来进行数据库类使用的讲解。但是在开始代码之前我们先要修改一下我们的数据库配置,让程序能够连接到我们刚才所创建的数据库上去,具体配置信息如下:

// 数据库服务器地址

$db['default']['hostname'] = 'localhost';

// 数据库用户名

$db['default']['username'] = 'root';

// 数据库密码

$db['default']['password'] = 'root';

// 数据库名

$db['default']['database'] = 'question';

// 数据库服务器类型

$db['default']['dbdriver'] = 'mysql';

这里只需要修改以上五项配置信息即可,其他的都保持系统默认,这里要说一下,在真实的项目开发中一般不会直接用root用户来连接数据库,因为这是非常危险的行为,在实际项目开发中,一般会为某个数据库或者某几个数据库创建一个用户,然后只给这个用户赋予对应数据库的权限,并不会赋予其全局的权限,但是这里楼主为了方便就用root用户了。

接下来就是增加数据,现在数据库已经建好了,但是里面还没有数据,那么现在就来往数据库里面添加一些数据。还是按照之前讲的,先创建一个控制器,然后修改默认控制器,然后创建视图并加载视图。先创建一个主控制器,主要用于显示主界面,之后还会对其进行修改,在controller文件夹下创建一个db_test.php文件,然后编写代码,具体代码如下:

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

/*

* 主控制器

*/

class Db_test extends CI_Controller {

// 构造方法

function __construct() {

parent::__construct();

}

function index() {

// 加载视图

$this -> load -> view("main_view");

}

}

然后是主视图main_view.php,具体代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<title>CI框架数据库类使用</title>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

</head>

<body>

<div>

<span><a href="" target="_self">Insert</a></span>

</div>

</body>

</html>

现在,把配置文件中的默认控制器修改为db_test,然后访问http://localhost/CI_05/,就能看到界面上有一个内容为“Insert”的超链接。

现在我们来编写和插入数据相关的代码,根据要实现的功能,可以知道,首先需要插入一个题干,然后还要插入题干下面的选项,最后还要给这道题设置一个标准答案,实现这些功能需要给用户提供一个可交互的表单界面,让用户填入题干和选项,然后再选择正确答案,那接下来就来编写插入数据所需要的控制器。在controller文件夹下创建db_insert.php文件,然后编写代码,具体代码如下:

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

/*

* 插入数据控制器

*/

class Db_insert extends CI_Controller {

// 构造方法

function __construct() {

parent::__construct();

$this->load->helper('form');

$this->load->helper('url');

}

function index() {

// 加载视图

$this -> load -> view("insert_view");

}

/**

* 插入数据

*/

function do_insert(){

}

}

然后创建该控制器所需要的视图insert_view.php文件,然后利用之前讲过的表单辅助函数创建我们所需要的表单,具体代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<title>插入数据</title>

<style type="text/css">

#insert {

margin: 10% auto;

width:600px;

border:1px solid #000;

}

</style>

</head>

<body>

<div id="insert">

<?php

// 创建表单

echo form_open(site_url('db_insert/do_insert'));

// 创建题干输入框

echo "题干:<br />";

echo form_textarea(array('name'=>'choice', 'rows'   => '5', 'cols' => '50'))."<br /><br />";

// 创建选项选择框和输入框

echo "选项:<br />";

for($i = 1; $i <= 4; $i++){

// 创建单选按钮

echo "选项{$i}:";

echo form_radio(array('name'=>'answer', 'value'=>$i));

// 创建选项

echo form_input(array('name'=>"option{$i}", 'maxlength'=>'100', 'size'=>'50'));

echo "<br />";

}

echo "<br />";

// 创建提交按钮

echo form_submit('mysubmit', '增加')."&nbsp;&nbsp;";

// 创建重置按钮

echo form_reset('myreset', '重置');

// 关闭表单

echo form_close();

?>

</div>

</body>

</html>

在上面的代码中,出现了一个函数

site_url('db_insert/do_insert')

这个函数是CI的URL辅助函数提供的一个函数,他的作用是构造一个本站点的URI,要想使用这个函数,含需要修改config.php配置文件中的

$config['base_url'] = '';

字段,按照本节课的内容,需要将该字段修改为

$config['base_url'] = 'http://localhost/CI_05/';

所以在调用site_url()函数时,如果传入参数,则可以自行帮你构建一个基于base_url的内容加上所传参数的URI,如果不传入参数,则直接返回base_url的内容,例如上面那段代码,其返回的结果就是:

http://localhost/CI_05/db_insert/do_insert

在插入数据控制器类的构造函数中,可以看到有一行这样的代码:

$this->load->helper('url');

这行代码就是加载URL辅助函数,CI框架的URL辅助函数提供了很多关于URI的方便实用的函数,其中最为常用的有site_url()、base_url()、current_url()、anchor()、redirect()等。

现在,需要把主界面和这个插入数据的控制器连接起来,就用到了之前讲过的URI的内容,根据CI的路由规则,我们想要看到上面所写的插入数据需要的表单,就需要访问

http://localhost/CI_05/db_insert

那么,我们就需要在main_view.php文件的Insert超链接上设置href的值为上面这个URI,所以需要修改一下main_view.php文件的对应的代码,具体内容为:

<span><a href="<?=site_url('db_insert')?>" target="_self">Insert</a></span>

这里又看到了site_url()函数,同理,在这里使用了该函数就需要在db_test类的构造函数中增加一行加载URL辅助函数的代码,大家自行添加即可。

大家现在可以访问一下http://localhost/CI_05/,刷新一下界面,现在点击界面上的“Insert”超链接,页面就会跳转到刚才所写的表单界面了,接下来我们来写增加题目的代码。

根据表单的提交地址,我们需要在db_insert控制器的do_insert方法中来接收和处理数据,具体流程应该是这样:第一,分别接收题干、选项和答案的表单内容;第二,将接收到的数据传给model,由model来插入数据库;第三,在model内,需要做的是先将题干插入数据库,然后获得题干的id,之后再插入该题的所有选项,最后,根据用户所选的答案,将答案对应的选项id设置给题干。在这个过程中,会用到的SQL操作有两个,一个是插入,一个是更新,现在我们先来编写对应的model的代码,在models文件夹下创建db_model.php文件,具体如下:

<?php if (!defined('BASEPATH')) exit('No direct script access allowed');

/**

* 题库管理模型

*/

class Db_model extends CI_Model {

function __construct() {

parent::__construct();

// 载入并初始化数据库类

$this -> load -> database();

}

/**

* 增加题目

*/

function insert($choice = FALSE, $options = FALSE, $answer = FALSE) {

// 检查参数

if (!$choice || !$options || !$answer || !is_array($options) || empty($options) || (intval($answer) >= count($options))) {

return FALSE;

}

// 开始事务

// begin

$this -> db -> trans_start();

// 插入题干

// INSERT INTO `choice`(`content`) VALUES ($choice)

if ($this -> db -> insert('choice', array('content' => $choice))) {

// 插入题干成功

// 获取题干ID

// LAST_INSERT_ID()

$choice_id = $this -> db -> insert_id();

// 插入选项

for ($i = 0; $i < count($options); $i++) {

// INSERT INTO `option`(`choice_id`, `content`) VALUES ($choice_id, $options[$i])

if ($this -> db -> insert('option', array('choice_id'=>$choice_id, 'content'=>$options[$i]))){

// 选项插入成功

if ($i === intval($answer)){

// 当前插入的选项为答案,需要更新一下对应题干的记录

// UPDATE `choice` SET `answer_id`=LAST_INSERT_ID() WHERE `id`=$choice_id

$this -> db -> where('id', $choice_id);

$this -> db -> update('choice', array('answer_id'=>$this -> db -> insert_id()));

}

}else {

// 选项插入失败

// 这里什么都不做......

}

}

} else {

// 插入题干失败

// 结束事务

// rollback

$this -> db -> trans_complete();

return FALSE;

}

// 结束事务

// commit

$this -> db -> trans_complete();

return TRUE;

}

}

在上面的代码中,我们可以看到构造函数中有一行代码是

$this -> load -> database();

这段代码的作用是载入并初始化数据库类,这和之前讲过的类库或辅助函数的加载方法都不一样,这里需要注意,所有用到数据库的类,都需要在构造函数中写上这样一段代码,一般情况下,最好将所有操作数据库的代码都放在model中,如果你对OOP和OOA有研究,你应该能明白这样做的意义,其实如果能够进行更高级别的抽象,那么所有对数据库的操作可以封装成一个类,然后放入特定的model或者library中,关于如何在CI框架中实现自定义的library,后面的课程中我会介绍,现在只是在这里简单提一下。

在上面的代码中,我使用的是Active Record 类对数据库进行操作,但我在每个操作的上面都以注释的形式给出了对应的SQL代码,大家可以对照阅读一下,然后再根据帮助文档,就能够快速掌握Active Record 类的使用方法了。

在这里还需要强调的一点就是,上面的代码中使用了事务,在事务内部,函数是不能返回或者终止的,也就是在

$this -> db -> trans_start();

$this -> db -> trans_complete();

之间,是不能出现return 或者 exit之类的代码,否则会引起表或者记录的死锁,楼主曾经在做项目的时候就遇到过这种的情况,关于如何解锁,本教程不进行介绍,感兴趣的童鞋可以自行搜索。

接下来我们来看一下调用该模型的控制器的方法,就是之前表单提交的目的函数,db_insert控制器下的do_insert()函数,具体代码如下:

/**

* 插入数据

*/

function do_insert(){

// 接收数据

$choice = $this -> input -> post('choice');

$answer = $this -> input -> post('answer');

$options = array();

for ($i = 0; $i < 4; $i++){

$options[$i] = $this -> input -> post("option{$i}");

}

if (!$choice || !$answer || !$options || !is_array($options) || empty($options)) {

// 接收数据失败,跳转回原来的页面

show_error('您提交的数据不正确,请重新提交');

exit();

}

// 存入数据库

if ($this -> db_model -> insert($choice, $options, $answer)){

// 添加题目成功

$this -> load -> view("insert_view");

}else {

// 添加题目失败

show_error('添加题目失败,请重新提交');

}

}

在这个函数里,我使用了CI提供的输入类来接收表单所提交的数据,即

$this -> input -> post('choice');

其实在之前的几节教程里都用到了这个,但是没有单独介绍过,这里我简单介绍一下,这个输入类也是CI框架中比较特殊的一个类,这个类不需要程序员显示的去加载,输入类会被系统默认自动加载,在输入类中提供了很多函数,大多数都是用来接收参数的,除此之外,还提供一个安全过滤机制,可以有效过滤掉具有危险的输入数据。输入类中常用的方法有以下几个:

$this->input->post('some_data', TRUE);

$this->input->get('some_data', TRUE);

$this->input->get_post('some_data', TRUE);

$this->input->cookie('some_data', TRUE);

这些方法都有两个参数,第二个为可选,如果将第二个设置为TRUE,那么函数将对输入进行XSS过滤,第一个参数为必须,是参数的名称,如果所要接收的参数不存在,函数则返回FALSE。这里大家可以结合上一节讲过的内容考虑一下它是如何实现这样的效果的。至于其他的输入类函数大家可以参考帮助文档,同时,CI框架还有一个输出类,和输入类的用法相似,大家可以自行了解一下,这里不做过多讲解。

在上面的代码中还有一个之前没有用过的函数

show_error('您提交的数据不正确,请重新提交');

这是CI提供的公共函数,用于显示错误信息,当CI框架核心代码报错时,也会调用该函数进行显示,调用该函数之后,你会看到一个页面,页面上会打印你传入的参数,其实这个页面就是application/errors目录下的error_php.php文件,如果你对HTML+JS+CSS熟悉的话,可以自己动手去修改一下这个文件里面的代码,然后再调用show_error()函数看看效果。这里需要指出的是,一般来说,在实际项目中,我们会修改application/errors目录下的几个用于显示错误信息的文件,一方面是为了报错界面能够和整个项目的其他界面保持统一的风格,另一方面,当这些报错页面被修改,再加上之前讲过的通过Apache的Rewrite机制和CI的路由机制重写路由规则时,即使网站意外报错,用户也不会轻易的看出该网站所使用的具体技术,能够提高一些项目的安全性。

现在大家可以刷新一下界面,然后填写数据进行增加题目的操作了,友情提示:记得要选择正确答案哦,否则有可能会提示“数据不正确”。点击添加之后,如果添加成功页面会跳转回插入数据页面,这时大家可以打开你的数据库看看,相信数据已经添加进去了。

 

接下来咱们说一下在数据库操作中最常用的操作——查询。查询操作是项目使用数据库服务最常用的操作,在CI中,可以通过Active Record类配合生成查询记录集来实现这一功能,其用法和上面所讲的插入和更新是类似的,不过关于查询的函数要多一点,大家可以打开帮助文档看一下,在Active Record类中,关于查询较为常用的函数主要有一下几个:

$this->db->get();

$this->db->select();

$this->db->from();

$this->db->join();

$this->db->where();

$this->db->or_where();

$this->db->like();

$this->db->group_by();

$this->db->having();

$this->db->order_by();

$this->db->limit();

其实大家就看看上面这些函数的名字,就已经能大概猜出来这些函数的功能了,下面我就以当前项目为例子进行展示,现在大家应该已经添加了几个数据进入数据库了,那么我们就来查询出来,为了方便,我将查询结果以表格的形式显示在main_view里,根据代码结构,可以看出,我应该在db_test控制器的index方法中获取数据,然后传递给main_view,现在就来实现这样的功能。如果要在db_test控制器的index方法中获取数据,我们应该在model里面写一个方法,给控制器提交数据,所以我们现在先写model部分,我们可以直接在db_model中增加一个方法用来实现查询的功能,具体代码如下:

/**

* 查询题目

*/

function select($choice_id = FALSE){

if ($choice_id){

$this -> db -> where('id', $choice_id);

}

$this -> db -> select('choice.id as id, choice.content as choice_content, choice.answer_id as answer_id, option.id as option_id, option.content as option_content');

$this -> db -> join('option', 'choice.id = option.choice_id');

$this -> db -> from('choice');

$this -> db -> where('choice.answer_id = option.id');

$this -> db -> group_by('id');

$result = $this -> db -> get();

/*

* 当$choice_id为FALSE时,上面这几句话代码对应的SQL语句是

* SELECT `choice`.`id` as id, `choice`.`content` as choice_content, `choice`.`answer_id` as answer_id, `option`.`id` as option_id, `option`.`content` as option_content FROM (`choice`) JOIN `option` ON `choice`.`id` = `option`.`choice_id` WHERE `choice`.`answer_id` = option.id GROUP BY `id`

* 当$choice_id不为FALSE时,上面这几句代码对应的SQL语句是

* SELECT `choice`.`id` as id, `choice`.`content` as choice_content, `choice`.`answer_id` as answer_id, `option`.`id` as option_id, `option`.`content` as option_content FROM (`choice`) JOIN `option` ON `choice`.`id` = `option`.`choice_id` WHERE `choice`.`id` = '$choice_id' AND `choice`.`answer_id` = option.id GROUP BY `id`

*/

if ($result && $result->num_rows() > 0){

return $result->result_array();

}else {

return FALSE;

}

}

在这段代码中,我们使用了select、join、from、where和get五个Active Record类的函数,同时还使用了num_rows和result_array函数。

关于Active Record类中的函数对应的SQL语句,我在代码中已经以注释的形式给出了,其实如果用过PDO类的童鞋可能更容易理解CI框架的Active Record类,CI框架的Active Record类其实更像是一种SQL语句的组合,只不过在组合的同时,兼顾了安全性、健壮性和代码的优雅性,但是这里仍要提醒一句:第一、根据过往经验,CI框架是一个轻量级框架,当进行高并发网站开发的时候,其提供的Active Record类在效率方面会有一定的不足;第二、在实现一些比较复杂的SQL查询语句时,Active Record类可能会因为拼接SQL语句的顺序问题,而引起SQL语句的逻辑错误。针对这两个问题,我只是在这里提一下,这也是我在使用CI时发现的一些问题,当然,这些问题都是可以通过一定的技术手段改进或者修复的,但我不会对这两点进行深入讲解。

接下来再简单说一下num_rows和result_array函数,这两个函数都是用来生成结果集的,num_rows函数会返回当前结果集的行数,也就是当前SQL语句执行结果的行数,而result_array函数则会以二维数组的形式返回当前SQL语句执行的完整结果集,这里需要说明一下,result_array函数返回的二维数组,第一维是索引数组,下标从0开始,代表结果集中的一行,第二维即是索引数组也是关联数组,代表结果集中的一列,也就是说,在第二维时,即可以用下标读取数据,也可以用结果集中的列名读取数据,这里需要注意,我说的是“结果集中的列名”而不是“数据表中的列名”,如果你在SQL语句中对某表的某列进行了重命名,这里读取数据时要使用重命名之后的列名,简单来说,将对应SQL语句放入数据库执行之后的结果里,会显示出列名,这里读取数据时,用的就是那个列名,当然,也可以通过下标读取,为了验证,我们可以将result_array函数返回的结果赋值给一个变量,假设该变量为$temp,那么以下两个数组元素的值是相等的

$temp = $result->result_array();

// 按索引数组取值

$temp[0][0];

// 按关联数组取值

$temp[0]['id'];

这个大家可以自己修改代码验证一下,我就不再验证了。

现在来看控制器,之前已经说了,我们要在main_view中显示当前的题库,而main_view是由db_test控制器加载的,所以需要在db_test控制器中调用刚才所编写的model,然后将数据传递给main_view进行显示,现在就来看看db_test怎么修改,具体如下:

function index() {

// 获取数据库中的题目

$data = $this -> db_model -> select();

// 加载视图

$this -> load -> view("main_view", array('data'=>$data));

}

修改了控制器,现在需要修改视图文件,视图文件在这里要做的就是一个解析数组,然后展示内容,这里我们要明确数组的格式和内容,通过阅读代码,我们知道这里的数组来源于result_array函数的返回值,在上面我已经讲解了这个函数的返回值,现在就需要编写视图代码来对它进行展示了,具体如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<title>CI框架数据库类使用</title>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

</head>

<body>

<div>

<span><a href="<?=site_url('db_insert')?>" target="_self">Insert</a></span>

</div>

<hr />

<div>

<? if (isset($data) && $data && is_array($data) && count($data) > 0):?>

<table>

<tr>

<th>题目ID</th>

<th>题目内容</th>

<th>正确答案</th>

<th>操作</th>

</tr>

<? foreach ($data as $value):?>

<tr>

<th><?=$value['id']?></th>

<th><?=$value['choice_content']?></th>

<th><?=$value['option_content']?></th>

<th><a href="">修改</a>&nbsp;&nbsp;<a href="">删除</a></th>

</tr>

<? endforeach;?>

</table>

<? else: ?>

<strong>当前没有题目</strong>

<? endif;?>

</div>

</body>

</html>

大家可以看到,相比原本的代码,我只是增加了一个表格。

这里我要扩展讲解一点,在较老的软件测试相关的书本中,较为常见的测试方法都是什么黑盒测试、白盒测试之类的,但是目前较新的软件测试方式有结点覆盖、路径覆盖、图覆盖、逻辑覆盖等等,为什么要说这个呢?请大家仔细看上面的代码,在编写代码的过程中,我们应该追求代码的健壮性和安全性,就拿这个程序展示表格来说,我们需要假定有可能当前系统中并没有题目,这个时候,我们也需要给出相应的提示,如果我们不将这种情况进行处理的话,当数据库被清空时,界面很有可能会报错,这在正常的软件运行过程中是难以接受的,所以在写代码的时候,我们需要提前尽可能多的预估出会出现的不同情况,然后针对每种情况给出对应的处理代码,这样才是一款能称之为软件的软件。其实,根据我的经验,一定范围内的新手和老手,很多时候是在经验上有差距,真正由于技术水平引起的差距并不是很大。这里就需要大家经常性的思考、阅读自己和他人的代码,发现自己在过往编码中的不足,做为一个程序猿,不能每天每时每刻都埋头写代码,这样的工作对程序猿本身来说只是重复的无用功,只能增长你的量,只有当你有时间思考自己的过往工作时,你才有机会在质上提高自己。

写完view页面之后,大家现在可以再访问http://localhost/CI_05/,看看现在的界面是什么样的,如果不出意外,你应该已经看到你刚才插入的那个题了。

 

接下来,来说删除功能,删除功能我们采用ajax方式进行,因为删除功能本身是很简单的,也不需要单独新建一个界面,所以采用ajax方式比较好,ajax方式就需要js,那么,我们首先要在页面上为“删除”超链接增加一个class,具体代码如下:

<a href="" class="delete">删除</a>

接下来,我们需要编写相关的js代码,这里为了方便我试用jQuery做为底层库,不使用原生的js,至于怎样在CI中实现ajax,我在之前的课程里面就已经讲过了,如果不会的,请回去看前面的内容。

在编写自己的代码之前,我们需要引入jQuery库,代码如下:

<script type="text/javascript" src="http://code.jquery.com/jquery-1.11.2.min.js"></script>

接下来编写自己的ajax代码,具体如下:

<script type="text/javascript">

$(function() {

$("#delete").click(function() {

if (confirm("确认删除你所选中的题目?")) {

var tr = $(this).parent().parent();

$.ajax({

type: "get",

url: "db_test/delete",

data: {

id: $(this).parent().prevAll().last().text()

},

dataType: "json",

success: function(data) {

if (data.success) {

alert("删除成功");

tr.remove();

} else {

alert("删除失败");

}

},

error: function(data) {

alert("请求错误,请检查您的网络");

}

});

}else {

alert("你在逗我吗?");

}

return false;

});

});

</script>

上面的代码是在点击删除链接时被调用的js代码,大家可以看到,通过jQuery的遍历函数,找到了当前这个题目所对应的tr标签,然后在后台删除成功后,用DOM操作将这个tr标签移除,从而实现了ajax删除动作,但这里要注意,这个代码其实还是有问题的,假设我们已经将所有的题目删除了,这里的js代码并没有让页面恢复到没有题目的状态,但是由于时间关系,我在这里就不写出对应的代码,感兴趣的同学,可以自己试着在这个代码的基础上进行修改。

写好了前端的js代码,现在来写model里面真正实现删除动作的函数,通过上面的代码我们可以知道,在删除题目时,我们提供的是题目的id,所以在model里面,我们需要根据题目的id删除这道题,具体代码如下:

/**

* 删除题目

*/

function delete($choice_id = FALSE){

if (!$choice_id) {

return FALSE;

}

if ($this -> db -> delete('choice', array('id'=>$choice_id))) {

return TRUE;

}else {

return FALSE;

}

}

这里还要说一下,在删除函数中,我们只删除了对应的选择题,那么该题的选项呢?由于前面在设置外键时,我设置了级联关系,所以对应选项也会被删除,这是由数据库提供的功能,并非PHP。但是,在实际项目中,是否使用级联关系的外键还需要慎重考虑!因为级联关系很容易造成数据误删,在有些场景下,就不适合使用级联外键。比如,在电商系统中,交易信息和商品信息、会员信息等肯定是通过外键关联的,那么这里就需要考虑了,如果你想让系统具有统计功能和深度的数据回溯功能,那么交易信息肯定是不能被删除的,因为交易信息不能被删除,所以这里设置外键时,就不能设置级联,否则代码里的一个小错误,或者是进行深度数据挖掘时的一个错误的SQL语句,都有可能导致数据丢失,这里就需要特别注意。

写好了视图和模型,接下来,再来看看控制器,其实控制器也很简单,就是接收前端发来的ajax请求,然后调用model,返回model执行的结果,具体代码如下:

function delete() {

// 获取要删除的题目ID

$id = $this->input->get('id');

$result = array('success'=>FALSE);

if (!$id) {

echo json_encode($result);

exit;

}

if ($this -> db_model -> delete($id)) {

$result['success'] = TRUE;

echo json_encode($result);

}else {

echo json_encode($result);

}

}

现在,刷新界面,然后点击删除链接试试看,相信已经可以实现正常的删除功能了。

 

——————————————————————————未完待续——————————————————————————

CI(CodeIgniter)框架入门教程——第五课 数据库类的使用》有7个想法

  1. 赞!楼主辛苦了。发现了一个小纰漏insert_view.php 和 db_insert.php for循环的开始下标不一致,在db_insert.php中$options[0]取不到值(NULL),插入数据库不成功。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用 Akismet 来减少垃圾评论。了解我们如何处理您的评论数据