深入分析PHP PDO配置及使用方法

澳门新葡萄京官网首页,一名新 PHP 数据对象 (PDO)
数据抽象层的原始开发人员为您简要介绍该抽象层,重点讲述与 Oracle
一起运行的情况。

PDO(PHP Data
Object)扩展在PHP5中加入,PHP6中将默认识用PDO连接数据库,所有非PDO扩展将会在PHP6被从扩展中移除。该扩展提供PHP内置类
PDO来对数据库进行访问,不同数据库使用相同的方法名,解决数据库连接不统一的问题。
我是配置在windows下做开发用的。
PDO的目标提供一种轻型、清晰、方便的 API统一各种不同 RDBMS
库的共有特性,但不排除更高级的特性。
通过 PHP 脚本提供可选的较大程度的抽象/兼容性。
PDO的特点:性能。PDO
从一开始就吸取了现有数据库扩展成功和失败的经验教训。因为 PDO
的代码是全新的,所以我们有机会重新开始设计性能,以利用 PHP 5
的最新特性。
能力。
PDO 旨在将常见的数据库功能作为基础提供,同时提供对于 RDBMS
独特功能的方便访问。
简单。
PDO 旨在使您能够轻松使用数据库。API
不会强行介入您的代码,同时会清楚地表明每个函数调用的过程。
运行时可扩展。

需要 PHP:5.0需要其他:Oracle 8 或更高版本客户端库下载用于 Oracle 的 PDO
(Windows):php_pdo.dll, php_pdo_oci.dll下载用于 Oracle 的 PDO
(Unix):pdo, pdo_oci

PDO
扩展是模块化的,使您能够在运行时为您的数据库后端加载驱动程序,而不必重新编译或重新安装整个
PHP 程序。例如,PDO_OCI 扩展会替代 PDO 扩展实现 Oracle 数据库
API。还有一些用于 MySQL、PostgreSQL、ODBC 和 Firebird
的驱动程序,更多的驱动程序尚在开发。■

PDO 简介

安装PDO

PHP
主要是由志愿者完成的项目;尽管有少数一些固定的“核心”开发人员,但是我们没有一个人在全职受薪的开发
PHP。除此之外,我们分别位于世界不同地方,您可以想象长期开发的协调工作是何等困难。因此,PHP
主要是基于突发奇想的个人短期需求来发展的,其原因也多种多样,有的是试验,有的则是因为“明天有活要交”。尽管这样通常每一步都会改善
PHP,但从长远来看则是缺乏完整性 - 数据库扩展就是一个重要的例子。

版本要求:

在各种不同的数据扩展之间根本没有真正的一致性,甚至在某些情况下,在这些扩展内部也没有真正的一致性。几乎所有这些扩展都在使用与基础数据库
API
紧密相连的不同代码完成着相同种类的任务。而且因为我们的人手非常有限,因此这就造成了代码更加难以维护,从而为
PHP 带来了很大的问题。

php5.1以及以后版本的程序包里已经带了;
php5.0.x则要到pecl.php.net下载,放到你的扩展库,就是PHP所在的文件夹的ext文件夹下;
手册上说5.0之前的版本不能运行PDO扩展。

由于 PHP 越来越受欢迎并不断成功,因此主要 PHP
数据库扩展的维护者们参加了在德国举行的 LinuxTag 2003
大会,在会上我们交换了对 PHP 前景的看法。在讨论 PHP
发展的随机性时,我们确定了在 PHP 中进行数据库访问的一些目标:

配置(Windows):

・提供一种轻型、清晰、方便的 API ・统一各种不同 RDBMS
库的共有特性,但不排除更高级的特性。 ・通过 PHP
脚本提供可选的较大程度的抽象/兼容性。

修改你的php.ini配置文件,使它支持pdo.(php.ini这个东西没有弄懂的话,先弄清楚,要修改调用你的phpinfo()函数所显示的那个php.ini)

extension=php_pdo.dll前面的分号去掉,分毫是php配置文件注释符号,这个扩展是必须的。

我们之所以提出了这种 PHP 数据对象 (PDO) 的概念,是因为我们希望通过采用
Zend Engine 2先进的面向对象特性获得该 API 的一些更优秀的性能。

往下还有

PHP 中的数据抽象层概念一点都算不上新;在 Google 中查询“PHP database
abstraction”会找到大约 83,200 个匹配项。它几乎是许多 PHP
开发人员梦寐以求的,而其产生则部分归因于我们不完整的
API。如果您曾经尝试过使用第三方抽象层来完成任何真正重要的工作,通常会发现这些抽象层对于手头的工作来说设计的功能过于强大了

或者表现为在使用前需要进行大量学习,或者表现为接口速度缓慢,参数需要经过多层脚本函数调用才能到达数据库自有的
API;通常是存在上述两种表象。

;extension=php_pdo.dll
;extension=php_pdo_firebird.dll
;extension=php_pdo_informix.dll
;extension=php_pdo_mssql.dll
;extension=php_pdo_mysql.dll
;extension=php_pdo_oci.dll
;extension=php_pdo_oci8.dll
;extension=php_pdo_odbc.dll
;extension=php_pdo_pgsql.dll
;extension=php_pdo_sqlite.dll

