PEAR:创建中间的数据库应用层

发表于:2007-07-14来源:作者:点击数: 标签:
内容: 一、 什么是DB类 二、 为什么要设计抽象的中间数据层 三、 DB的使用入门 四、 DB_Common 使用参考 五、 更进一步,创建你自己的中间数据库应用层 六、 DB的不足 七、参考资源 关于作者 相关内容: 1、用PEAR来写你的下一个php程序 2、常用模块 3、使


内容:

一、 什么是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,
        );
function errorMessage($dbcode)
返回DB错误文本信息。

function &raiseError($code = DB_ERROR, $mode = false, $level = false,$debuginfo = false, $nativecode = false)
抛出一个错误。这个函数由DB来调用。

function setFetchMode($fetchmode)
设置缺省的fetch模式。

fetchmod有以下几种:
DB_FETCHMODE_DEFAULT :使用缺省的模式
DB_FETCHMODE_ORDERED :每条记录的数据列使用数字索引,从0开始
DB_FETCHMODE_ASSOC :每条记录的数据列使用字段名索引,同查询中的字段名一致
DB_FETCHMODE_FLIPPED:如果结果集是多维的,多条记录多个字段,一般来说返回一个2维数组,第一维是记录号,标明是第几条记录,第2维的数组则使用字段名或数字索引。DB_FETCHMODE_FLIPPED则会交换这个顺序,也就是说,第一层是字段名,第2维才是记录号.

function setOption($option, $value)
设置后端数据库选项。$options,$value分别是选项名和相应的值。

一般不用直接调用,在DB_Common及其子类的构建函数中会调用这个函数。

function getOption($option)
取得某个option的值

function prepare($query)
为execute()准备编译一个查询。对于某些后端数据库,这是通过仿真来实现的。返回编译后的查询句柄,出错则返回一个DB_Error对象。

function execute($stmt, $data = false)
执行编译后的查询。$stmt是由prepare返回的句柄。$data是参数数据,如果你使用的是参数查询,$data将要包含每个?参数的值

例子:
<?php
  /**
   * 下面是执行一个删除查询,从文章表中将指定用户的文章记录删除
   */
  $sql = "delete from artilce where article.userid =$userid";
  $qh = $db->prepare($sql);
  if(DB::isError($qh)){
     return $qh;
  }
  $result = $db->execute($qh);
  return $result;
?>



function executeEmulateQuery($stmt, $data = false)
返回一个实际的查询字符串,供仿真prepare,execute的时候使用,内部函数。如果出错,则返回DB_Error对象

function executeMultiple( $stmt, &$data )
在同一个查询句柄上执行多个查询。$data必需是一个从0开始的数组,这个函数将 依次使用数组中的每一行数据来调用execute。这个函数一般用于参数查询。你可执行一次 查询的编译,然后将不同的参数值放入$data数组,然后就可一次同时执行查询了。需要注意,如果中间某个查询出错,剩余的查询不会继续进行而是返回错误。

function modifyQuery($query)
内部函数,用于后端数据库修正查询,后端数据库实现这个函数,然后返回进行优化和修正后查询串。例子:这是DB_mysql的实现,
<?php
   function modifyQuery($query)
    {
        if ($this->options['optimize'] == 'portability') {
            // "DELETE FROM table" gives 0 affected rows in MySQL.
            // This little hack lets you know how many rows were deleted.
            if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
                $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
                                      'DELETE FROM \1 WHERE 1=1', $query);
            }
        }
        return $query;
    }
?>



function &query($query)
执行一个查询,查询成功,如果是有结果集的查询,则回一个DB_Result对象,如果没有结果集的查询则返回DB_OK。

出错则返回DB_Error对象。

function &getOne($query, $params = array())
执行查询,并返回结果集中第一行第一列的数据。$params是参数值,如果后端数据库支持,将调用prepare/execute来使用这些参数。

例子:
<?php
$sql = "select id,date from article where  userid= $userid orderby date";
$last = $db->getOne($sql);
if(DB::isError($last)){
   echo "出错:".DB::errorMessage($last);
}
echo "id:".$last;
?>



function &getRow($query, $fetchmode = DB_FETCHMODE_DEFAULT, $params = array())
执行查询,请返回结果集的第一条记录。

$fetchmod 指定fetch模式,如果省略,使用缺省模式。

