PHP数据库抽象层

本章着重讲述PHP数据库抽象层与数据库抽象类。
目前,在PHP的网站及众多书籍中,很少讲到数据库抽象层, 有的PHP开发者虽然已做了几年开发工作,但似乎也没有意识到它的 存在。

很多人并不理解数据抽象的重要性,也不 了解mysql或mysqli扩 展与PDO的区别,PDO与ADODB又有什么区别。
在本章中将讲述以下内容,通过学习,上面的问题都会迎刃而解。
Ø使用数据库抽象层的原因;
Ø数据库抽象层的发展;
Ø一些流行新抽象层的介绍;
ØPDO数据库抽象层开发技术;
ØADODB数据库抽象层开发技术。

17.1什么是数据库抽象层

使用数据库抽象层,意味着当从一个数据 库系统向另一个数据库系统迁移时,几乎不用更改太多的程序代码,如将MS SQL Server迁移到MySQL。

首先,代码规划必须规范,即整个系统使 用同一个数据对象实例,并且使用同一个较好的数据库抽象层。如果有一天用 户要求将Oracle切 换到MySQL,则只需 要改变系统的配置文件即可。

在当今工业领域中,每个数据库开发商如 微软、Oracle、MySQL,都有自己的一套SQL标准,它们声称是按照ANSI SQL92标准而增加自己的特性,以达 到垄断或占领市场的目的。

优秀的数据库抽象层,会根据我们现在使 用的数据库自动调整一些SQL性能。当没有使用数据库本身特定的特性时,就不必更改太多的数据库连接和数据库SQL查询。

使用数据库抽象层的其他好处是:其性质、概念简化了复杂的任务。因此,我们不必学习某个数据库系统的全新特 性,而只用一个标准的抽象层的代码特性即可。

虽然这是一种理想化,但随着技术的发 展,相信数据库抽象层会为我们做更多的事。

目前,使用PHP进行不同的数据库系统开发,这些系统很不相 同,而许多数据库抽象层在PHP的层次有所不同,但彼此使用方法相当,它的发展无疑会提高开发效率。

请看图17-1,观察有数据抽象层和没有数据抽象层的区 别。
数据库抽象层的主要性能指标是速度,由于数据库抽象层是额外的代码层,因为面向不同的架构与体系,因此有的效率较高,有 的则相对比较慢些。

如Metabase是PHP中较慢的一个数据抽象层,它使用C语言编写,因为它的设计与可移植性最高,而PDO和ADODB是当今世界最快的数据库抽象类。

(b)使用数据抽象层的PHP程序架构
图17-1
如果您非常关注系统的效率和性能,则可 以按自己的基准,设置模拟环境,编写代码,测试每个数据库抽象层(类)的 性能。

17.2常用的数据库抽象层

目前,有4种主流数据库抽象层:Metabase、PEAR:DB、PDO及ADODB。
从目前的应用来看,ADODB和PEAR:DB最受欢迎(因为PEAR是被捆绑在PHP目录中的,但PEAR:DB的效率问题一直引人诟病)。
从PHP 5开始出现的PDO及ADODB(其中包括PDO、MySQLi的底层实现)已经逐渐普及。
也有一些更新的抽象类值得一用,PEAR:MDB(现在的版本名称为PEAR:MDB2)已经将Metabase和PEAR:DB合并,并且效率较之从前有很大的提 高。
我们先对现今常用的数据库抽象类的历史 及它们的简要特点进行一一介绍。

17.2.1 PEAR:DB

PEAR:DB自2001年以来,存在于PEAR中,由于PEAR默认绑定在PHP目录中,基于这些原因,成为比较流行的数据库抽象层。PEAR:DB所支持的数据库系统包括:
ØFirebird
ØInterbase
ØInformix
ØmSQL
ØMS SQL Server
ØMySQL
ØOracle
ØODBC
ØPostgreSQL
ØSQLite
ØSybase
PEAR:DB的下载地址:http://pear.php.net/package/DB/download

17.2.2 ADODB

ADODB(Active Data Objects DataBase),起源于2000年,到目前为止仍然在增强与更新,发展至今仍然是一个热门的数据抽象层。
ADODB的开发初衷,源于很多从Microsoft ASP或ADO转过来的工程师,很多用法与MS ADO非常相像,目前使用ADODB的流行软件,例如egroupware、Mambo等均采用ADODB。
ADODB的数据库封包程序库提供了共通的应用程序界面来跟所有支持的数据库沟通,值得一提的是,它除了支持PHP,还支持Python语言。
ADODB提供很多实用的方法,使它超越了一个抽象层的功能,如具有表格化和数据库缓存等非常好的特性。ADODB支持的数据库系统包括:
ØAccess
ØADO
ØDB2
ØFirebird
ØFoxPro
ØFrontBase
ØInformix
ØInterbase
ØLDAP
ØMS SQL Server
ØMySQL
ØODBC
ØOracle
ØPostgreSQL
ØSAP DB
ØSQLite
ØSybase

另外,因为ADODB的逐渐发展,特性增强,使得本身体积过 大,为此ADODB开发 团队还提供了基本功能的ADO Lite。
有关ADODB的使用和开发方法,我们会在17.4节重点介绍。

17.2.3
Metabase

Metabase由Manuel Lemons开发。Metabase已被誉为慢抽象层,但是它支持PHP的全部版本,也是目前提供最大的可移植性设计的唯一抽象层。目前支持的数据库系统包括:
ØAccess
ØInformix
ØInterbase
ØmSQL
ØMS SQL Server
ØMySQL
ØODBC
ØOracle
ØPostgreSQL
ØSQLite
下载地址:http://www.phpclasses.org/browse/package/20.html#download

17.2.4 MDB

SCMV-MDB的性能介于和融合PEAR:DB、Metabase数据库抽象层之间,它的初衷就是试图改善性能和最大可移植性。稳定版于2004年4月发布。
MDB目前支持的数据库系统包括:
ØFirebird
ØFrontbase
ØInterbase
ØMS SQL Server
ØMySQL
ØOracle
ØPostgreSQL
ØQuerysim
PEAR:MDB下载地址:http://pear.php.net/package/MDB/download

17.2.5 MDB2

PEAR:MDB2把Metabase/PEAR:DB合并,并在SCMV-MDB的基础上进一步巩固,以及去除不必要的冗余工作。另外,它还整合了与PDO的接口。
PEAR:MDB2目前支持的数据库系统包括:
ØFirebird
ØFrontbase
ØInterbase
ØMS SQL Server
ØMySQL
ØOracle
ØPostgreSQL
ØQuerysim
ØSQLite
PEAR:MDB2下载地址:http://pear.php.net/package/MDB/download

17.2.6 Creole

Creole是基于Java的JDBC技术,在某种程度上是PEAR:DB、PEAR: MDB2和ADODB的结合体。
Creole是PHP 5上较新的数据抽象层,与PHP 4不兼容。Creole提供充分的面向对象开发的API,以及PHP 5的异常处理机制。
Creole目前支持的数据库系统包括:
ØMySQL
ØMS SQL Server
ØOracle(in progress)
ØPostgreSQL
ØSQLite
Creole库的下载地址:http://creole.phpdb.org/wiki/index.php?node=2。

17.2.7 PDO

PDO数据抽象层是随着PHP 5.1推出的,PDO使用C语言编写,因为是与PHP同级的API,所以它的执行速度快。PDO目前支持的数据库系统包括:
ØFirebird
ØFreeTDS
ØInterbase
ØMySQL
ØMS SQL Server
ØODBC
ØOracle
ØPostgreSQL
ØSQLite
ØSybase
PDO必须运行在PHP 5.1及以上版本上。本章会重点讲述PDO的编写方法。

17.2.8 PHPLib

到目前为止,PHPLib已经使用了近6年,只是因为它曾经是非常受欢迎的数据库类,它 在设计时并不是一个数据库抽象层,而只用于概括连接串、查询和处理错误和封装。
因为它小巧方便,加载速度快,现在使用 这个类作为数据库层查询的PHP项目也为数不少。PHPLib的官方网站为:http://phplib.sf.net。

17.3PDO数据库抽象类

17.3.1 PDO简介

PDO(PHP Data Objects Layer)提供一个公共的数据库系统接口,它使用C语言做底层开发,运行速度比较快。
PDO以PHP 5.1为基础进行设计,设计沿承PHP的特点,以简洁易用为准,从严格意义上讲,PDO应该归为PHP 5的SPL库之一,而不应该归于数据抽象层,因为其本身和MySQL和MySQLi扩展库的功能类似。

17.3.2 PDO的安装

PDO本身结果是模块化的,它被分成一个公共核心,以及一个或多个驱动程序扩展,公共核心提供了在脚本(PDO本身)中使用的API,驱动程序扩展则为PDO和本地RDBMS客户机API库架起一座桥梁,用来访问指定的数据库系统。 比如,IBM DB2用 户会希望使用PDO_ODBC驱动程序,Oracle用户会用Oci8_PDO接口,MySQL用户则会用pdo_mysql驱动程序。
PDD的核心在PHP 5.2下默认为开启状态,驱动程序除pdo_sqlite之外,都需要手工打开。
下面是在FreeBSD环境下使用Ports安装PDO核心驱动程序的步骤:
cd /ports/database/pecl-PDO
make install