为什么这些抽象层会存在这种问题?这些抽象层总是在试图完成太多的任务,甚至可能是不可能的任务。我们决定以实用为目标,仅将一些最常见的数据库
API 特性作为我们的基础,并使得 PDO
驱动程序能够将它们特定于产品的特性暴露为常规扩展函数。

找到extension=php_pdo.dll 和 extension=php_pdo_mysql.dll
,去掉前面“;”的注释,修改后的两行配置内容如下:

为什么使用 PDO?

extension=php_pdo.dll
extension=php_pdo_mysql.dll

听过有关数据库抽象扩展谣传的大多数人会立刻对 PDO 的扩展方面产生疑惑 -
我们是否要分析 SQL,将其转换为相应的后端方言呢?我们如何处理特性 X
或特性 Y,等等。因此,当您听说我们在 PDO
中根本不用为此而担忧时可能会大吃一惊;我们不希望使所有内容都完全统一,因为要使得这种统一成为可能,只能是将自己限制在最低的通用标准。

各各扩展所对应的数据库是:

如果 PDO 不是一个整体的抽象层,那还有什么别的原因值得您考虑使用它吗?

Driver name Supported databases
PDO_DBLIB FreeTDS / Microsoft SQL Server / Sybase
PDO_FIREBIRD Firebird/Interbase 6
PDO_INFORMIX IBM Informix Dynamic Server
PDO_MYSQL MySQL 3.x/4.x
PDO_OCI Oracle Call Interface
PDO_ODBC ODBC v3 (IBM DB2, unixODBC and win32 ODBC)
PDO_PGSQL PostgreSQL
PDO_SQLITE SQLite 3 and SQLite 2

・性能。PDO 从一开始就吸取了现有数据库扩展成功和失败的经验教训。因为 PDO
的代码是全新的,所以我们有机会重新开始设计性能,以利用 PHP 5
的最新特性。 ・能力。PDO
旨在将常见的数据库功能作为基础提供,同时提供对于 RDBMS
独特功能的方便访问。 ・简单。PDO 旨在使您能够轻松使用数据库。API
不会强行介入您的代码,同时会清楚地表明每个函数调用的过程。
・运行时可扩展。PDO
扩展是模块化的,使您能够在运行时为您的数据库后端加载驱动程序,而不必重新编译或重新安装整个
PHP 程序。例如,PDO_OCI 扩展会替代 PDO 扩展实现 Oracle 数据库
API。还有一些用于 MySQL、PostgreSQL、ODBC 和 Firebird
的驱动程序,更多的驱动程序尚在开发。

你要使用哪种数据库,只要把相应的扩展前的注释符号”;”去掉就可以了。

您可能想了解 PDO 与其他常用的抽象层的对比情况,例如 PEAR DB 或
ADODB。无论在 API 方面还是在性能方面,PDO
都比其他常见抽象层要轻型,但是涉及到在各个数据库后端之间提供统一性方面,则不如那些抽象层,例如用于处理大量可移植性问题的
PEAR MDB 2 抽象层。

配置(Linux):

在哪里可以获得 PDO?

编译PHP时加上:

PDO 是通过 PECL,即 PHP 扩展库提供的。如果您在运行 Linux
计算机,请按照下面的说明进行设置;稍后是在 Windows 上安装的详细信息。

–enable-pdo –with-pdo-sqlite
–with-pdo-mysql=/usr/local/mysql/bin/mysql_config

请注意,PDO
及其驱动程序当前处于“alpha”状态;这就意味着我们会合理保证没有重大缺陷,但是该程序包功能并不完善

我们还要添加很多功能。虽然我们鼓励您测试该程序包,但是实在不推荐在现阶段将其用于生产。

重启apache或iis,搞一个测试页,里面就phpinfo函数,可查看PDO模块是否开启。

Unix/Linux 安装

使用PDO(Mysql 数据库举例)

如果您以前尚未尝试过 PHP 5,则请花一点时间来通读一下“新闻”和各种声明。在
UNIX 计算机上,您可能要安装或升级 libxml2;如果没有
libxml2,“pear”程序包管理工具就无法运行,您安装 PDO
时就会遇到很多困难。获取 PHP 5,并将其编译和安装。确保指定的前缀不是
/usr/local/,这样它就不会与 PHP 4 安装发生冲突了:

数据库的连接:

% ./configure –prefix=/usr/local/php5 –with-zlib [此处指定其他选项]%
make install

我们通过下面的例子来分析PDO连接数据库,建立一个 PDO_config.php
文件,使用的时候直接 include
一下:
<?php
//数据库类型 Oracle 用 OCI
,对于开发者来说,使用不同的数据库只要改这个:
$dbms   = ‘mysql’;
$host   = ‘localhost’;
//数据库主机名
$dbName = ‘test_test_test’;
//使用的数据库
$user   = ‘root’;
//数据库连接用户名
$pass   = ‘ ‘;
//对应的密码
$dsn    = “$dbms:host=$host; dbname=$dbName”;

现在您就可以使用“pear”工具获取并安装 PDO 以及用于 PDO 的 Oracle
驱动程序了。因为 PDO 当前标记为 alpha,所以默认情况下 pear
工具不会下载该程序包。在该程序包名称后面添加后缀“-alpha”,通知该 pear
工具可以安装 alpha 版本:

try{

% PATH=”/usr/local/php5/bin:$PATH”% pear install PDO-alpha

//初始化一个PDO对象,就是创建了数据库连接对象 $dbh:
 $dbh = new PDO($dsn, $user, $pass);
//echo “连接成功<br/>”; /*你还可以进行一次搜索操作
 foreach($dbh->query(‘SELECT
* from FOO’)as$row){
  print_r($row);
  //你可以用 echo($GLOBAL); 来看到这些值

您需要告知 PHP 从专用于 PHP 5 的 php.ini 文件加载 PDO
驱动程序。如果您使用的前缀与我使用的一样,PHP 则会在
/usr/local/php5/lib/php.ini 中查找 php.ini 文件。向该文件中添加以下行:

 }
 $dbh = null;
}catch(PDOException$e){
 die(“Error!: “.$e->getMessage().”<br/>”);
}
/*
默认这个不是长连接,如果需要数据库长连接,需要最后加一个参数:array(PDO::ATTR_PERSISTENT
=> true) 变成这样:
$db=newPDO($dsn,$user,$pass,array(PDO::ATTR_PERSISTENT=>true));
*/
$db = new PDO($dsn, $user, $pass);
//创建数据库连接对象 $db
?>

extension=pdo.so

数据库查询:

现在您需要获取数据库特定的驱动程序;对于 Oracle,此特定程序称为
PDO_OCI。在 shell 中,键入:

上面我们已经进行了一次查询,我们还可以使用如下的查询:
<?php
include(“./PDO_config.php”);

% pear install PDO_OCI-alpha

$db -> setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL);

此驱动程序也需要从 php.ini 文件加载;将下行添加到前面添加的那行之后:

//设置属性
$rs = $db->query(“SELECT * FROM yourtable”);
$rs -> setFetchMode(PDO::FETCH_ASSOC);
$result_arr = $rs -> fetchAll();

extension=pdo_oci.so

//获取所有记录集到一个变量中
print_r($result_arr);

现在检查一下,确保它能够运行:

?>

% php -m

以上因为用到setAttribute()方法,放上那两个参数,把字段名强制转换成大写。

在模块列表中,您应该会看到 PDO 和 PDO_OCI。

下面列出多有PDO::setAttribute()的参数:

防火墙碍事了?

PDO::ATTR_CASE:
强制列名变成一种格式,详细如下(第二个参数):
PDO::CASE_LOWER
强制列名是小写.
PDO::CASE_NATURAL
列名按照原始的方式
PDO::CASE_UPPER
强制列名为大写
PDO::ATTR_ERRMODE
错误提示
PDO::ERRMODE_SILENT
不显示错误信息,只显示错误码
PDO::ERRMODE_WARNING
显示警告错误
PDO::ERRMODE_EXCEPTION
抛出异常
PDO::ATTR_ORACLE_NULLS(不仅仅是ORACLE有效,别的数据库也有效): )
指定数据库返回的NULL值在php中对应的数值
PDO::NULL_NATURAL:
不变.
PDO::NULL_EMPTY_STRING
Empty string is converted toNULL
PDO::NULL_TO_STRING
NULL is converted to an empty string
PDO::ATTR_STRINGIFY_FETCHES
Convert numeric values to strings when fetching. Requiresbool.
PDO::ATTR_STATEMENT_CLASS
Set user-supplied statement class derived from PDOStatement. Cannot be
used with persistent PDO instances. Requiresarray(string classname,
array(mixed constructor_args)).
PDO::ATTR_AUTOCOMMIT(available in OCI, Firebird and MySQL)
Whether to autocommit every single statement.
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY(available in MySQL)
Use buffered queries.

如果您位于防火墙的后面,则在使用 pear
安装程序获取程序包时可能会遇到一些问题。如果发生这种情况,则可以按照下列说明手动下载并安装这些程序包:

例子中的

% wget % pear install PDO-0.1.1.tgz

$rs->setFetchMode(PDO::FETCH_ASSOC);
是PDOStatement::setFetchMode(),对返回类型的声明。

[ 将 extension=pdo.so 添加到 php.ini ]

有如下:

% wget _OCI% pear install PDO_OCI-0.1.tgz