例子:
<?php
$sql = "select * from articles order by date desc";
$row = $db->getRow($sql);
if(DB::isError($row)){
   echo "出错:".DB::errorMessage($row);
}
for($i=0;$i<count($row;$i++){
   echo "<td>$row[$i]</td>";
}

?>



function &getCol($query, $col = 0, $params = array())
执行查询,并返回包含结果集中指定字段列的值的数组。

$col是要返回的列的索引,可以是整数,或者是关键字段名。

例子:
<?php
$sql = "select id,userid,date from articles order by date desc limit 100";
//只返回用户列表
$row = $db->getCol($sql,1);
if(DB::isError($row)){
   echo "出错:".DB::errorMessage($row);
}

for($i=0;$i<count($row;$i++){
   echo "<tr>$row[$i]</tr>";
}

?>



function &getAssoc($query, $force_array = false, $params = array())
执行查询,并返回一个关联数组。

$force_arry 强制返回数组。如果true,那么即使你的结果集里只有2个字段,那么关键字段对应的值也是一个只有一个元素的

数组。如果false,那么关键字段对应的值是一个标量了。

注意,这个关联数组有些特别:
如果你查询的是"select userid,name,reg_date from user",记录集是:
userid     name       reg_date
test       testor     2001-07-05
test2      teest2     2001-07-06
那么返回的数组是这样的:
array( 'test' => array('testor','2001-0705'),
       'test2'=> array('teest2','2001-07-06'
     )

例子:

<?php
$sql = "select userid,name,reg_date,last_login from users limit 100";
$userinfo = $db->getAssoc($sql);
if(DB::isError($userinfo)){
    die "错误!".DB::errorMessage($userinfo);
}
if(empty($userinfo)){
   echo "warning:NO users!";
   return;
}
/*现在,userinfo里面保存有用户的信息*/
function getUserInfo($user_id=''){
  $info = $userinfo[$user_id];
  if (empty($info){
     echo "没有这个用户信息!";
  }
  return $info;
}
?>



function &getAll($query, $fetchmode = DB_FETCHMODE_DEFAULT, $params = array())
返回包含结果集中全部记录的数组。注意,如果你的结果集很大,不要使用这个函数。

例子:
<?php
  $sql ="select * from users limit 1000";
  $list = $db->getAll($sql);
  if(DB::isError($list)){
     die "数据库错误:".DB::errorMessage($list);
  }
  for ($i=0;$i<count($list);$i++){
       $user = $list[$i];
       echo "<tr>";
       for($j=0;$j<count($user);$j++){
           echo "<td>".$user[$j]."</td>";
       }
       echo "</tr>";
  }
?>



function autoCommit($onoff=false)
指定是否自动提交事务。有的后端数据库不支持。

function commit()
提交当前事务

function rollback()
回滚当前事务

function numRows($result)
返回结果集中的记录数

function affectedRows()
返回上一次查询操作的记录数

function nextId($seq_name, $ondemand = true)
返回指定的sequence的下一个值

function createSequence($seq_name)
创建一个Sequence

function dropSequence($seq_name)
删除一个Sequence


五、 更进一步,创建你自己的中间数据库应用层
到此,我们对于DB类的功能已经有了更深的了解。我们可以基于DB类来创建你自己的数据库应用层了。也许你会问,我为什么还要创建新的类,直接在我的应用程序中使用DB不好么?答案是,当然可以,但是我不推荐。

首先,虽然DB的功能很强大,但是仍然是过于底层的,你的类应该是面向应用的。你的这个数据库层应该屏蔽不需要使用的功能和函数,同时,也要提供一些更'高级'的方法,比如,你的应用程序不应该去联接数据库,释放资源,这些应该是透明的。那么这些工作就要由你的这个类来完成了。

其次,DB仍有一些缺陷,一旦找到比它更好的,你可以迅速地升级。你所要改的只是这个类的方法如何实现,你的应用程序的其他模块不会为此受到影响。

在你设计自己的类的时候,希望能够一些准则:

提供基本的自由的查询接口。包括query,execute.分别对应有结果集和无结果集的查询。
不要使用特定数据库的某些特性,即使因为放弃使用这些特征会给你增加不少的代码量。
尽量屏蔽一些数据库的操作细节,比如连接数据库,释放资源等等。
六、 DB的不足
上面我们讨论了DB的优点和一些使用的方法与技巧。但是,任何事物都不是十全十美的,DB类更是如此,由于DB乃至PEAR的开发时间并不长,因此DB并没有最终全部完成,其中也或多或少地存在一些BUG或者设计上的问题,需要我们在使用中去发现和修正。

MYSQL的问题
问题1:前段我在开发一个项目中,发现DB的MYSQL类有一个问题,那就是当你connect的时候,MYSQL类自动将当前数据库设置为$DSN中的数据库名。以后使用query的时候,都是使用当前数据库。下面是原来connect的代码:
  if ($dsninfo["database"]) {
            if (!mysql_select_db($dsninfo["database"], $conn)) {
                return $this->raiseError(); // XXX ERRORMSG
            }
        }



这导致了一个后来令我费解的问题:

我的项目需要我连接2个数据,假设分别是user和test。test是我的主要数据库,但是我要从第一个数据库中user中取得用户信息,同时test中保存用户的权限信息。我为此做了一个中间的类CV_DB,用来实现我的数据库层。在CV_DB的创建函数中,我连接到指定的数据库,从而我可以这样使用:

$db1 = new CV("user");

$db2 = new CV("test");

后面当我执行,查询某个用户的信息的时候,出现了"该表不存在"的DB错误。但实际上这个表是存在的。最后,我发现原来是DB/mysql.php的问题,因为它的查询使用的是,mysql_query,而不是mysql_db_query,当我连接到第2个数据库的时候,MYSQL的当前数据库变成了第2个,这时我再执行针对第一个数据库的查询,当然会出错。

解决方式有2个,在创建CV的时候不连接到数据库,查询的时候连接,查询完毕后断开。或者,修改DB/Mysql.php。我选择后者,我将上面几行注释,然后将SimpleQuery中的mysql_query 替换成mysql_db_query.

问题2:mysql没有execute,因此它继承使用了DB_common中模拟方式,但是不幸地是,这带来了新的问题,在一些更新查询中,所要更新的数据有? &这2个特殊字符的时候,会出现问题,因为prepare认为这是参数查询的字符,将进行分析,导致无法更新数据。

解决方式也有2个:替换?和&,但是这样要考虑的事情很多。或者:直接使用simpleQuery或者query。

我选择后者,由于我的其他类只和CV――我这个中间数据库应用类打交道,于是,我在CV的execute方法中做了判断,如果是后端数据库是mysql,那么我直接调用simpleQuery,否则,调用后端数据库的prepare和execute。这样,实际的后端数据库对于我项目中的其他应用类是透明的,我可以简单地做相应的调整,这也是我设计数据库应用层的初衷。

DB的开发状态
DB目前仍在不断地开发当中,在DB/下面有一个文件STATUS,它描述了DB类的功能和各个后端数据库的实现情况,下面是PHP4.0.6这个发布中的开发情况:
"x" - 已经实现,但尚未测试implemented, but without tests
"t" - 已经实现,但是一个或多个测试没有通过implemented, but one or more tests fail
"T" - 实现并通过全部测试implemented, passing all tests
"e" - 仿真实现,没有测试emulated, without tests
"E" - 仿真实现,通过全部测试emulated, passing all tests
"n" - 返回 "not capable",没有这个能力提供该项功能
"-" - 没有实现
                   fbsql      ifx      mssql      oci8     pgsql
FEATURE              |  ibase  |  msql   |   mysql |   odbc  |  sybase
simpleQuery          x    x    x    x    x    T    T    x    T    x
numCols              x    x    x    x    x    T    T    x    T    x
numRows              x    n    n    x    x    T    E    n    T    n
errorNative          x    n    x    n    n    T    x    x    T    n
prepare/execute      e    x    e    e    e    E    T    e    E    e
sequences            e    n    n    n    n    E    T    n    T    n
affectedRows         x    n    x    n    n    T    T    n    T    n
fetch modes          x    x    x    x    x    T    T    x    T    x
fetch absolute rows  x    n    x    x    x    x    n    x    x    x
transactions         x    x    n    n    n    n    x    x    x    n
auto-commit          x    x    n    n    n    n    x    x    x    n
error mapping        x    -    e    -    -    T    T    x    E    -
tableInfo            x    n    n    n    n    T    n    n    n    n




七、参考资源
ADODB另一个非常好操纵数据库的PHP程序,DB的绝好的替代者

关于作者
潘凡(Night Sailer):北京赛迪数据有限公司工程师。主要研究兴趣是Perl,PHP与XML的应用,PHP的MVC开发模式,PERL-GTK的使用。您可以通过 E-mail:nightsailer@hotmail.com 跟他联系。  

原文转自:http://www.ltesting.net