安装后,它会自动修改php.ini配置文件,如果没有该项则自行加 入:
extension=pdo.so

安装PDO MySQL驱动程序:
cd /ports/database/pecl-PDO_MYSQL/
make install

修改php.ini文件,在刚才的项后加入该段:
extension=pdo_mysql.so

使用apachectl –k restart命令重新 启动Apache后,即 可完成PDO的安装了。
在Win32环境中,由于PHP 5.1版本以上的压缩包里已经自带PDO扩展库文件,因此只要在php.ini文件中打开该扩展即可,不需要再安 装。

17.3.3 PDO连接数据库

其实,PDO与其他数据库接口和数据库抽象层使用区别不 大,首先创建一个连接句柄:

<?php
// 连接MySQL数据库的账号
$login = “root”;
$passwd = “root”;
$db = new PDO(‘mysql:host=localhost;dbname=test’,$login, $passwd);
//如果连接失败,则抛出异常
try {
foreach($db->query(’select * from test’) as $row){ //查询数据库
print_r($row);
}
$db=null;//关闭数据库连接
} catch (PDOException $e) {
echo $e->getMessage();
}
?>

(1)使用持久连接pconnect
持久连接的好处是能够避免在每个页面命 中时都打开和关闭数据库服务器连接,速度更快,如Oracle数据库的一个进程创建了两个连接,PHP则会把原有连接与新的连接合并共享为一个连接。pdo_connect.php脚本如下:

<?php
//连接MySQL数据库的账号
$login = “root”;
$passwd = “root”;
$opt = array(PDO::ATTR_PERSISTENT => TRUE);
try {
$db = new PDO(‘mysql:host=localhost;dbname=test,$login,$passwd,$opt);
} catch (PDOException $e) {
echo $e->getMessage();
}
?>

(2)使用DSN- ODBC方式连接数据库
一个DSN(数据源名称)是一个标识符,定义为一个ODBC的数据源驱动。格式为:
Database name数据库名称
Directory目录
Database driver数据库驱动
User ID登录
Password密码
在UNIX系统下,DSN的配置通常存储在ini文件中,使用PDO读取文件配置,代码如下:

<?php
ini_set(“pdo.dsn.dbserver”, “mysql::test”);
try {
$db = new PDO(“dbserver”);
} catch (PDOException $e) {
echo $e->getMessage();
}
?>

17.3.4  使用PDO查询

使用PDO进行查询执行,可以使用两种方法。
第一种方法是预处理句柄(Prepared Statements),推荐使用,速度快而且安全。请看下例:

<?php
require_once(‘pdo_connect.php’);
$rs = $db->prepare(“SELECT * FROM test”);
$rs->execute();

while($row = $rs->fetch()){
print_r($row);
}
?>

Prepared预处理语句的作用是,编译一次,可以多次执行,可以有效防止SQL注入,在执行单个查询时快于直接使用query()/exec()的方法。
1.绑定参数使用Prepared预处理语句做INSERT操作时的参数需要赋一个名字,以及绑 定一个变量。

$stmt = $db->prepare(“INSERT INTO users VALUES(:name,:pass,:mail)”);
foreach (array(‘name’,’pass’,’mail’) as $v){
$stmt->bindParam(‘:’.$v,$$v); }
$fp = fopen(“./users.csv”, “r”);
while (list($name,$pass,$mail) = fgetcsv($fp,4096)){
$stmt->execute();
}
}

2.绑定结果列结果列可以绑定为变量,请看下面例子。

$qry = “SELECT :type, :data FROM images LIMIT 1”;
$stmt = $db->prepare($qry);
$fp = fopen(tempname(“/tmp”, “LOB”), “w”);
$stmt->bindColumn(‘:type’,$type);
$stmt->bindColumn(‘:type’,$fp, PDO::PARAM_LOB);
$stmt->execute(PDO::FETCH_BOUND);
header(“Content-Type: “.$type);
fflush($fp);
fseek($fp, 0, SEEK_SET);
fpassthru($fp);
fclose($fp);

第二种方法就是直接执行。
直接执行常见于直接查询操作或更新数据 库操作,可以使用exec()方法,请看下面的例子。

$db = new PDO(“DSN”);
$db->exec(“INSERT INTO foo (id) VALUES(‘bar’)”);
$db->exec(“UPDATE foo SET id=‘bar’”);

该方法返回的是操作影响的行数,若执行 错误,则返回False值。
在一些UPDATE的查询执行后,若没有影响到列,则返回0值,我们可以根据它返回的值或布尔值来进行相关 处理。例如:

$qry = “UPDATE foo SET id=‘bar’”;
$res = $db->exec($qry) or die(); //错误的返回
if (!$res) //未执行成功
if ($res !== FALSE) // 执行正确并返回
一个完整的例子:

<?php
$dsn = “mysql:host=localhost;dbname=test”;
$db = new PDO($dsn, ‘root’, ”);
//如果为持续性连接,则修改为下面样式
//$db = new PDO($dsn, ‘root’, ”, array(PDO::ATTR_PERSISTENT => true));
$count = $db->exec(“INSERT INTO foo SET id = NULL,name = ‘john’,gender=’male’,time=NOW()”);
echo $count;
$db = null;
?>

17.3.5 错误与异常处理

PDO提供两个方法来取得错误信息:
&Oslash;errorCode()——SQL语句错误,如:42000 == 语法错误;
&Oslash;errorInfo()—— 更详细的错误信息。
如下所示为错误信息内容:
array(
[0] => 42000,
[1] => 1064
[2] => Syntax Error
)
1.面向过程的处理

<?php
$db = new PDO(‘mysql:host=localhost;dbname=test’, $user, $pass);
$rs = $db->query(“SELECT aa,bb,cc FROM foo”);
if ($db->errorCode() != ‘00000′){
print_r($db->errorInfo());
exit;
}
$arr = $rs->fetchAll();
print_r($arr);
$db = null;
?>

PDO和PDOStatement对象有errorCode()和errorInfo()方法,如果没有任何错误,errorCode()返回的是00000;否则,就会返回一些错误代码。errorInfo()返回的是一个数组,包括PHP定义的错误代码和MySQL的错误代码及错误信息。数组结构如下:
Array
(
[0] => 42S22
[1] => 1054
[2] => Unknown column ‘aaa’ in ‘field list’
)
每次执行查询以后,errorCode()的结果都是最新的,所以我 们可以很容易地自己控制错误信息显示。
2.面向对象的处理标准的错误句柄,应该是一个面向对象方法来扩展PDO,以允许错误句柄取得系统异常。
请看下面的例子,如果查询出错,将抛出 异常。

$db->setAttribute(
PDO::ATTR_ERRMODE,
PDO::ERRMODE_EXCEPTION
);

一个完整的例子:

<?php
try {
$db = new PDO(‘mysql:host=localhost;dbname=test’, $user, $pass);
$db = null;
} catch (PDOException $e) {
print “Error: ” . $e->getMessage() . “<br/>”;
die();
}
?>

使用PHP 5的异常处理:这里利用PHP 5面向对象的异常处理特征,如果里面有异常,就调用PDOException来初始化一个异常类。
PDOException异常类的属性结构如下:

<?php
class PDOException extends Exception{
//错误信息,可以调用 PDO::errorInfo() 或 PDOStatement::errorInfo()来访问
public $errorInfo = null;

//异常信息,可以使用 Exception::getMessage() 来访问
protected $message;

//SQL状态错误代码,可以使用 Exception::getCode() 来访问
protected $code;
}
?>

这个异常处理类使用了PHP 5的异常处理类,下面简单地看一下PHP 5内置的异常处理类结构。

<?php
class Exception{
//属性
protected $message = ‘Unknown exception’; //异常信息
protected $code = 0; //用户自定义异常代码
protected $file; //发生异常的文件名
protected $line; //发生异常的代码行号

//方法
final function getMessage(); //返回异常信息
final function getCode(); //返回异常代码
final function getFile(); //返回发生异常的文件名
final function getLine(); //返回发生异常的代码行号
final function getTrace(); //backtrace()数组
final function getTraceAsString(); //已格式化成字符串的 getTrace() 信息
}
?>

相应的,在代码中可以合适地调用getFile()和getLine()来进行错误定位,以更方便地进行 调试。

17.3.6取得查询结果

PDO最大的特点之一是它的灵活性,本节将介绍如何取得查询结果,包括:
&Oslash;数组(数值或关联数组);
&Oslash;字符串(单列的结果集);
&Oslash;对象;
&Oslash;回调函数。
1.快取一行FetchColumn是为应用程序取得一个仅包含单列的数据,代码如下:

<?php
$u = $db->query(“SELECT id FROM users WHERE login=‘login’ AND password=‘password’”);
fetch(PDO::FETCH_COLUMN)
if ($u->fetchColumn()) { //返回一个字符串
//登录成功
} else {
//验证失败
}
?>