PDO::FETCH_ASSOC– 关联数组形式
PDO::FETCH_NUM — 数字索引数组形式
PDO::FETCH_BOTH — 两者数组形式都有,这是缺省的
PDO::FETCH_OBJ — 按照对象的形式,类似于以前的
mysql_fetch_object()更多返回类型声明(PDOStatement::方法名)看手册。★插入,更新,删除数据,
$db->exec(”DELETE FROM `xxxx_menu` where
mid=43″);简单的总结一下上面的操作:查询操作主要是PDO::query()、PDO::exec()、PDO::prepare()
PDO::query()主要是用于有记录结果返回的操作,特别是SELECT操作,
PDO::exec()主要是针对没有结果集合返回的操作,比如INSERT、UPDATE、DELETE等操作,它返回的结果是当前操作影响的列数。
PDO::prepare()主要是预处理操作,需要通过$rs->execute()来执行预处理里面的SQL语句,这个方法可以绑定参数,功能比较强大,不是本文能够简单说明白的,大家可以参考手册和其他文档。获取结果集操作主要是:PDOStatement::fetchColumn()、PDOStatement::fetch()PDOStatement::fetchALL()。
PDOStatement::fetchColumn()是获取结果指定第一条记录的某个字段,缺省是第一个字段。
PDOStatement::fetch()是用来获取一条记录,
PDOStatement::fetchAll()是获取所有记录集到一个中,获取结果可以通过PDOStatement::setFetchMode来设置需要结果集合的类型。另外有两个周边的操作,一个是PDO::lastInsertId()和PDOStatement::rowCount()。PDO::lastInsertId()是返回上次插入操作,主键列类型是自增的最后的自增ID。
PDOStatement::rowCount()主要是用于PDO::query()和PDO::prepare()进行DELETE、INSERT、UPDATE操作影响的结果集,对PDO::exec()方法和SELECT操作无效。
事务和自动提交至此,您已经通过 PDO 连接到了
mysql,在发出查询之前,您应该理解 PDO
是如何管理事务的。如果之前没有接触过事务,那么首先要知道事务的 4
个特征:原子性(Atomicity)、一致性(Consistency)、独立性(Isolation)和持久性(Durability),即
ACID。用外行人的话说,对于在一个事务中执行的任何工作,即使它是分阶段执行的,也一定可以保证该工作会安全地应用于数据库,并且在工作被提交时,不会受到来自其他连接的影响。事务性工作可以根据请求自动撤销(假设您还没有提交它),这使得脚本中的错误处理变得更加容易。
事务通常是通过把一批更改积蓄起来、使之同时生效而实现的。这样做的好处是可以大大提高这些更新的效率。换句话说,事务可以使脚本更快,而且可能更健壮(不过需要正确地使用事务才能获得这样的好处)。
不幸的是,并不是每种数据库都支持事务(Mysql5支持事务,mysql4我不知道),所以当第一次打开连接时,PDO
需要在所谓的“自动提交(auto-commit)”模式下运行。自动提交模式意味着,如果数据库支持事务,那么您所运行的每一个查询都有它自己的隐式事务,如果数据库不支持事务,每个查询就没有这样的事务。如果您需要一个事务,那么必须使用
PDO::beginTransaction()
方法来启动一个事务。如果底层驱动程序不支持事务,那么将会抛出一个
PDOException(无论错误处理设置是怎样的:这总是一个严重错误状态)。在一个事务中,可以使用
PDO::commit() 或 PDO::rollBack()
来结束该事务,这取决于事务中运行的代码是否成功。
当脚本结束时,或者当一个连接即将被关闭时,如果有一个未完成的事务,那么
PDO
将自动回滚该事务。这是一种安全措施,有助于避免在脚本非正常结束时出现不一致的情况
——
如果没有显式地提交事务,那么假设有某个地方会出现不一致,所以要执行回滚,以保证数据的安全性。

[ 将 extension=pdo_oci.so 添加到 php.ini ]

在上述两种情况下,都需要首先调用“pear
install”;上述示例中的版本号在本文编写之时是最新的,但随着开发的继续进行会发生变化。

Windows 安装

如果您正在运行 Windows,则请按照下列说明执行:

・从 #v5 获取 PHP 5,将其解压缩到 C:/php5。 ・从 _5_0/php_pdo.dll
和_5_0/php_pdo_oci.dll 分别获取 PDO 和 PDO_OCI,将其放入
C:/php5/ext。或者,您可以从 PHP 5 下载页上列出的“用于 PHP 5.0.0 的 PECL
模块集合”zip 文件中找到所有这些 PDO 驱动程序,以及所有 PECL 程序包的所有
Windows 版本。 ・编辑 C:/php5/php.ini 文件,并添加下列内容:

extension=php_pdo.dllextension=php_pdo_oci.dll

编辑 php.ini 文件时,有一点很重要,即要在任何其他 PDO 驱动程序之前先加载
PDO 扩展,否则就不能正确初始化。

如果在 Windows 目录中有一个 PHP 4 的全局 php.ini
文件,则可能会遇到问题。最好的解决方法是,移动该 php.ini 文件,使其与
PHP 4 SAPI 位于相同的文件夹中,以隔离 PHP 4 安装;例如,将其移动到与
php4apache.dll 相同的文件夹中。请注意,PHP 5
程序中并非所有文档都是最新的;推荐的安装过程如上面所述 - 如 install.txt
文件所声明的,请勿将任何 DLL 复制到 windows 文件夹或 system 文件夹中 -
任何内容都是自包含的。如果您运行的是 apache,并且遇到无法加载 DLL
的错误,则检查一下是否将 C:/php5 添加到了 PATH 中。另外,还要注意 PHP 5
的 CGI 版本现在的名称为 php-cgi.exe。

连接 PDO

首先创建 PDO
类的一个实例,将其用作数据库句柄。使用哪个基础驱动程序并不重要;您总要使用
PDO 类名。构造函数的第一个参数为数据源名称
(DSN),第二个参数为用户名,第三个参数为该用户名的口令。DSN 的 PDO
命名惯例为 PDO
驱动程序的名称,后面一个冒号,再后面是可选的驱动程序特定的信息。在我们的示例中,会加载
OCI
驱动程序但不指定任何其他信息;这样会使用默认的数据库。对于其他驱动程序,如
ODBC 驱动程序,第一个冒号后面的所有内容都将被用作 ODBC DSN。MySQL
驱动程序会同样以不同的方式解释它的 DSN。

如果无法加载该驱动程序,或者发生了连接失败,则会抛出一个
PDOException,以便您可以决定如何最好地处理该故障。

