内容:
一、 什么是DB类
二、 为什么要设计抽象的中间数据层
三、 DB的使用入门
四、 DB_Common 使用参考
五、 更进一步,创建你自己的中间数据库应用层
六、 DB的不足
七、参考资源
关于作者
相关内容:
1、用PEAR来写你的下一个php程序
2、常用模块
3、使用PHPDoc轻松建立你的PEAR文档
潘凡 (nightsailer@hotmail.com)
北京赛迪网信息技术有限公司
2001 年 8 月
对于PHP的应用程序来说,90%以上需要和数据库来打交道。那么,你是如何操纵数据库的?当你的后端数据库升级或变迁后,你的这些程序是否能够随之平滑地升级和挂接呢?如果你正在考虑这个问题,那么不妨和我来讨论一下,如何使用PEAR中的DB类来创建与数据库无关的数据库应用层。
一、 什么是DB类
我们首先简单地了解一下DB类。DB类是PEAR中进行数据操作的几个类的集合,它的主要目的是提供一个统一的,抽象的数据接口,这个接口与后端的数据库是无关的。因此,如果你的应用程序使用这个通用的接口来进行数据库的操作,那么就能够平滑地切换到不同的数据库下面,如MYSQL,SQL,SYBASE等等。实际上,DB类希望能够起到简单的类似ODBC或者是PERL中的DBI的作用。说到这里,不得不提一下PHP中的另一个优秀的库:ADODB。ADODB也和DB一样,提供了一个抽象的中间层,而且ADODB所支持的后端数据库要比DB多(至少目前如此),不过ADODB没有直接使用PEAR的一些特性,只是吸取了PEAR的许多思想,包括DB,因此二者的使用方法有许多相似的地方。我不想评论二者孰优孰劣,大家可以根据个人的喜好来使用。
二、 为什么要设计抽象的中间数据层
在详细讨论DB的使用之前,我们先讨论一下为什么要设计中间的数据层,因为这意味着你需要作出一些牺牲和让步,比如,你需要多写一些代码,有的局限于特定数据库的特性将无法直接使用。
我们回忆一下我们过去的做法,如何连接到MYSQL数据库?这的确是个小儿科的问题,下面的代码你一定很熟悉:
<?php
/**
* 连接到MYSQL数据库
*/
$host = "localhost";
$user = "root";
$passwd = "";
$persistent = 1;
if ($persisternt){
$conn = mysql_connect($host,$user,$passwd);
}else {
$conn = mysql_pconnect($host,$user,$passwd);
}
?>
好了,现在建立了数据库连接,我们可以使用它来进行数据库的操作,我们可能使用类似的代码:
<?php
function sql_exec($sql) {
global $db_Name;
$result = mysql_db_query($db_dbName,$sql);
if (!$result) {
echo mysql_errno(). ": ".mysql_error(). "<br>$sql<br>";
exit();
}
return $result;
}
$db_Name = "test";
$sql = "select * from users";
$result = sql_exec($sql);
while( $row = mysql_fetch_row($result) ){
echo "姓名:$row[0] 性别:$row[1] 年龄 $row[2]<br>";
}
mysql_free_result($result);
?>
看起来很不错,是吗?你可能在你的代码里使用很多类似的代码片段。但是,不要太高兴,问题来了。假如,突然,你的数据库需要从MYSQL迁移到别的数据库平台,比如ORACLE,SYBASE。迁移的原因很多,也许是你的老板突发奇想,认为这样能卖个好价钱,或者是你的数据猛增,导致MYSQL的性能下降,总之,迁移是事在必行了。你怎么做,你也许会想,呵呵,这简单,把相关的函数替换一下不就行了。
听起来简单,但是……首先,连接数据库的函数要改,需要把mysql_connect和mysql_pconnect替换成OCILogon和OCIPLogon。mysql_errno和mysql_error()当然不能使用,你需要从OCIError()返回的数组中提取响应的信息。
这还不是太糟,最糟的是相关的mysql_fetch_row,mysql_fetch_array等语句遍布于你的许多代码函数和过程中,你需要逐一查找,分析,然后重新替换或者编写相应的ORACLE的版本。如果,你的数据库操作是集中在一个某一个模块或类中,这项工作还可以接受,否则,等于你重新阅读和修改了绝大部分的代码。即使这个不幸的人不是你,那么他也会暗地里诅咒你的;=)
以上,我们回忆了我们以前的做法,以及可能带来的不幸。那么,如果使用DB类来做类似的操作,应该是什么样的呢?下面是相应的DB版本代码:
<?php
include_once "DB.php";
/*
* 连接到数据库
*/
$db_host = "localhost";
$db_user = "root";
$db_passwd = "";
$db_dbName = "test";
$PersistentConnection = 1 ;
$db_type ="mysql";
$db_proto ="";
$db = DB::connect("$db_type://$db_user@$db_passwd:$db_host/$db_dbName",$db_options);
if( DB::isError($db) ){
die "无法连接数据库,错误原因:".DB::errorMessage($db);
}
function sql_exec($sql) {
global $db;
$result = $db->query($sql);
if (DB::isError($result)){
echo "发生数据库错误:".DB::errorMessage($result);
exit();
}
return $result;
}
/////////////////////////////////////////////
$sql = "select * from users";
$result = sql_exec($sql);
while( $row = $result->fetchRow() ){
echo "姓名:$row[0] 性别:$row[1] 年龄 $row[2]<br>";
}
?>
除了连接数据库部分,其他的看起来只是有一些微小的变化,出错处理使用的是PEAR类似的方式(isError),实际上也是从PEAR继承来的。同样的情况,如果你要把数据库从mysql迁移到别的形式,这次假如说是PostegreSQL,一个LINUX中很优秀的数据库,你所做的只是改变一行代码:
$db_type ="mysql";
变成:
$db_type ="pgsql";
其他的,不用变动。怎么样,升级的感觉是不是很清爽呢,你可以用剩下的时间好好研读其余的代码,或者和我继续往下讨论DB的使用方法。
三、 DB的使用入门
DB类由3部分组成:
DB.php 这是前端接口,在DB类里提供了许多"静态"的公用方法,我们一般只需要INCLUDE_ONCE这个文件就可以了。
DB/common.php 这是后端数据库的通用抽象类,不同的数据库的后端类需要继承并实现这个类中定义的公用方法和属性,如果你的数据库不被支持,你可以自己编写一个支持类,这样,你的应用程序就可以迁移过来了。
DB/storage.php 这是一个辅助的工具,它可以把SQL查询做为对象返回,同时能够维护这些对象,在对象改变的时候,相应地更新数据库。
DB/ifx.php
mssql.php Ms SQL Server支持类
oci8.php Orcale 8i支持类
pgsql.php PostegreSQL支持类
sybase.php Sybase支持类
ibase.php ibase支持类
msql.php mSQL 支持类
mysql.php mysql支持类
odbc.php odbc 支持类
这些是相应后端数据库的支持类了。相应具体的数据库的操作是由这些支持类来实现的。
下面,我们首先详细介绍DB.PHP中的一些"静态"方法:
connect()方法
这个方法是最重要的静态方法了,我们通过得到一个DB_COMMON对象,并且连接到相应的数据库。这个方法的原型如下:
function &connect($dsn, $options = false)
$dsn是数据源名称(data source name)的缩写,可以是字符串,或者是特定的数组形式。
一般来说,$dsn是一个字符串,它的格式如下:
phptype(dbsyntax)://username:password@protocol+hostspec/database
* phptype: php后端数据库的类型名称(如mysql, odbc 等等.)
* dbsyntax: 数据库所使用的SQL语法标准,一般不用。
* protocol: 使用的通讯协议。(如tcp, unix 等等.)
* hostspec: 数据库所在的主机的描述。(形式是:主机名[:端口号])
* database: 数据库的名称。
* username: 登陆的用户名。
* password: 登陆的密码。
对于DSN,常用的形式如下:
* phptype://username:password@protocol+hostspec:110//usr/db_file.db
* phptype://username:password@hostspec/database_name
* phptype://username:password@hostspec
* phptype://username@hostspec
* phptype://hostspec/database
* phptype://hostspec
* phptype(dbsyntax)
* phptype
对于省略的部分,将使用缺省值。
当然,$dsn也可以是一个数组,数组的形式如下:
$dsn = array(
'phptype' => 'mysql',
'dbsyntax' => '',
'protocol' => '',
'hostspec' => 'localhost',
'database' => 'test',
'username' => 'root',
'password' => ''
)
$options 是数据库的选项,混合型。如果是布尔型,那么一般来说,这个参数指明是否使用持久性连接(persistent connect),如果后端数据库支持,当$options是TRUE的时候,将使用持久性连接。如果是数组,那么表示这是特定的后端数据库的选项,这些选项将传递到DB_common类中的set_option方法中,后端数据库通过实现或重载这个方法,可以自己决定如何使用这些选项。
isError($value)
这个方法用来判断DB的一些方法返回的结果是否是一个错误对象,你可以使用这个方法来判定某个操作的结果是否是抛出了异常。
当然,如果你的应用程序从是PEAR继承的,也可以直接使用PEAR的isError来判断,尤其是当你的程序中抛出的异常的可能是数据库以外的异常的时候,这个方法只能判断是否是DB_Errro对象,其他的PEAR_Error对象是无法识别出的。
function isWarning($value)
这个方法是判断DB方法的返回结果是否是一个警告。和错误不同的是,警告不是致命的,所以仍可以继续执行下去。
function errorMessage($value)
一旦确定出现了错误,那么可以使用这个方法来取得相应的错误信息,下面是DB中的预定义的错误信息:
DB_ERROR => "unknown error",
DB_ERROR_ALREADY_EXISTS => "already exists",
DB_ERROR_CANNOT_CREATE => "can not create",
DB_ERROR_CANNOT_DELETE => "can not delete",
DB_ERROR_CANNOT_DROP => "can not drop",
DB_ERROR_CONSTRAINT => "constraint violation",
DB_ERROR_DIVZERO => "division by zero",
DB_ERROR_INVALID => "invalid",
DB_ERROR_INVALID_DATE => "invalid date or time",
DB_ERROR_INVALID_NUMBER => "invalid number",
DB_ERROR_MISMATCH => "mismatch",
DB_ERROR_NODBSELECTED => "no database selected",
DB_ERROR_NOSUCHFIELD => "no such field",
DB_ERROR_NOSUCHTABLE => "no such table",
DB_ERROR_NOT_CAPABLE => "DB backend not capable",
DB_ERROR_NOT_FOUND => "not found",
DB_ERROR_NOT_LOCKED => "not locked",
DB_ERROR_SYNTAX => "syntax error",
DB_ERROR_UNSUPPORTED => "not supported",
DB_ERROR_VALUE_COUNT_ON_ROW => "value count on row",
DB_OK => "no error",
DB_WARNING => "unknown warning",
DB_WARNING_READ_ONLY => "read only"
assertExtension($name)
动态载入PHP的数据库扩展。$name是你的PHP扩展的名称,不包含扩展名(如.dll,.so)。
当你需要让PHP载入某个数据库的扩展,但是你没有权限修改php.ini的时候,可以使用这个函数。
当然,DB内部也是自动调用这个方法来载入所需的PHP数据库的扩展。
比如:你如果需要加载oracle的扩展,那么可以这样使用:
<?php
include_once "DB.php";
if ( DB::assertExtension("php_oci8") ){
echo "oracle 8扩展加载成功!";
}else {
die "无法加载oracle 8扩展";
}
?>
以上是在DB类中定义的"静态"方法,所谓静态方法,是指你可以不需要构建对象,就可以直接使用,并且只要你指明DB::,你可以在任何地方直接调用这些方法。
在DB.php中,除了DB外,也有几个非常重要的类,在后端数据库中也使用了这些类:
DB_Error
这个类是从PEAR_Error继承来的,在数据库操作中,对于出现的致命的错误,一般会抛出这个错误。
构建函数:
function DB_Error($code = DB_ERROR, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE, $debuginfo = null)
$code是DB错误代码,$mode决定错误处理的模式,缺省是返回,$level 错误级别,
$debuginfo是附加的调式信息,如刚刚执行的SQL语句等等。
DB_Warning
类似DB_Error。
DB_result
这是非常重要的类。
当执行相应的SQL查询后,后端的数据库类将返回一个DB_result的对象实例,你可以使用这个类的方法来获得查询结果的数据。这个类实际上是后端数据库结果集的包装,这里说的方法,在实际运行中是直接调用了后端数据库的同名的方法,不过,对于我们来说,使用这个包装类会感觉更自然一些。
function fetchRow($fetchmode = DB_FETCHMODE_DEFAULT)
取得一行数据,$fetchmod是获取数据的模式,如果没有指定,那么使用缺省方式。其余的方式我们在后面讨论DB_Common接口的时候会详细讨论。
function fetchInto(&$arr, $fetchmode = DB_FETCHMODE_DEFAULT)
取得一行数据,同时追加到给定的$att数组中。$att是要追加到的数组,它是按照引用方式传递的。
function numCols()
取得结果集的列数。如果出错,返回DB_Error对象。
function numRows()
取得结果集的行数。如果出错,返回DB_Error对象。
function free()
释放结果集所占用的资源。
至此,我们已经学习了DB基本的使用方法,下面我们简单复习一下,对于其中没有见到的方法,我们在后面会详细介绍:
连接数据库:
<?php
$db = DB::connect("$db_type://$db_user@$db_passwd:$db_host/$db_dbName",$db_options);
if (DB::isError($db)){
echo "数据库连接错误:".DB::errorMessage($db)."<br>";
exit();
}
/* 查询1 */
$sql = "select * from user_log";
$result = $db->query($sql);
if (DB::isError($result) ){
echo "数据库错误:".DB::errorMessage($result);
exit();
}
$colCount = $result->numCols();
echo "<tr><td colspan=\"".$colCount."\">共找到".$result->numRows()."位用户</td></tr>";
while($row = $result->fetch()){
echo "<tr><br />";
for($i=0;$i<$colCount;$i++){
echo "<td>".$row[$]."</td><br />";
}
echo "</tr><br />";
}
$result->free();
$db->disconnect();
?>
四、 DB_Common 使用参考
DB_Common类是一个通用的接口,DB跨数据库平台的能力是通过实现这个接口来做到的。如果你的数据库不在DB支持之列,你可以自己编写一个类继承DB_Common类,实现这些接口的函数。
function toString()
返回当前类的字符串描述,格式是类名:(phptype="",dbsyntax="")[connected],一般是类似于:
DB_mysql:(phptype=mysql,dbsyntax=)[connected]
function quoteString($string)
圈引一个字符串,使之在查询中能够安全地放在单引号的分界符之间。一般对于字符型的字段,我们查询的时候使用''作为分隔符,因此如果字符串中有'则会出错,这个函数把字符串中的'替换成\'.
function provides($feature)
指明当前DB的后端程序是否实现了给定的特性,返回布尔值。$feature是功能的明称,一般是:"prepare","pconnect","transactions".在后端程序的构建函数中应该设置这些值。例子:
if($db->provider("transactions")){
//支持事务
}else {
//不支持事务
}
function errorCode($nativecode)
用于将后端数据库产生的错误代码映射到DB的通用错误代码中去。内部调用。后端数据库在构建函数中应该初始化$errorcode_map属性。例子(mysql):
//在DB_mysql的构建函数中
$this->errorcode_map = array(
1004 => DB_ERROR_CANNOT_CREATE,
1005 => DB_ERROR_CANNOT_CREATE,
1006 => DB_ERROR_CANNOT_CREATE,
1007 => DB_ERROR_ALREADY_EXISTS,
1008 => DB_ERROR_CANNOT_DROP,
1046 => DB_ERROR_NODBSELECTED,
1050 => DB_ERROR_ALREADY_EXISTS,
1051 => DB_ERROR_NOSUCHTABLE,
1054 => DB_ERROR_NOSUCHFIELD,
1062 => DB_ERROR_ALREADY_EXISTS,
1064 => DB_ERROR_SYNTAX,
1100 => DB_ERROR_NOT_LOCKED,
1136 => DB_ERROR_VALUE_COUNT_ON_ROW,
1146 => DB_ERROR_NOSUCHTABLE,
);