2.取得一个标准对象还可以将取得的一行作为一个标准类stdClass的对象实例,其中列名=属性名。

<?php
$res = $db->query(“SELECT * FROM foo”);
while ($obj = $res->fetch(PDO::FETCH_OBJ)) {
// $obj == instance of stdClass
}
?>

3.存取为一个类 PDO允许将结果保存为一个类,例子如下:

<?php
$res = $db->query(“SELECT * FROM foo”);
$res->setFetchMode(
PDO::FETCH_CLASS,
“className”,
array(‘optional’=‘Constructor Params’)
);

while ($obj = $res->fetch()) {
// $obj == instance of className
}
?>

4.从一个类取得常量PDO允许查询的结果可以被用来生成一个目的类。

<?php
$res = $db->query(“SELECT * FROM foo”);
$res->setFetchMode(
PDO::FETCH_CLASS |
PDO::FETCH_CLASSTYPE
);

while ($obj = $res->fetch()) {
// $obj == instance of class who’s name is
// found in the value of the 1st column
}
?>

5.存取为一个对象PDO还允许获取数据到一个已经存在的对象。

<?php
$u = new userObject;
$res = $db->query(“SELECT * FROM users”);
$res->setFetchMode(PDO::FETCH_INTO, $u);
while ($res->fetch()) {
// 取得的记录集将放在$u这个对象变量中,在此显示
}
?>

6.存取为关联数据PDO实现了迭代器(Iteator)接口,允许一个方法实现迭代的功能。

[php]
<?php
$res = $db->query(
“SELECT * FROM users”,PDO::FETCH_ASSOC);

foreach ($res as $row) {
// $row是一个关联数组,可以直接显示,如$row['id']
}
?>
[/php]

7.fetchAll()方法PDO也提供了和ADODB类似的fetchAll()方法,它允许从一个结果集中取 得数据,然后放于关联数组中。

<?php
$db->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
$qry = “SELECT * FROM users”;
$res = $db->query($qry)->fetchAll(PDO::FETCH_ASSOC);
?>

或者获取到索引数组里:

<?php
$res = $db->query(“SELECT * FROM foo”);
$result_arr = $res->fetchAll();
print_r($result_arr);
?>

数字索引数组比较浪费资源,请尽量使用 关联数组,这样可以在内存中使用相当密集的大型结果集。
相关说明如下:

setAttribute()方法用于设置部分属性,主要属性有:PDO::ATTR_CASE、PDO::ATTR_ERRMODE等,这里需要 设置的是PDO::ATTR_CASE,就是使用关联索引获取数据集时,关联索引是大写还是小写,有如下几个选择:
&Oslash;PDO::CASE_LOWER——强制列名是小写;
&Oslash;PDO::CASE_NATURAL——列名按照原始的方式;
&Oslash;PDO::CASE_UPPER——强制列名为大写。

我们 使用setFetchMode方法来设置获取结果集的返回值的数据类型,类型有:
&Oslash;PDO::FETCH_ASSOC——关联数组形式;
&Oslash;PDO::FETCH_NUM——数字索引数组形式;
&Oslash;PDO::FETCH_BOTH——两种数组形式都有,这是默认的;
&Oslash;PDO::FETCH_OBJ——按照对象的形式,类似于以前的 mysql_fetch_object()。
当然,一般情况下,我们使用PDO::FETCH_ASSOC取得关联数组。 具体使用哪种类型,应按照自己的实际应用选择。

8.fetchColumn()方法如果想获取指定记录里的一个字段结果,则可以使用PDOStatement::fetchColumn()。

<?php
$rs = $db->query(“SELECT COUNT(*) FROM foo”);
$col = $rs->fetchColumn();
echo $col;
?>

一般使用fetchColumn()方法进行count统计,对某些只需要单字段的记录可以很 好地操作。
9.回调函数PDO还规定在每一个fetch模式下,经过处理后的结果中使用一个回调 函数。

<?php
function draw_message($subject,$email) { … }
$res = $db->query(“SELECT * FROM msg”);
$res->fetchAll(PDO::FETCH_FUNC,“draw_message”);
?>

10.直接查询的问题直接使用Query查询行每次都会直接提交给数据库,如果查 询较多,每次频频查询将导致效率降低。
另外,在安全问题上,没有过滤一些特殊 字符容易产生SQL注 入。

11.过滤字符下面我们来看看如何使用PDO进行过滤引起SQL注入的方法,即过滤特殊字符。我们在PDO中使用quote()方法,使用例子如下:

$query = “SELECT * FROM users WHERE
login=“.$db->quote($_POST[‘login’]).”
AND
passwd=“.$db->quote($_POST[‘pass’]);

12.事务处理PDO驱动程序支持所有的事务数据库,并且PDO提供更简便的方法,如下:

<?php
$db->beginTransaction();
if ($db->exec($qry) === FALSE) {
$db->rollback();
}
$db->commit();
?>

13.执行一个批处理事务在下面的示例中,假设我们为一个新雇员创建一组条 目,这个雇员有一个ID号, 即23。除了输入这个雇 员的基本数据外,还需要记录雇员的薪水。分别完成两个更新很简单,但通过将这两个更新包括在beginTransaction()和commit()调用中,就可以保证在更改完成之 前,其他人无法看到更改。如果发生了错误,catch块可以回滚事务开始以来发生的所有更改,并打印出一条错误消息。代码内容如下:

<?php
try {
$dbh = new PDO(‘odbc:SAMPLE’, ‘db2inst1′, ‘ibmdb2′,
array(PDO_ATTR_PERSISTENT => true));
echo “Connected/n”;
$dbh->setAttribute(PDO_ATTR_ERRMODE, PDO_ERRMODE_EXCEPTION);
$dbh->beginTransaction();
$dbh->exec(“insert into staff (id, first, last) values (23, ‘Joe’, ‘Bloggs’)”);
$dbh->exec(“insert into salarychange (id, amount, changedate)
values (23, 50000, NOW())”);
$dbh->commit();
} catch (Exception $e) {
$dbh->rollBack();
echo “Failed: ” . $e->getMessage();
}
?>

并不是一定要在事务中做出更新,也可以 通过复杂的查询来提取数据,还可以使用信息构建更多的更新和查询。当事务在活动时,可以保证其他人在工作进行当中无法做出更改。事实上,这不是100%的正确,但如果您之前没有听说过事务的 话,这样介绍也未尝不可。
下面是一个扩展PDO&PDO语句的类,内容如下:

<?php
class Database extends PDO{
function __construct()
{
parent::__construct(‘mysql:dbname=test;host=localhost’, ‘root’, ”);
$this->setAttribute(PDO::ATTR_STATEMENT_CLASS,array(‘DBStatement’array($this)));
}
}

class DBStatement extends PDOStatement{
public $dbh;
protected function __construct($dbh)
{
$this->dbh = $dbh;
$this->setFetchMode(PDO::FETCH_OBJ);
}

public function foundRows()
{
$rows = $this->dbh->prepare(‘SELECT found_rows() AS rows’,array(PDO::MYSQL_ATTR_ USE_BUFFERED_QUERY => TRUE));
$rows->execute();
$rowsCount = $rows->fetch(PDO::FETCH_OBJ)->rows;
$rows->closeCursor();
return $rowsCount;
}
}
?>

14.存储过程在MySQL一章,我们已经了解了存储过程的创建,下 面我们来看使用PDO调 用的例子:

<?php
$stmt = $dbh->prepare(“CALL sp_set_string(?)”);
$stmt->bindParam(1, $str);
$str = ‘hospinfo’; //绑定参数
$stmt->execute();
?>

与先前讲过的绑定例子差不多,只是这里 使用了“?”数据绑定方 法,sp_set_string是存储过程名称。
带有输出参数的存储过程:

<?php
$stmt = $dbh->prepare(“CALL sp_get_string(?)”);
$stmt->bindParam(1, $ret,PDO:ARAM_STR, 4000);
if ($stmt->execute()) {
echo “返回值 $ret/n”;
}

下面是绑定列输出的脚本例子:
$stmt = $dbh->prepare(“SELECT extension, name from CREDITS”);
if ($stmt->execute()) {
$stmt->bindColumn(‘extension’, $extension);
$stmt->bindColumn(‘name’, $name);
while ($stmt->fetch(PDO::FETCH_BOUND)) {
echo “Extension: $extension/n”;
echo “Author: $name/n”;
}
}
?>

17.4 ADODB

PHP在数据库的支持上是很令人称道的,几乎所有的知名数据库系统都有对应的函数库支持,而且支持得很完整。但遗憾的是,每一群数据库 支持函数无论在名称或参数结构上,都有很大的差异,这使得PHP的系统开发者在面临更换数据库时,总会觉得痛苦万分。

难道这个问题就没有解决方法吗?当然 有,答案就是将要介绍的ADODB这个PHP类库。
ADODB提供了完整的方法和属性,可以用来控制数据库系统,更棒的是你只要记得它的功能即可,因为不同的数据库系统,只要修改一个属性 值,ADODB就会自动 依据设定取用正确的PHP函 数。