?phptry {$dbh = new PDO(“OCI:”, “scott”, “tiger”);} catch (PDOException
$e) {echo “Failed to obtain database handle ” .$e-getMessage(); }?

在连接字符串中,您可以指定两个可选参数;第一个是数据库名称,第二个是字符集;这些参数与可选的第三个和第四个参数相对应,后两个参数您可能在
oci8 扩展函数 ociconnect() 或 ocipLogon()
中使用过。要使用特定的字符集连接一个特定的数据库,则可以执行下列操作:

?phptry {$dbh = new PDO(“OCI:dbname=accounts;charset=UTF-8”, “scott”,
“tiger”);} catch (PDOException $e) {echo “Failed to obtain database
handle ” .$e-getMessage(); }?

省略 try..catch
控制结构并无裨益。如果在应用程序的较高级别没有定义异常处理,则在无法建立数据库连接的情况下,该脚本会终止。

连接管理

目前,PDO 完全没有执行自己的任何连接管理,因此每个“新
PDO”调用都会建立一个新的数据库连接。该连接在 $dbh
变量越界时,或者当您为其指定 NULL 值时会被释放。

?phptry {$dbh = new PDO(“OCI:dbname=accounts;charset=UTF-8”, “scott”,
“tiger”);} catch (PDOException $e) {echo “Failed to obtain database
handle ” .$e-getMessage();exit; }// 在此处对数据库执行一些操作 // …//
现在完成,释放该连接$dbh = null;?

计划在不久的将来为 PDO 增加连接缓存功能;就当前的 oci8
扩展而言,会重用与现有服务器的连接,并且在这些连接中,还会重用闲置的登录。当在缓存连接模式中运行时,如上面的代码段所示释放
$dbh 时会将该登录标记为可由其他连接重用。

如果您使用 ODBC 驱动程序访问 Oracle,则可能会很高兴地注意到,默认情况下
PDO_ODBC 驱动程序支持 ODBC 连接池。

使用 PDO

了解一个编程 API
的最好方式就是使用它,因此我们来看一下附带的这个演示,以了解如何进行批次更新(代码如下)。

?php

// Create a PDO database handle object// the ‘oci:’ string specifies
that the OCI driver should be used// you could use ‘oci:dbname=name’ to
specify the database name.// The second and third parameters are the
username and password respectively$dbh = new PDO(‘oci:’, ‘scott’,
‘tiger’);

// Create a test table to hold the data from
credits.csv$dbh-exec(“CREATE TABLE CREDITS (extension varchar(255),name
varchar(255))”);

// start a transaction$dbh-beginTransaction();

// prepare to insert a large quantitiy of data$stmt =
$dbh-prepare(“INSERT INTO CREDITS (extension, name) VALUES (:extension,
:name)”);

// bind the inputs to php variables; specify that the data will be
strings// with a maximum length of 64
characters$stmt-bindParam(‘:extension’,$extension,PDO_PARAM_STR,
64);$stmt-bindParam(‘:name’, $name,PDO_PARAM_STR, 64);

// Open the .csv file for import$fp = fopen(‘credits.csv’, ‘r’);while
(!feof($fp)) {list($extension, $name) = fgetcsv($fp,
1024);$stmt-execute();}fclose($fp);

// Commit the changes$dbh-commit();

?

既然我们已经成功连接到了
Oracle,那么现在就可以创建一个表来保存一些数据了。对于此示例,我们使用一些
PHP 扩展及其作者,并将这些内容输入一个数据库中。数据库句柄对象的 exec()
方法可用来发出不会返回结果集的快速一次性查询,因此我们在这里使用该方法来发出
CREATE TABLE 查询。

为了使得示例更自然,我从 PHP
源代码中抽取了扩展及其作者的信息,并将其存储到了一个 CSV
文件中。这就代表一个常见情形:从 CSV
文件批次导入数据。在我们的示例中,我们充分利用了 Oracle
的预处理语句和绑定参数,以获得一个高效的数据导入脚本。在讲述该示例之前,有必要了解一下
PDO 处理事务的方式。

PDO 中的事务处理

Oracle
具有一个敏感的默认操作模式:当您进行连接时,将会位于一个隐式事务处理中,在提交事务之前其中的更改不会完全生效。除了事务处理的标准优点之外,数据库服务器在执行每次更新之后还不需要重新构建索引和其他内部结构;它可以延迟到提交之后进行。这样会加速代码的执行。Oracle
这点确实很好。

但不幸的是,并非每个数据库供应商都支持事务处理,并且因为 PDO
旨在以一种相对可移植的方式支持这些事务处理,所以它默认情况下以自动提交模式运行。启用自动提交模式后,数据库驱动程序会隐式提交每个成功的更新。当您调用
$dbh-beginTransaction() 时,就会请求关闭自动提交,直到调用 $dbh-commit()
或者 $dbh-rollBack()
才会重新启用,具体取决于您的代码是怎样编写的。如果基础驱动程序不支持事务处理,则会抛出一个
PDOException。

如果发生了问题并且 PHP
出错,您的脚本将退出并且事务处于待批状态;或者您关闭数据库句柄时,PDO
会自动针对任何待批的事务调用
$dbh-rollBack()。此行为会减少向数据库中提交可能未定义或者已损坏数据的可能性,这是用于处理已放弃事务的标准语义。

预处理语句、存储过程

PDO 支持使用 Oracle 样式命名的占位符语法将变量帮定到 SQL
中的预处理语句。PDO
还为其他数据库提供了命名占位符模拟,甚至可以为生来就不支持该概念的数据库模拟预处理语句和绑定参数。这是
PHP 向前迈进的积极一步,因为这样可以使开发人员能够用 PHP
编写“企业级”的数据库应用程序,而不必特别关注数据库平台的能力。

使用 PDO 预处理语句非常简单,调用数据库句柄的 prepare()
方法即可。它会返回一个语句句柄对象,然后您可以使用该对象来绑定参数和执行语句。在此示例中,我们将要定义两个命名占位符,“:extension”和“:name”,这两个占位符分别与
.CSV 文件中的 PHP 扩展名称和其中一个作者的姓名相对应。

$stmt = $dbh-prepare(“INSERT INTO CREDITS (extension, name) VALUES
(:extension, :name)”);

预处理了语句之后,我们使用 bindParam() 方法来将这些命名参数分别与 PHP
变量名称“$extension”和“$name”相关联。我们还会通知
Oracle,这些数据将要格式化为字符串,最大长度为 64 个字符。

$stmt-bindParam(‘:extension’, $extension, PDO_PARAM_STR,
64);$stmt-bindParam(‘:name’, $name, PDO_PARAM_STR, 64);

我们现在即准备好插入数据了 - 我们只需要打开该 CSV
文件,并从中获取数据即可。通过使用 fopen() 和 fgetcsv()
函数可以相当简单地完成此操作。然后,我们可以使用 PHP list()
构造函数直接将 CSV
的列指定给变量“$extension”和“$name”。因为这些变量已经绑定到了语句中,所以我们现在要做的只是调用该语句对象的
execute() 方法使其执行插入。这种方式既方便又快捷 -
在事务处理时每个迭代循环只有两行。到达文件尾时,我们就可以立即使用数据库句柄的
commit() 方法来提交这些更改了。

如果您只是要传递输入参数,并且有许多这样的参数要传递,那么您会觉得下面所示的快捷方式语法非常有帮助;此语法使您能够省去对
$stmt-bindParam() 的调用。

$stmt = $dbh-prepare(“INSERT INTO CREDITS (extension, name) VALUES
(:extension, :name)”);$stmt-execute(array(‘:extension’ = $extension,
‘:name’ = $name));

您还可以使用 bindParam
来为存储过程设置输入/输出参数;语法是完全相同的,只是查询有所不同。下面的代码演示如何调用一个名为“sp_add_item”的存储过程;其目的是要针对输入设置
$item_name,然后该存储过程将在返回时更新 $error_code。

$stmt = $dbh-prepare(“begin sp_add_item(:item_name, :error_code);
end”);$stmt-bindParam(‘:item_name’, $item_name, PDO_PARAM_STR,
12);$stmt-bindParam(‘:error_code’, $error_code, PDO_PARAM_STR,
12);$stmt-execute();

抓取数据

使用 PDO
抓取数据与进行插入或更新相似,只是您执行完查询之后,将要重复调用 fetch()
方法来获取结果集的下一行。进行获取的最简单情况如下所示,值得注意的一点是,您还可以将参数绑定到查询,以控制如
WHERE 子句这样的内容;执行此操作的语法与我们已经看到的 bindParam()
代码完全相同。

$stmt = $dbh-prepare(“SELECT extension, name from CREDITS”);if
($stmt-execute()) {while ($row = stmt-fetch()) {print_r($row); }}

PDO
支持一些不同的抓取策略,这些策略在方便性和性能方面有所差别;通过将下列选项之一指定为
fetch() 方法的参数,您可以更改其返回值以适应您的语法:

・PDO_FETCH_NUM - 每个行抓取返回一个按照列位置索引的数组,并且以 0
为基数。

while ($row = $stmt-fetch(PDO_FETCH_NUM)) {printf(“Extension %s, by
%sbr”, $row[0], $row[1]);}

・PDO_FETCH_ASSOC -
每个行抓取根据行集中的列名,返回一个按列名索引的数组。

while ($row = $stmt-fetch(PDO_FETCH_ASSOC)) {echo “Extension
$row[EXTENSION] by $row[NAME]br”;}

・PDO_FETCH_BOTH -
每个行抓取返回一个既按照列位置又按照列名索引的数组。也就是上述两种情况的直接组合。如果没有指定抓取模式,则该模式为默认模式。
・PDO_FETCH_OBJ - 每个行抓取返回一个匿名对象,其属性名与列名对应。

while ($row = $stmt-fetch(PDO_FETCH_ASSOC)) {echo “Extension
{$row-EXTENSION} by {$row-NAME}br”;}

・PDO_FETCH_LAZY -
每个行抓取返回一个引用语句对象的重载对象。这“看起来”好像是
PDO_FETCH_OBJ 和 PDO_FETCH_BOTH 的组合,只是只有当您在脚本中访问 PHP
变量时才创建这些变量。 ・PDO_FETCH_BOUND - 抓取每行,返回
TRUE。在使用绑定输出列时这种方式非常有用,它可以避免创建不需要的任何数组或对象。。

无论您使用哪种抓取策略,当没有其他行可抓取时,fetch() 方法将会返回
FALSE。