此外,再配合数据库系统修改SQL命令,这样PHP系统就可以在最短的时间内更换到另一个数据库 系统;如果在编写程序时,对SQL命令能做妥善规划,那就更快了。
经过以上的介绍,相信你已经对ADODB的功用有所了解了,以下为ADODB的详细介绍。
下载ADODB:可以在http://adodb.sourceforge.net/ 取得最新版的ADODB(见图17-2)。

图17-2
ADODB的最新版本是adodb495a版,根据需要,可以下载完整版本的ADODB。如果您主要使用MySQL,则可以下载ADODB的lite版本,减少体积和一些不必要的系统开销。

17.4.1 使用ADODB

由于PHP的数据库存取函数没有标准化,所以需要一组函 数库或类别来隐藏不同数据库函数界面间的差异。可以实现相对简单的数据库系统移植,这就是ADODB抽象层要实现的目标。

ADODB目前支持的数据库系统MySQL、Oracle、MS SQL Server、Sybase/Sybase SQL Anywhere、Informix、PostgreSQL、FrontBase、Interbase(Firebird及Borland版本)、Foxpro、Access、ADO和ODBC连接。
与其他数据库抽象层相比,其他类大多集 中在处理与SELECT操 作有关的内容,而ADODB对 于INSERT及UPDATE操作也提供额外的支持,并且可以很快 连接到多类数据库,提供的统计方法,在底层实现字串连接及字串标记变量的差异处理(在某些数据库中,字串的连接和标记符号是有差异的)。

字段类型对照系统是ADODB抽象层的特性之一,所以我们可以描述像CHAR、TEXT(而Oracle是Clob类型)及STRING这样的通用字段类型,而不必理会采用的 数据库使用的是哪种类型,它会在底层替我们做对应和转换。
因此使用ADODB开发,对于系统来说移植相对变得容易,因 为所有与数据库相依存的程序代码被都隐藏在后端,使用者不再需要去移植类别里的逻辑。

ADODB拥趸者很多,很多著名的开源软件:PostNuke、phpWiki、Mambo、eGroupware等,都使用ADODB作为数据库抽象类库。

17.4.2 ADODB安装

首先要将全部文件解压缩到Web服务器目录里,如lib/adodb。要测试ADODB则需要一个数据库,打开testdatabase.inc.php 这个 文件,并且修改连接参数,以适合你所使用的数据库。这个程序会在你的数据库中建立一个新的资料表,以支持我们提供的测试程序及范例。

17.4.3  启动ADODB

当要执行ADODB时,至少有两个文件要被加载进来,第一个 是 ADOdb.inc.php,这里面包含了所有数据库类中要被使用的函数。而对数据库操作的程序代码则被放置在ADOdb.inc.php文件里。

ADODB可以连接的名称及对应的数据库系统,如表17-1所示。
表17-1

下面我们来看怎么使用ADODB连接数据库。
1.连接MySQL数据库我们使用如下方法连接一个MySQL数据库:

require_coce(‘/libs/adodb/ADOdb.inc.php’);
$conn = &ADONewConnection(‘mysql’);

无论连接到一个什么类型的数据库,我们都要使用ADONewConnection()函数来创建一个连接对象。

ADONewConnection接收一个选择性参数:<database name>,用 以表示要连接的数据库。如果没有参数被指定,它将会使用ADOLoadCode() 内部方法所载入的最后一个数据库。
当建立好一个连接对象后,我们还没有真 正连接上数据库,需要使用$conn->Connect()或者$conn->PConnect() 两个 方法,任选其一来完成真正的数据库连接:
$conn -> Connect(DB_HOST,DB_USER,DB_PASSWORD,$database)
下面是创建一个mysql数据库连接对象,并创建一个数据库连 接:

$conn = ADONewConnection(‘mysql’); //创建一个ADODB连接对象
$mysql_obj = $conn->connect(‘localhost’,’root’,’passwd’,’dbname’); //连接到MySQL服务器
if (!$ora_obj) {
echo $mysql_conn->ErrorMsg(); //显示连接的错误信息
exit();
}else{
$mysql_conn -> SetFetchMode(3);
}
Var_dump($mysql_obj);

这样我们就创建好与本地MySQL服务器的连接了。可以看到,ADODB连接数据库同样很方便。
2.连接Oracle数据库

如果 要连接Oracle数据 库该如何写呢?结合表17-1,我们可以编写脚本如下:

require_once (“libs/adodb/adodb.inc.php”);
$ora_conn = &ADONewConnection(‘oci8′);
$ora_obj = $ora_conn -> Connect(‘localhost’,'oracle’,'oracle’,'oradb’);
if (!$ora_obj) {
echo $ora_conn->ErrorMsg();
exit();
}else{
$ora_conn -> SetFetchMode(3);
}
Var_dump($ora_conn);

注意:连接Oracle数据库需要在PHP本地环境安装好Oracle的客户端驱动,以及连接文件设置。

其中,SetFetchMode(3)指的是取得记录集 时,返回的是二维关联数组。也可以使用字符形式,这样更明了一些:
$mysql_conn->SetFetchMode(ADODB_FETCH_ASSOC);

17.4.4  ADODB的查询方法

ADODB的查询方法如下。
1.直接查询

Excute()方法通过连接句柄执行SQL查询,并返回一个变量,如下:
$sql=’SELECT * FROM ice_cream WHERE flavor LIKE ?’;
$res = $cnx->Execute($sql,array(‘Chocolate’));

2.缓存查询

ADODB提供内置的缓存机制,CacheExecute()方法用于每次查询数据 时,会把相应的结果序列化后保存到文件中,以后同样的查询语句就可以不用直接查询数据库,而是从缓存文件中获取,从而提高Web系统的性能。CacheExecute()方法的格式如下:
CacheExecute($ttc,$sql)
该方法以缓存的形式执行一段查询,其中$ttc为缓存的时间,请看如下代码:

$ADODB_CACHE_DIR = “/var/tmp/adodb_cache”; //缓存保存的文件目录
$sql = “SELECT surname, age FROM employees”; //构造一个查询
$rs = &$db->CacheExecute(600,$sql);
// 缓存式查询,缓存将被保存600秒后更新

3.清除缓存CacheFlush()

该方 法用来清除所有ADODB数 据库的缓存。
17.4.5 移动记录集

记录集(Record Set):从执行的方法中返回指定的 记录,使用moveFirst()、moveLast()、moveNext()和move($n)方法访问记录集中的一个指定记录。例如:

[php]
$rs=$cnx->Execute(‘SELECT flavor,price FROM ice_cream’);
$rs->MoveLast();
print “Flavor “.$rs->fields[0].” costs ”
.$rs->fields[2].”/n”;
$rs->MoveFirst();
//省略…
$rs->Move(2);
//省略…
$rs->MoveNext();
[/php]

设置一个全局变量$ADODB_FETCH_MODE,从ADODB_FETCH_NUM(或从ADODB_FETCH_ASSOC返回)中取得 数字索引(或关联索引)数组。例如:

[php]
$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC ;
$rs=$cnx->Execute(‘SELECT flavor,price FROM ice_cream’);
print “<table>/n”;
while (! $rs->EOF) {
print “<tr><td>”.$rs->fields['flavor']}.”</td>”.
“<td>”.$rs->fields['price']“.”</td></tr>/n”;
$rs->MoveNext();
}
print “</table>/n”;
[/php]

FetchObject()方法也是一个记录集变量。

17.4.6 使用ADODB生成HTML

ADODB的rs2html($res)函数,它提供一个简单的方法,从$res变量中的记录自动生成HTML表格。

它在tohtml.inc.php中,使用时需要包含 进来,如下代码:
require ‘adodb/tohtml.inc.php’;
//引入rs2html函数
require ‘adodb/adodb.inc.php’;
//省略…
$rs = $cnx->Execute(‘SELECT flavor,calories,price FROM ice_cream’);
rs2html($rs);
使用rs2html($res,’CLASS=”myClass”)这种格式,表 示指定表格使用的myClass的样式表类进行显示。

17.4.7 使用ADODB进行分页

ADODB_Pager(该方法定义在adodb-pager.inc.php中)提供一种简单分页显示记录的方法。
require ‘adodb/adodb.inc.php’;
require ‘adodb/adodb-pager.inc.php’;
$pager=new ADODB_Pager($cnx,”SELECT id,flavor,price FROM ice_cream_big”);
$pager->Render();
使用$pager->Render($num)来 设置每次显示的行数,默认值为10。

17.4.8  生成下拉选择菜单

getMenu($selectName)方法将帮助我们生成一个<select>标签,即从记录集中生成一个下拉菜单。其中,$selectName是该表单中select的name标签。例如:
$rs = $cnx->execute(‘SELECT flavor,id FROM ice_cream’);
print $rs->GetMenu(‘which_flavor’);
相关说明如下:
&Oslash;getMenu($sN,$selected,$blank,$mult,$nm):其中,$selected (string)为 默认选择的值/项。
&Oslash;$blank (boolean):默认初始值或布尔值为true时,下拉选项为空。
&Oslash;$mult (boolean):下拉菜单是否可以允许多选。
&Oslash;$nm (integer):设置菜单多重选择的属性,也就是默认几行。