现在我要讲述一些技巧,如果您需要最后再调整一下脚本性能的话,这些技巧可能会对您有所帮助。但先给你一个忠告:要像躲避瘟疫一样避免不成熟的优化。您应该总是首选最清晰、可维护性最好的解决方案。请记住,在一个典型的
Web
应用程序中,您不能衡量各种抓取模式间的区别,除非脚本要处理很多行。我再重复一遍:抓取模式间的性能区别非常小
- 请使用最适合您代码的模式。

请记住,使用 PDO_FETCH_NUM
的花销最小,因为访问列数据只是一个简单的数值查询。PDO_FETCH_OBJ
使您能够使用 OO
语法将数据集的列作为对象的属性来访问,但是每个属性访问都涉及一个附加的散列查询,使得使用它的花销基本上与
PDO_FETCH_ASSOC 相同。每个这样的模式都会复制整行,从而占用稍多的内存。

很多数据库驱动程序都会代表您预先抓取并缓存一定数量的行。PHP
每次访问其中一个这样行中的列时,它都需要将其复制到自己的专用内存区域中。如果您的查询涉及很多行,而只需要基于某种复杂的逻辑访问给定行的特定列,则您会发现
PDO_FETCH_LAZY
是一种避免使用很多内存的有用方法,因为它只有在您访问给定列时才复制该列。使用此方式时要注意,从某个给定语句为每个
fetch()
抓取的“惰性对象”是每次迭代时使用的同一对象。这就暗示着您不能只是简单地存储该对象用于以后的比较,因为它仍然会引用该语句的当前行
- 您需要手动复制所需要的部分。

最后一种模式为 PDO_FETCH_BOUND,该模式会告知 PDO
您已经将所有列绑定到了 PHP
变量,并且除了要它在到达行集的末尾时通知您外不需要它执行别的任何操作。绑定输出列在概念上与绑定输入参数相似,只是绑定输出列可以用于所有数据库驱动程序。您可以将
PHP 变量绑定到命名列,PDO 将在每次调用 execute()
时对其进行更新。此技术可用来剃去结果集中每列、每行的一些虚拟机器操作码。这种技术的缺点在于,可能会使您的代码难以跟踪,您使用变量名称时需要倍加小心。下面的代码说明了绑定输出列的使用。请注意,您不必指定
PDO_FETCH_BOUND 即可使用 $stmt-bindColumn();PDO_FETCH_BOUND
只是一个对于您了解只能使用绑定值的情况的一种优化。

$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,
Author:$name/n”; }}

可移植性

区分大小写的列

PDO 旨在令使用可移植 SQL
的脚本运行良好、可移植。本文中提及的所有查询在使用任何 PDO
驱动程序时其运行性能应该相同 - 包括所有绑定输入变量和绑定输出列。

但有一个转换问题 - 当您使用 PDO_FETCH_ASSOC
抓取数据时,不同的驱动程序会以不同的方式返回列名 -
某些会将列名转化为大写,某些转换为小写,某些则会使其呈查询中指定的样式。这对于
PHP 脚本来说是一个潜在的问题,因为数组键区分大小写。PDO
提供了一个兼容性属性来帮助规范脚本的结果。下面的小代码段是上面
PDO_FETCH_BOUND 示例的可移植版本,因为 setAttribute() 方法调用会指导
PDO 将抓取返回的列名全部转换为大写:

$dbh = new PDO(‘OCI:’, ‘scott’,
‘tiger’);$dbh-setAttribute(PDO_ATTR_CASE, PDO_CASE_UPPER);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, Author:$name/n”; }}

除了 PDO_CASE_UPPER 之外,还有 PDO_CASE_LOWER和 PDO_CASE_NATURAL。

错误和错误处理

可移植脚本的另一个难题是处理从各种数据库处理程序返回的各种不同的错误消息;某些数据库对于程序化处理错误的支持能力很差,而其他一些数据库则具有非常丰富的错误代码。只要可行,PDO
将为您的脚本提供一个统一的错误代码,从而使您不必为应对可移植性的这个方面所累。当然,PDO
还会为驱动程序提供原生错误代码和错误消息,以防您需要用它来进行诊断,或者错误代码映射不完整。

另一个困扰 PHP
数据库扩展的一致性问题是错误处理策略的一致性:某些扩展会返回的错误代码需要您手动抓取错误字符串,而其他一些扩展则只是发出
PHP 警告。PDO 允许您从下列三种不同的错误处理策略中选择一种:

・PDO_ERRMODE_SILENT这是默认模式;它只是使用语句和数据库句柄对象的
errorCode() 和 errorInfo() 方法为您设置要检查的错误代码。

if (!$dbh-exec($sql)) {echo $dbh-errorCode() .”BR”;$info =
$dbh-errorInfo();// $info[0] == $dbh-errorCode() 统一的错误代码//
$info[1] 是驱动程序特定的错误代码// $info[2]
是驱动程序特定的错误字符串}

・PDO_ERRMODE_WARNING除了设置错误代码之外,PDO 还会发出 PHP
警告,您可以使用常规的 PHP
错误处理程序捕获该警告,并集中应用您准备好用于应用程序的任何错误处理/记录策略,或者只是使该错误显示在浏览器中。
・PDO_ERRMODE_EXCEPTION除了设置错误代码之外,PDO 还会抛出一个
PDOException,并将其属性设置为包含该错误代码和信息。然后,您可以在代码的较高级别捕获该异常,使用全局异常处理程序捕获该异常,或者不对其进行处理而终止脚本。

try {$dbh-exec($sql);} catch (PDOException $e) {// 显示警告消息print
$e-getMessage();$info = $e-errorInfo;// $info[0] == $e-code; unified
error code// $info[1] 是驱动程序特定的错误代码// $info[2]
是驱动程序特定的错误字符串}

请注意,与警告或异常相比,静默模式针对运行时错误使用的资源最少,但是为了获得该速度,您牺牲了一些简单性,而变得有一点复杂。

统一错误代码表当前包括下列常量:
PDO_ERR_NONE、PDO_ERR_CANT_MAP、PDO_ERR_SYNTAX、PDO_ERR_CONSTRAINT、PDO_ERR_NOT_FOUND、PDO_ERR_ALREADY_EXISTS、PDO_ERR_NOT_IMPLEMENTED、PDO_ERR_MISMATCH、PDO_ERR_TRUNCATED、PDO_ERR_DISCONNECTED。

这些常量所代表的意思字面即可推知,但是 PDO_ERR_CANT_MAP
代码除外;这是一个 PDO
特定的代码,也就是说它无法将驱动程序特定的代码映射到统一的错误代码,因此您应该查询
errorInfo() 方法返回的驱动程序特定代码来获得更多信息。

数据类型

PDO
在某种程度上类型不可知,因此它喜欢将数据表示为字符串,而不是将其转换为整数或双精度类型。此时您可能对此有些迷惑,但是原因非常简单:字符串类型是最精确的类型,在
PHP
中具有最广泛的应用范围;过早地将数据转换为整数或者双精度类型可能会导致截断或舍入错误。通过将数据以字符串抽出,PDO
为您提供了一些脚本控制,您可以使用普通的 PHP
类型转换工具来控制如何进行转换以及何时进行转换。

NULL

如果结果集中的某列包含一个 NULL 值,PDO 则会将其映射为 PHP null
值。Oracle 在将数据返回 PDO 时会将空字符串转换为 NULL,但是 PHP
支持的任何其他数据库都不会这样处理,从而导致了可移植性问题。PDO
提供了一个驱动程序级属性
PDO_ATTR_ORACLE_NULLS,该属性会为其他数据驱动程序模拟此行为:

$dbh = new PDO(‘OCI:’, ‘scott’,
‘tiger’);$dbh-setAttribute(PDO_ATTR_ORACLE_NULLS, true);// 现在从此
$dbh 打开的任何语句中的// 空字符串都将被转换为 NULL

POD 的现状和未来

PDO
现在仍相当不成熟,但是会快速成熟起来。在编写本文之时,我在本文中提到的任何内容都能够通过
PDO_OCI 驱动程序适用于 Oracle 8 或更高版本。

已经计划增加以下主要特性,在不久将可以使用:

1.使用 PHP 流的 LOB 支持。
使用绑定参数,您能够将任何流资源作为输入或输出参数传递到在 LOB
上运行的查询中。与之相似,类型为 LOB 的输出参数将表现为 PHP
流,因此您可以使用 fread()、fwrite()、fseek()
和其他流函数来访问这些参数。此时,在 PDO 中根本没有 LOB 支持。
2.持久性连接和缓存的预处理语句。
持久性连接使您能够避免在每个页面命中时打开和关闭数据库服务器连接。缓存的预处理语句又前进了一步,它使您能够持久保持查询的预处理版本以及数据库句柄。
3.游标。 目前,PDO
只提供前向只读游标,但是将来会提供可滚动游标、REF-CURSOR、使用游标进行定位更新,以及可更新滚动游标。

我们希望在 PHP 5.1 中默认启用 PHP 扩展,但是在此之前,我们希望能让 PDO
在 PHP 5.0
发布时稳定运行,但是我们日常工作中的压力稍稍拖延了这些工作。同时,通过
PECL 发布 PDO 使我们能够在收到问题报告时做出回应,并根据不同于 PHP 5.0
发布时间表的时间表发布修复版本,因此您在 PHP 5.1 发布前即可使用 PDO。

我们需要您的反馈

如果您试用了
PDO,并且发现了问题,请务必使用我们的错误跟踪软件将其报告给我们。如果您使用的是
Oracle 驱动程序,则请使用此页:

_OCI

如果您使用的是其他驱动程序,则请用其名称替换该 URL 中 PDO_OCI。

如果您使用 PDO
时遇到问题,或者针对某些特性存在疑问,或者具有特性请求,请联系
pecl-dev@lists.php.net。如果您愿意,当然还可以直接联系我
(wez@php.net),但是请注意,我每天都会收到大量有关 PHP
的电子邮件;您可能会发现如果首先与前面的邮件列表联系会更快得到答复。

———–关于作者Wez Furlong 是 Brain Room Ltd.
的技术总监,他在该公司不但使用 PHP 用于 Web 开发,还将其用作 Linux 和
Windows 应用程序和系统的嵌入式脚本引擎。Wez 是 PHP
的核心开发人员,经常向 SQLite、COM/.Net、ActivePHP、mailparse 和 Streams
API 等投稿,他是 PECL 即 PHP 扩展社区库的“头儿”。他的咨询公司的网页为 。

发表评论

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