17.4.9 ADODB开发实例

为了能更好地理解和使用ADODB,下面一起做一个完整的实例。
首先准备一个简单的表,名为“library”,用于保存书籍目录。结构与记录 内容如下:

mysql> SELECT * FROM library;
+—-+——————–+—————+
| id | title

| author
|
+—-+——————–+—————+
| 14 | ASP.NET 2.0网络编程 | Dennis Lehane  |
| 15 | For Kicks
| Dick Francis   |
| 16 | XML and PHP

| Vikram Vaswani |
| 17 | Where Eagles Dare
| Jack Higgins   |
+—-+——————–+—————-+

在library表中,包含3个字段,分别为:
&Oslash;id——主键,用于保存书的序列号;
&Oslash;title——用于保存书的标题;
&Oslash;author—— 用于保存作者信息。
下面看使用PHP的传统开发方式,即使用PHP的MySQL API库进行编写的代码,如下:

[php]
<?php
// 创建一个数据库连接
$connection = mysql_connect(“localhost”, “root”, “passwd”) or die (“Unable to connect!”);
// 选择数据库
mysql_select_db(“adodb”) or die (“Unable to select database!”);
// 构建SQL和开始查询
$query = “SELECT * FROM library”;
$result = mysql_query($query) or die (“Error in query: $query. ” . mysql_error());
// 遍历记录集,并显示字段的内容
while ($row = mysql_fetch_row($result)){
echo “$row[1] – $row[2]/n”;
}
// 显示返回的记录行数
echo “/n[" . mysql_num_rows($result) . " 行记录被返回]/n”;
// 关闭数据库连接
mysql_close($connection);
?>
[/php]

输出结果如下:
ASP.NET 2.0网络编程- Dennis Lehane
For Kicks – Dick Francis
XML and PHP – Vikram Vaswani
Where Eagles Dare – Jack Higgins

[4 行记录被返回]
可以看到此例子比较简洁,连接到数据 库,执行查询,取回结果后显示出来。
上述例子用mysql_fetch_row()的抽取功能,取 得记录后生成一个连续整数的索引数组,数据内容分别对应数据表中的字段。与此相似的还有mysql_fetch_assoc(),它生成一个关联数组,或者mysql_fetch_object函数,它生 成一个对象(其属性相当于字段名)。
这段代码是没有任何问题的,但如果我们 想从MySQL迁移到PostgreSQL数据库或者Oracle怎么办呢?代码需要重写了!这时,就 需要数据抽象层ADODB上 场了。
我们把上例改用ADODB重新编写,请看下面的代码:

[php]
<?php
include_once(“libs/adodb/adodb.inc.php”);
// 创建一个MySQL连接对象
$db = NewADOConnection(“mysql”);
// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “passwd”, “adodb”) or die(“Unable to connect!”);
// 执行一个查询
$query = “SELECT * FROM library”;
$result = $db->Execute($query) or die(“Error in query: $query. ” . $db->ErrorMsg());
// 遍历返回的记录集内容,打印返回列的内容:TITLE 和AUTHOR
while (!$result->EOF) {
echo $result->fields[1] . ” – ” . $result->fields[2] . “/n”;
$result->MoveNext();
}
// 显示返回的记录行数
echo “/n[" . $result->RecordCount() . " 行记录被返回]/n”;
// 关闭数据库连接
$db->Close();
?>
[/php]

这个程序和上面程序的实现功能是一样 的,但是由于采用ADODB抽 象层,而没有采用PHP的 本地API(mysql或mysqli扩展),因此可以灵活地更换数据库系 统,无论哪个数据库以及如何更换,这段代码都是可用的。
下面对上述代码进行详细的解释说明。
首先,我们引用ADODB主类文件:

<?php
include_once(“libs/adodb/adodb.inc.php”);

实际上,ADODB库并非只有这一个文件,事实上有超过30个不同的文件,负责驱动不同的数据库系统。我 们不用担心是否完全将类包含完全,该文件会自动识别和匹配当前的PHP版本及数据库系统,并自动包含相关的类库文件。
然后,创建ADODB的对象实例:

// 创建一个mysql连接对象
$DB = NewADOConnection(“mysql”);
通过对象的构造参数,告诉ADODB连接数据库,这里连接的是MySQL数据库服务器,对于其他数据库,还可以 相应地使用”pgsql”或”oci8″。可以连接的数据库及连接标识参见表17-1。

接下来,连接数据库,这是通过Connect()方法完成的,必须设置一套连接 参数:
// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “passwd”, “adodb”) or die(“Unable to connect!”);
以上代码试图打开一个MySQL连接,3个参数分别为主机名、用户名和密码。
一旦Connect()方法建立连接后,就可以调用该对 象的Execute()方 法进行SQL数据库查 询。
// 构造并执行一个查询
$query = “SELECT * FROM library”;
$result = $db->Execute($query) or die(“Error in query: $query. ” . $db->ErrorMsg());
当查询执行成功后,则返回含有查询结果的对象。特别说明的是errormsg()方法,可以用来获取查询的一个错误信息。
从返回的结果集中循环取值:
// 遍历返回的记录集内容,打印返回列的内容:TITLE 和AUTHOR
while (!$result->EOF) {
echo $result->fields[1] . ” – ” . $result->fields[2] . “/n”;
$result->MoveNext();
}
在这种情况下,在循环体内,使用对象的movenext()方法进行记录的移动,每次循环打印后就移动到下一条记录。
取得记录集后,可以使用recordcount()方法取得记录的条数。
// 遍历返回的记录集内容,打印返回列的内容:TITLE 和AUTHOR
echo “/n[" . $result->RecordCount() . " 行记 录被返回]/n”;
最后,使用Close()方法关闭数据库的连接。
// 关闭数据库连接
$db->Close();
如果有人决定要更换数据库,唯一需要改变的是将上述脚本的连接改为新的数据库名称和主机地址即可。
这就是数据抽象层最好的优势之一,它提供一些尽可能通用的方法,将与数据库打交道的代码隐藏在内部,从而使PHP代码简单、清爽、可维护性好,并且缩短开发周 期,以及拥有软件包的整体感。ADODB还提供了一些不同的方法处理结果记录 集。例如,我们可以取回结果集为一个关联数组。请看如下脚本:

[php]
<?php
include_once(“libs/adodb/adodb.inc.php”);
// 创建一个mysql连接实例对象
$db = NewADOConnection(“mysql”);
// 打开一个数据库连接
$db->Connect(“localhost”, “john”, “doe”, “adodb”) or die(“Unable to connect!”);
// 将结果保存为一个关联数组,也可以使用数值3
$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
// 构造并执行一个查询
$query = “SELECT * FROM library”;
$result = $db->Execute($query) or die(“Error in query: $query. ” . $db->ErrorMsg());
// 遍历记录集,显示列的内容:TITLE 和AUTHOR
while (!$result->EOF) {
echo $result->fields['title'] . ” – ” . $result->fields['author'] . “/n”;
$result->MoveNext();
}
// 取得和显示返回的记录行数
echo “/n[" . $result->RecordCount() . " 行记录被返回]/n”;
// 关闭数据库连接
$db->Close();
?>
[/php]

在这个例子里,$ADODB_FETCH_MODE的值用于确定ADODB如何构建结果记录集。
你也可以把取得的行转变为一个对象,其 属性相当于字段名,这就是ADODB的FetchNextObject()方法,请看如下脚本例子:

[php]
<?php
include(“libs/adodb/adodb.inc.php”);
// 创建一个mysql连接对象实例
$db = NewADOConnection(“mysql”);
// 打开一个数据库连接
$db->Connect(“localhost”, “john”, “doe”, “adodb”) or die(“Unable to connect!”);
// 构造并执行一个查询
$query = “SELECT * FROM library”;
$result = $db->Execute($query) or die(“Error in query: $query. ” . $db->ErrorMsg());
// 遍历记录集,显示列的内容:TITLE 和AUTHOR
while ($row = $result->FetchNextObject()) {
echo $row->TITLE . ” – ” . $row->AUTHOR . “/n”;
}
// 取得和显示返回的记录行数
echo “/n[" . $result->RecordCount() . " 行记录被返回]/n”;
// 关闭数据库连接
$db->Close();
?>
[/php]
值得一提的是,FetchNextObject()方法可以自动 在记录集中移动到下一行,我们不需要再使用MoveNext()方法,当到达记录集尾部时,该方法返回false。
1.GetAll方法我们可以使用GetAll方法代替Execute()方法,该方法返回的结果为一个二维关联数据,这样可以使用foreach或for循环语句处理,非常方便。另外,GetAll取得的数组与Smarty模板的foreach配合得非常好。
我们一起看下面的脚本例子:

[php]
<?php
include_once(“libs/adodb/adodb.inc.php”);
// 创建一个mysql连接实例对象
$db = NewADOConnection(“mysql”);
// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “root”, “library”) or die(“Unable to connect”);
// 构造并执行一个查询
$query = “SELECT * FROM library”;
$result = $db->GetAll($query) or die(“Error in query: $query. ” . $db->ErrorMsg());
// 清除无用的对象
$db->Close();
// 可以使用print_r打印该数组的内容
// print_r($result); exit(0);
// 遍历记录集,显示列的内容:TITLE 和AUTHOR
foreach ($result as $row){
echo $row[1] . ” – ” . $row[2] . “/n”;
}
// 取得和显示返回的记录行数
echo “/n[" . sizeof($result) . " 行记录被返回]/n”;
?>
[/php]
GetAll()方法取得记录集后,产生一个二维数组,类似于下面的样子:
Array
(
[0] => Array
(
[0] => 14
[id] => 14
[1] => Mystic River
[title] => Mystic River
[2] => Dennis Lehane
[author] => Dennis Lehane
)

[1] => Array
(
[0] => 15
[id] => 15
[1] => For Kicks
[title] => For Kicks
[2] => Dick Francis
[author] => Dick Francis
)
//下略
)
我们在数组一章,提到过这类混合数组最 适合用foreach来 处理。这种方法是对Execute()方法的补充或替代,尤其适合在遍历查询整个表时使用。
另外,ADODB还提供取得一条记录的方法:GetOne()。
2.GetOne()方法ADODB有个比较直接的方法可以比较方便地检测 某条记录是否存在,那就是它的GetOne($sql)方法。
该方法返回查询记录的第1条第1个字段名的值,如果执行过程中出现错误,则返回布 尔值false。
我们可以检测这个值是否存在:

<?php
Include_once(“libs/adodb/adodb.inc.php”);
// 创建一个mysql连接实例对象
$db = NewADOConnection(“mysql”);
// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “passwd”, “adodb”) or die(“Unable to connect!”);
$rs = $db->GetOne(“SELECT * FROM library WHERE id=’$id’”);
if($rs){
echo ‘记录存在’;
}else {
echo ‘记录不存在’;
}
?>

不过这样有一个问题是,如果数据表中id=$id的记录有多条,不仅仅要知道是否存在 有这样一条记录,还要把这条记录提取出来,则可以使用ADODB的GetRow()方法。
3.GetRow()方法

<?php
Include_once(“libs/adodb/adodb.inc.php”);
// 创建一个mysql连接实例对象
$db = NewADOConnection(“mysql”);
// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “passwd”, “adodb”) or die(“Unable to connect!”);
$rs = $db->GetRow(“SELECT * FROM library WHERE id=’$id’”);
if(is_array($rs)){
echo ‘记录存在’;
print_r($rs);
} else {
echo ‘记录不存在’;
}
?>

需要注意的是,GetOne($sql) 和 GetRow($sql) 都能得到一条特定的 记录,或者得到该记录不存在的信息,但是如果符合查询条件的记录存在多条时,则这两个方法只传回第一条记录,其他的都自动抛弃。
如果只要得到查询结果的行数,则可以使 用结果集方法中的RecordCount()方法。
4.取得返回的记录行数ADODB还提供了一批实用功能,如在进行查询时,提供了非常有用的RecordCount() 和FieldCount()方法,分别返回记录的数量和字段的数量,以下是应用 这两个方法的例子。

<?php
include(“libs/adodb/adodb.inc.php”);
// 创建一个mysql连接实例对象
$db = NewADOConnection(“mysql”);
// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “passwd”, “adodb”) or die(“Unable to connect!”);
// 构造并执行一个查询
$query = “SELECT * FROM library”;
$result = $db->Execute($query) or die(“Error in query: $query. ” . $db->ErrorMsg());
// 取得和显示返回的记录行数
echo $result->RecordCount() . ” 行记录被返回/n”;
// 取得和显示返回的字段个数
echo $result->FieldCount() . ” 个字段被返回/n”;
// clea up
$db->Close();
?>

我们可以使用FetchField()方法取得字段的信息,其 中含有该字段的详细资料,包括名称和类型等,请看如下的脚本例子。

<?php
include(“libs/adodb/adodb.inc.php”);
// 创建一个mysql连接实例对象
$db = NewADOConnection(“mysql”);
// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “passwd”, “adodb”) or die(“Unable to connect!”);
// 构造并执行一个查询
$query = “SELECT * FROM library”;
$result = $db->Execute($query) or die(“Error in query: $query. ” . $db->ErrorMsg());
// 取得记录集中字段的结构信息
for($x=0; $x<$result->FieldCount(); $x++){
print_r($result->FetchField($x));
}
// 清理无用的对象
$db->Close();
?>

下面输出的是有关id字段的结构信息。
stdClass myMagicbject
(
[name] => id
[table] => library
[def] =>
[max_length] => 3
[not_null] => 1
[primary_key] => 1
[multiple_key] => 0
[unique_key] => 0
[numeric] => 1
[blob] => 0
[type] => int
[unsigned] => 1
[zerofill] => 0
[binary] =>
)
5.其他相关方法当执行一个INSERT查询时,如果该表的主键是一个自动增量 的字段,则可以使用ADODB的insert_id()方法,来获得最后数据插入时自动产生的增量值。

<?php
include_once(“libs/adodb/adodb.inc.php”);
// 创建一个mysql连接实例对象
$db = NewADOConnection(“mysql”);
// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “root”, “adodb”) or die(“Unable to connect!”);
// 构造并执行INSERT插入操作
$title = $db->qstr(“PHP5与MySQL5 Web开发技术详解”);
$author = $db->qstr(“杜江”);
$query = “INSERT INTO library (title, author) VALUES ($title, $author)”;
$result = $db->Execute($query) or die(“Error in query: $query. ” . $db->ErrorMsg());
// 显示插入的记录号
if ($result){
echo “最后插入的记录ID: ” . $db->Insert_ID();
}
// 清理无用的对象
$db->Close();
?>

脚本中的qstr()方法,功能是过滤SQL查询中的非法字符。

执行后,即无论查询(SELECT)、删除(DELETE)或修改(UPDATE)数据,如果想知道是否对表有影响,可以使用affected_rows()方法,它可以告诉我们操作后有多少(记录)行受到了影响。 请看下面的脚本例子:

<?php
include_once(“libs/adodb/adodb.inc.php”);
// 创建一个mysql连接实例对象
$db = NewADOConnection(“mysql”);
// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “root”, “adodb”) or die(“Unable to connect!”);
// 构造并执行一个查询
$query = “DELETE FROM library WHERE author = ‘J. Luser’”;
$result = $db->Execute($query) or die(“Error in query: $query. ” . $db->ErrorMsg());
// 取得和显示执行后影响的记录行数
if ($result){
echo $db->Affected_Rows() . ” 行已被删除”;
}
// 清理无用的对象
$db->Close();
?>

6.限制查询结果上面我们讨论了如何通过使用一个数据库库函数使应用程序更简洁,更易于移植。比如从MS SQL Server转移到MySQL,在MS SQL Server中使用指令“SELECT TOP 15 name FROM employee”取得数据的前15条,可在MySQL中却不支持这种写法,而要写成:SELECT name FROM employee LIMIT 15。
它似乎对我们敲响了警钟,应该停止在查询语句中使用非标准SQL指令,而去认真地学习标准的SQL。
幸运的是,ADODB有一个处理 LIMIT的方法:SelectLimit(),这样我们就根本不用管连接的是MySQL还是MS SQL Server,ADODB会在底层为我们自动转换,请见下面的脚本例子:

[php]
<?php
include_once(“libs/adodb/adodb.inc.php”);
// 创建一个mysql连接实例对象
$db = NewADOConnection(“mysql”);
// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “passwd”, “adodb”) or die(“Unable to connect!”);
// 构造并执行一个查询
// 我们要取得5行记录,从符合记录的第3行开始取
$query = “SELECT * FROM library”;
$result = $db->SelectLimit($query, 5, 3) or die(“Error in query: $query. ” . $db->ErrorMsg());
// 遍历记录集
while (!$result->EOF) {
echo $result->fields[1] . ” – ” . $result->fields[2] . “/n”;
$result->MoveNext();
}
// 清理无用的对象
$db->Close();
?>
[/php]
在这个例子中,selectlimit()方法类似于MySQL的LIMIT语句,可用于控制从某行开始查询,到某行 的结果,从而取得我们指定的记录集。
我们可以利用ADODB提供的MetaDatabases()方法取得当前服务器 中所有数据库的清单。还有一个方法和它很类似,即使用MetaTables()方法可以取得当前库中所有表的清单。请看下面的例子:

<?php
include(“libs/adodb/adodb.inc.php”);
// 创建一个mysql连接实例对象
$db = NewADOConnection(“mysql”);
// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “passwd”, “adodb”) or die(“Unable to connect!”);
// 取得数据列表
echo “数据库:/n”;
foreach($db->MetaDatabases() as $d){
echo “* $d/n”;
}
// 取得数据表清单
echo “/n当前数据库下的表:/n”;
foreach($db->MetaTables() as $table){
echo “* $table/n”;
}
// 清理无用的对象
$db->Close();
?>

7.快速存取有时,我们需要对一些不同的值做一些特殊的查询, 比如一系列的INSERT(插 入)语句。ADODB类 提供了两个方法,可以使我们既节约时间又节省系统的开销,请看如下示例:

[php]
<?php
include(“libs/adodb/adodb.inc.php”);
// 创建一个mysql连接实例对象
$db = NewADOConnection(“mysql”);
// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “passwd”, “adodb”) or die(“Unable to connect!”);
// 构造准备查询,使用参数绑定
$query = $db->Prepare(“INSERT INTO library (title, author) VALUES (?, ?)”);
// 从CSV 中取得要插入的标题和作者名称
$data = file(“./book_list.csv”);
// 遍历该文件,并执行插入操作
foreach ($data as $l){
$arr = explode(“,”, $l);
// 插入值并绑定准备语句
$result = $db->Execute($query, array($arr[0], $arr[1])) or die(“Error in query:$query. ” . $db->ErrorMsg());
}
// 清理无用的对象
$db->Close;
?>
[/php]
prepare()函数,把一个SQL查询作为参数,读取一个查询,但并不立即执行。prepare()返回一个句柄给一个prepare查询,当保存和传递给Execute()方法后,则立即执行该查询。
8.处理事务处理事务是许多应用程序的一个重要的特征(比如, 钱从你的账户转出,然后转入到某个人的账户中。只要其中任意一步操作失败,这整个过程都必须被认定为失败。不然,钱被划出,而没有进对方的账户;或者,钱 没有划出,但对方账户无端多了一笔钱)。
处理事务可以在代码级上进行机警地管理控 制。常数错误检查被用来判断执行COMMIT(事务的所有各项都正确,执行正确, 结束事务)还是执行ROLLBACK(事务中有错误,所有改动需要恢复原来状况)。
现在的数据库系统绝大多数都支持事务, 如MySQL、Oracle、MS SQL Server等,ADODB提供一个非常好的功能,能够让你更透明 地使用这一特性。请看下面的例子:

<?php
include(“libs/adodb/adodb.inc.php”);
// 创建一个mysql连接实例对
$db = NewADOConnection(“mysql”);
// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “root”, “adodb”) or die(“Unable to connect!”);
//关闭auto-commit自动提交事务
// 开始事务处理语句块
$db->BeginTrans();
// 第一次查询
$query = “INSERT INTO library (title, author) VALUES (‘测试用书’, ‘佚名’)”;
$result = $db->Execute($query) or die(“Error in query: $query. ” . $db->ErrorMsg());
//使用第一次查询返回的ID号
if ($result){
$id = $db->Insert_ID();
$query = “INSERT INTO purchase_info (id, price) VALUES ($id, ‘RMB 31.9′)”;
$result = $db->Execute($query) or die(“Error in query: $query. ” . $db->ErrorMsg());
}
// 如果操作成功
if ($result){
// 事务提交
$db->CommitTrans();
}// 否则回滚
else{
$db->RollbackTrans();
}
// 清理无用的对象
$db->Close;
?>

该脚本首先需要关掉数据库的auto commit功能,通过begintrans()方法来处理,这种方法也 标志着一个事务的开始。可以使用CommitTrans()或RollbackTrans()函数来处理操作,一旦auto commit已经关掉,你就可以任意执行所需要的查询,确认事务的查询执行无误并完毕后,由我们自己决定何时执行commit操作。
每一次执行Execute()事务块后,它会返回一个布尔值, 告诉我们是否成功地执行了查询。可以跟踪到这个值,以及使用的时间,以决定是否要进行整个交易行为。一 旦你相信一切都没问题,则告诉数据库committrans()方法;如果发现有错误发生,则可以进行回滚操作——执行rollbacktrans()方法。
值得注意的是,注意您的数据库类型是否 支持这些事务函数,前面已经说过,MySQL的InnoDB类型表支持事务,但是MyISAM类型并不支持。
9.使用缓存查询在一个动态页面中,如果其中的一个查询指令很少改 变且频繁被执行,我们则可以使用ADODB的缓存功能,可以将查询指令的结果缓存成静态文件,从而提高PHP脚本的性能。
当试图通过缓存来提高你的应用程序的性 能之前,建议先去优化查询指令再开始本操作,这样才会起到事半功倍之效果。
ADODB最棒的功能就是提供查询缓存的功能。缓存可以大大改善应用程序的性能,尤其是网站系统,因为大部分用户都是在浏览网站,数据库完 成的任务多半是查询(SELECT操作)。为了更好地理解与应用缓存查询的功能,我们来看下面的脚本例子。

[php]
<?php
include_once(“libs/adodb/adodb.inc.php”);
// 创建一个mysql连接实例对象
$db = NewADOConnection(“mysql”);
// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “root”, “adodb”) or die(“Unable to connect!”);
// 构造并执行一个查询
$query = “SELECT * FROM library”;
$result = $db->Execute($query) or die(“Error in query: $query. ” . $db->ErrorMsg());
// 遍历返回的记录集,显示列数据的内容 TITLE 和 AUTHOR
while (!$result->EOF) {
echo $result->fields[1] . ” – ” . $result->fields[2] . “/n”;
$result->MoveNext();
}
// 显示取得的记录行数
echo “/n[" . $result->RecordCount() . " 行记录被返回]/n”;
// 关闭数据库连接
$db->Close();
?>
[/php]
这段代码使用ADODB进行一个SELECT操作。比如说,这就是您的网站,平均有 每分钟5000次的点击 (PV,Page View)量,那么数据库系统每小时至 少要被查询3万次以上, 可以想象,这对我们的MySQL数据库的负载是相当繁重的。
因此ADODB提供了缓存的功能,可以将经常查询的结果 保存起来,进而降低数据库服务器的负荷,同时也向用户提供更快速的内容响应。
下面是修改上面的脚本,改为使用CacheExecute来进行缓存查询的示例:

[php]
<?php
include(“libs/adodb/adodb.inc.php”);

//设置缓存保存的路径,.表示当前目录
$ADODB_CACHE_DIR = ‘.’; //为了管理方便,实际开发环境请指向到独立的目录中,如/tmp/adodb
// 创建一个mysql连接实例对象
$db = NewADOConnection(“mysql”);

// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “passwd”, “adodb”) or die(“Unable to connect!”);

// 构造并执行一个查询
$query = “SELECT * FROM library”;
$result = $db->CacheExecute(300,$query) or die(“Error in query: $query. ” . $db->ErrorMsg());

// 遍历返回的记录集,显示列数据的内容 TITLE 和 AUTHOR
while (!$result->EOF) {
echo $result->fields[1] . ” – ” . $result->fields[2] . “/n”;
$result->MoveNext();
}

// 取得和显示返回的记录行数
echo “/n[" . $result->RecordCount() . " 行记录被返回]/n”;

// 关闭数据库连接
$db->Close();
?>
[/php]
CacheExecute()方法的第一个参数是缓存文件(缓存文件被命名为adodb_*.cache)将被保留的时间,以秒计时;第二个参数是SQL声明。第一个参数是可选择的,若没有限定时间,默认值是3600秒,也就是1个小时。
值得一提的是,使用CacheExcute()方法时,需要将php.ini中的参数magic_quotes_runtime设为0。
也可以根据需要,在程序运行时动态修改 它的值:
set_magic_quotes_runtime(0);

注意:将上述代码放到调 用数据库的指令之前,我们还可以在任何时候,通过调用CacheFlush()来清除过时的缓存。

17.5 PHPLib

PHPLib可能是伴随PHP一同成长最老的数据库抽象层(但和ADODB相比,它只算是一个MySQL抽象类库),当前最新版本为7.4a。我们只需要它的数据库访问功能,除了支持MySQL外,它也同时支持访问Oracle 8以上版本的数据库。
PHPLib访问SQL数据的类名为DB_Sql,包含在db_mysql.inc.php文件中。我们要使用的仅是它的数据库抽象类,直接用require()和include()包含进来就可以进行开发了。下面是开发实例,并且所用类库都是最新版本,这些都可以在光盘中找到。

17.5.1 使用PHPLib查询

首先定义一个配置文件config.inc.php,用于扩展PHPLib的MySQL类:DB_Sql。代码内容如下:

<?php
/*** 系统路径,可根据需求更改路径 */
define(‘ROOT’, ‘./’);

/*** PHPLib library path */
define(‘DB_HOST’, ‘localhost’);
define(‘DB_USER’, ‘root’);
define(‘DB_PASSWORD’, ‘root’);
define(‘DB_NAME’, ‘phpdev’);
require_once(ROOT.”lib/db.inc.php”);
$db = new DbClass;

/*
设置字符集,如utf-8和gbk、Latin1等,根据数据库的字符集而定,如果是Latin1,则可以不需要运行

*/
$sql = “SET NAMES ‘utf8′”;
$db->query($sql);

?>

上面代码引用的db.inc.php用于扩展DB_Sql类,主要是重新声明连接的数据库和账 号,以及出错信息的处理和显示功能,如下所示。

<?php
require_once(“db_mysql.inc.php”);
class DbClass extends DB_Sql{
public $Host;
public $Database;
public $User;
public $Password;
function __constructor()
{
$this->Host = DB_HOST;
$this->Database = DB_NAME;
$this->User = DB_USER;
$this->Password = DB_PASSWORD;
}

function halt($msg)
{
printf(“</td></table><b>Database Error:</b> %s<br>/n”, $msg);
printf(“<b>MySQL Error</b>: %s (%s)<br>/n”,
$this->Errno, $this->Error);
printf(“Please contact administrator and report the “);
printf(“exact error message.<br>/n”);
die(“Session halted.”);
}
}
?>

我们还以symbols表为例,查询该表的数据,然后全部显 示出来。

<html>
<head>
<title>国家代表动物</title>
</head>
<body>
<?php
require_once(‘lib/config.inc.php’);
$sql = “SELECT * from symbols”;
//也可以使用该方法执行INSERT、UPDATE、DELETE等SQL查询
$db->query($sql);
?>
<table cellpadding=10 border=1>
<tr>
<?php
while($db->next_record()) {
$country = $db->f(“country”);
$animal = $db->f(“animal”);
$cname = $db->f(“cname”);
echo “<tr>”;
echo(“<td><b>国家:</b> </td><td>$country </td><td><b>代表动物:</b> </td><td>$animal ($cname) </td>/n”);
echo “</tr>”;
}
?>
</tr>
</table>
</body>
</html>

显示结果如下:

关于$db->next_record(),这是 一个没有返回值的语句,它的功能是将当前表的指针向前移动并更新Record、Error、Row、Errno的方法。
while($db->next_record()) {

}
我们根据它的指针循环操作,直至到达尾 记录,若条件不满足,则while退出循环。
$name = $db->f(“name”);
给$name变量分配的是数据字段name的值。

echo(“<p><b>国家:</b> $country   <b>代表动物:</b> $animal ($cname)<br>/n”);
echo(“</p>/n/n”);
打印出一行数据,也可不用变量赋值,而 是直接引用方法返回的字段名即可,但是这种方法没有上面程序的可读性好。

while($db->next_record()) {
echo(“<p><b>国家:</b> “.$db->f(“country”).”   <b>代表动物:</b> ”
.$db->f(“animal”).”(“.$db-f(“cname”).”<br>/n”);
echo(“</p>/n/n”);
}

我们也可以开发一个函数,像ADODB或PDO那样,让抽出的记录生成一个二维关联数组。这 样,PHPLib同样也 可以与Smarty引擎 协同工作,并且工作更方便,请看如下代码。

[php]
<?php
function GetAll($sql){
global $db;
//全局PHPlib $db的实例
$db->query($sql);
$Array = array();
$j= 0;

while($db->next_record()) {
$Array[$j]= $db->Record;
$j++;
}

return $Array;
}
?>
[/php]
然后把该方法放在一个文件中,比如func.inc.php,同样保存在lib目录下。这样,我们再不用每次都重复使用while循环,直接调用该函数即可。

<?php
require_once(‘lib/config.inc.php’);
require_once(‘lib/func.inc.php’);
$sql = “SELECT * from symbols WHERE country=’China’”;
$getAnimal = GetAll($sql);
print_r($getAnimal);
?>

在浏览器上的显示结果如下:
Array
(
[0] => Array
(
[0] => 2
[id] => 2
[1] => China
[country] => China
[2] => dragon
[animal] => dragon
[3] => 龙
[cname] => 龙
)
)

GetAll方法产生的$getAnimal关联数组和ADODB的是一样的,这样,我们可以让PHPLib和Smarty匹配,把该数组抛给模板显示即可。
虽然没有ADODB、PDO那么强大,但PHPLib小巧方便,对于系统负载相对较小。如果 仍在旧版本下或虚拟主机环境下进行开发,使用它也是不错的选择。

17.6 小结

在本章中,我们一起了解了PDO及ADODB数据库类与抽象层在Web应用程序中的应用开发。其中包括记录集的概 念(resultsets), 如何连续获得表信息,如何使用PDO、ADODB进行优化查询,提高查询效率,以及结果缓存化的方法,如何输出HTML或其他格式文本的方法。
通过前面的介绍,相信您已经了解了数据 库抽象层的特点和重要性:良好且丰富的功能和方法,方便使用,良好伸缩性与可移植性。
下一 章,我们将要讨论PHP中热门且较实用的内容——页面模板化与Smarty技术。

10.生成下拉列表菜单ADODB特意为Web开发 任务提供几个通用的方法。其中,最有用的是GetMenu()方法,通过抽取数据库的记录集,自动地生成表单及菜单列表框。
下面介绍的就是从数据库动态构建下拉菜 单(Option)的例 子。

<html>
<head></head>
<body>
<?php
include_once(“libs/adodb/adodb.inc.php”);

// 创建一个mysql连接实例对象
$db = NewADOConnection(“mysql”);

// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “root”, “library”) or die(“Unable to connect!”);

// 构造并执行一个查询
$query = “SELECT title, id FROM library”;
$result = $db->Execute($query) or die(“Error in query: $query. ” . $db->ErrorMsg());

//显示HTML下拉列表菜单
echo $result->GetMenu(“library”, ”, false);

// 关闭数据库连接
$db->Close();
?>
</body>
</html>

GetMenu()方法需要传入参数,用来控 制列表框的行为。上例中第一个参数是列表框的名字(这个例子为“library”);第二个参数是显示时默认的值, 可以为空,从第一个记录开始;第三个参数,指定列表框的项目是否为空;第四个参数,控制是否允许用户多选。
上例的显示结果如下:
<select name=”library” >
<option value=”15″>Mystic River</option>
<option value=”16″>Where Eagles Dare</option>
<option value=”17″>XML and PHP</option>
</select>
可以看到,该列表菜单内容是从library表抽取的记录,列表框的名字为“library”,在记录集中,ID是菜单选项的值,名称为菜单框显示的元素。
由此可以看出,GetMenu()方法可以大幅度简化Web开发任务,大大减少代码量。
11.输出到文件ADODB还允许我们将记录输出为一个不同形式的文件:如逗号分隔符CSV文件,制表符表格,甚至于HTML形式的表格。
这些功能属于ADODB的附属功能,在使用时需要包含相关ADODB类文件,下面是样例的内容。

<?php
include(“libs/adodb/adodb.inc.php”);
// 包含转换方法的文件
include_once(“libs/adodb/toexport.inc.php”);
// 创建一个mysql连接实例对象
$db = NewADOConnection(“mysql”);
// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “passwd”, “library”) or die(“Unable to connect!”);
// 构造并执行一个查询
$query = “SELECT title, id FROM library”;
$result = $db->Execute($query) or die(“Error in query: $query. ” . $db->ErrorMsg());
// 返回一个CSV字符串
echo rs2csv($result);
// 关闭数据库的连接
$db->Close();
?>

输出结果如下:
title,id
Mystic River,15
Where Eagles Dare,16
XML and PHP,17
我们也可以去除结果中第一行,即字段的 名称,使用脚本格式如下:
// 返回一个 CSV 字符串
echo rs2csv($result, false);
脚本的输出结果将没有字段名称,如下:
Mystic River,15
Where Eagles Dare,16
XML and PHP,17
ADODB还提供生成制表符或分隔符文件功能,使用rs2tab()方法:

<?php
include(“libs/adodb/adodb.inc.php”);

// 包含转换方法的文件
include(“toexport.inc.php”);

//创建一个mysql连接实例对象
$db = NewADOConnection(“mysql”);

// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “root”, “library”) or die(“Unable to connect!”);

// 构造并执行一个查询
$query = “SELECT title, id FROM library”;
$result = $db->Execute($query) or die(“Error in query: $query. ” . $db->ErrorMsg());

// 返回一个TAB制表符分隔的字符串
echo rs2tab($result);

// 关闭数据库连接
$db->Close();
?>

显示结果如下:
title   id
Mystic River   15
Where Eagles Dare   16
XML and PHP   17
ADODB还提供生成HTML表格的功能,使用rs2html()方法:

<html>
<head></head>
<body>
<?php
include_once(“libs/adodb/adodb.inc.php”);

// 包含转换方法的文件
include_once(“libs/adodb/tohtml.inc.php”);

// 创建一个mysql连接实例对象
$db = NewADOConnection(“mysql”);

// 打开一个数据库连接
$db->Connect(“localhost”, “root”, “passwd”, “library”) or die(“Unable to connect!”);

// 构造并执行一个查询
$query = “SELECT title, id FROM library”;
$result = $db->Execute($query) or die(“Error in query: $query. ” . $db->ErrorMsg());

// 返回一个HTML格式的表格
echo rs2html($result);

// 关闭数据库连接
$db->Close();
?>
</body>
</html>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值