当前位置:首页 >> 计算机软件及应用 >>

专用的php连接各个数据库的说明,主要是pdo的说明


//首先要连接 mysql 数据库 $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass); //如果你想连 mssql: //mssql:host=localhost;dbname=testdb //如果你想连 sysbase: //sysbase:host=localhost;dbname=testdb

//连 pgsql: //pgsql:host=localhost password=mypass //连 odbc(DSN) //odbc:testdb //连 access: //odbc:Driver={Microsoft (*.mdb)};Dbq=C:\\db.mdb;Uid=Admin $dbh=new PDO('odbc:Driver={sqlserver};server=127.0.0.1;database=pn;uid=sa ;pwd=qqq'); Access Driver port=5432 dbname=testdb user=bruce

//还有 oracle,sqlite,db2.... //执行个查询 foreach ($dbh->query('SELECT * from FOO') as $row) {

print_r($row); // 这 个 结 果 和 mysql_fetch_array 差 不 多 。 PDOStatement::setFetchMode 可以调整。 } //另外还可以: $sth = $dbh->prepare("SELECT name, colour FROM fruit"); $sth->execute(); //将整个记录集读到数组里: $result = $sth->fetchAll(); print_r($result); //输出: Array ( [0] => Array ( [NAME] => pear [0] => pear [COLOUR] => green [1] => green ) [1] => Array ( [NAME] => watermelon

[0] => watermelon [COLOUR] => pink [1] => pink ) ) //插入 / 删 / 更新数据: $count = $dbh->exec("DELETE FROM fruit WHERE colour = 'red'"); //$count 就是删除的条数。相当于 mysql_affected_rows //也可用 PDOStatement::rowCount //偶忘了偶用啥数据库了。。 。。 if ($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') { echo "Running on mysql; doing something mysql specific here\n"; } //原来插入数据的时候要用 mysql_escape_string,现在? print "Unquoted string: $string\n"; print "Quoted string: " . $conn->quote($string) . "\n"; //得到: Unquoted string: Nice Quoted string: 'Nice' //你看现在连引号都自动加了。。 。。 //注意在不同的数据库中结果不同, 比如有的' => '',有的' => \', => \\ \

//现在没顾虑了,全自动。 //最后偶要关闭它了 $conn = null; //但是!你可以保持连接: $dbh = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2', array(PDO_ATTR_PERSISTENT => true)); //很简单的不是?

附:特别简单的特殊调用方法: $stmt = $dbh->prepare("SELECT * FROM REGISTRY where name = ?"); if ($stmt->execute(array($_GET['name']))) { //你怕啥?自动 quote! while ($row = $stmt->fetch()) { print_r($row); } } 也可以: $stmt->bindParam(1, $id); $stmt->bindParam(2, $_FILES['file']['type']); $stmt->bindParam(3, $fp, PDO::PARAM_LOB); 我用的是 MySQL 4.0.26, 但是我个人推荐大家使用 MySQL 4.1.x 或 者 MySQL 5.0.x,因为那些版本有很多有趣的东西值得去学习。我

们这里 PDO 需要连接的就是我的 MySQL 4.0 啦,如果你没有安装 MySQL,请自行安装。我们建立好了 MySQL,并且在 test 库里添 加了表 foo,包括 id,name,gender,time 等四个字段。 我们开始构造第一个 PDO 应用,建立一个 pdo.php 文件在 Web 文 档目录下: <?php $dsn = "mysql:host=localhost;dbname=test"; $db = new PDO($dsn, 'root', ''); $count = $db->exec("INSERT INTO foo SET name = 'heiyeluren',gender='男',time=NOW()"); echo $count; $db = null; ?> 不明白啥意思,俺们来慢慢讲讲。这行: $dsn = "mysql:host=localhost;dbname=test"; 就是构造我们的 DSN(数据源) ,看看里面的信息包括:数据库类型 是 mysql,主机地址是 localhost,数据库名称是 test,就这么几个信 息。不同数据库的数据源构造方式是不一样的。 $db = new PDO($dsn, 'root', ''); 初始化一个 PDO 对象,构造函数的参数第一个就是我们的数据源,

第二个是连接数据库服务器的用户, 第三个参数是密码。 我们不能保 证连接成功, 后面我们会讲到异常情况, 这里我们姑且认为它是连接 成功的。 $count = $db->exec("INSERT INTO foo SET name = 'heiyeluren',gender='男',time=NOW()"); echo $count; 调用我们连接成功的 PDO 对象来执行一个查询,这个查询是一个插 入一条记录的操作, 使用 PDO::exec() 方法会返回一个影响记录的结 果,所以我们输出这个结果。最后还是需要结束对象资源: $db = null; 默认这个不是长连接, 如果需要数据库长连接, 需要最后加一个参数: array(PDO::ATTR_PERSISTENT => true) 变成这样: $db = new PDO($dsn, 'root', '', array(PDO::ATTR_PERSISTENT => true)); 一次操作就这么简单,也许跟以前的没有太大区别,跟 ADOdb 倒是 有几分相似。 如果我们想提取数据的话, 那么就应该使用数据获取功 能。 (下面用到的$db 都是上面已经连接好的对象) <?php foreach ( $db->query("SELECT * FROM foo") as $row)

{ print_r($row); } ?> 我们也可以使用这种获取方式: <?php $rs = $db->query("SELECT * FROM foo"); while($row = $rs->fetch()){ print_r($row); } ?> 如果想一次把数据都获取到数组里可以这样: <?php $rs = $db->query("SELECT * FROM foo"); $result_arr = $rs->fetchAll(); print_r($result_arr); ?> 输出: Array ( [0] => Array

( [id] => 1 [0] => 1 [name] => heiyeluren [1] => heiyeluren [gender] => 男 [2] => 男 [time] => 2006-10-28 23:14:23 [3] => 2006-10-28 23:14:23 ) } 我们看里面的记录,数字索引和关联索引都有,浪费资源,我们只需 要关联索引的: <?php $db->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER); $rs = $db->query("SELECT * FROM foo"); $rs->setFetchMode(PDO::FETCH_ASSOC); $result_arr = $rs->fetchAll(); print_r($result_arr); ?> 看上面的代码,setAttribute() 方法是设置部分属性,主要属性有: PDO::ATTR_CASE、 PDO::ATTR_ERRMODE 等等, 我们这里需要

设置的是 PDO::ATTR_CASE, 就是我们使用关联索引获取数据集的 时候,关联索引是大写还是小写,有几个选择: PDO::CASE_LOWER -- 强制列名是小写 PDO::CASE_NATURAL -- 列名按照原始的方式 PDO::CASE_UPPER -- 强制列名为大写 我们使用 setFetchMode 方法来设置获取结果集的返回值的类型,同 样类型还有: PDO::FETCH_ASSOC -- 关联数组形式 PDO::FETCH_NUM -- 数字索引数组形式 PDO::FETCH_BOTH -- 两者数组形式都有,这是缺省的 PDO::FETCH_OBJ -- 按 照 对 象 的 形 式 , 类 似 于 以 前 的 mysql_fetch_object() 当然,一般情况下我们是使用 PDO::FETCH_ASSOC,具体使用什 么,按照你自己的需要,其他获取类型参考手册。 除了上面这种获取数据的方式,还有这种: <?php $rs = $db->prepare("SELECT * FROM foo"); $rs->execute(); while($row = $rs->fetch()){ print_r($row); } ?>

其实差不多啦。 如果你想获取指定记录里一个字段结果的话, 可以使 用 PDOStatement::fetchColumn(): <?php $rs = $db->query("SELECT COUNT(*) FROM foo"); $col = $rs->fetchColumn(); echo $col; ?> 一般使用 fetchColumn()来进行 count 统计或者某些只需要单字段的 记录很好操作。 简单的总结一下上面的操作: 查询操作主要是 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 类的错误信息和异常 处理。 1. 面向对象的方式 先看看如果连接错误等的处理,使用面向对象的方式来处理: <?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 面向对象的异常处理特征,如果里面有异常的 话就初始化调用 PDOException 来初始化一个异常类。 PDOException 异常类的属性结构: <?php class PDOException extends Exception { public $errorInfo = null; // 错 误 信 息 , 可 以 调 用 PDO::errorInfo() 或 PDOStatement::errorInfo()来访问 protected $message; Exception::getMessage() 来访问 protected $code; Exception::getCode() 来访问 } ?> 这个异常处理类是集成 PHP 5 内置的异常处理类,我们简单的看一 下 PHP 5 内置的异常处理类结构: <?php class Exception { // 属性 protected $message = 'Unknown exception'; // 异常信息 // SQL 状态错误代码,可以使用 // 异 常 信 息 , 可 以 试 用

protected $code = 0; 异常代码 protected $file; 件名 protected $line; // 方法 final function getMessage(); final function getCode(); final function getFile(); 文件名 final function getLine(); final function getTrace(); final function getTraceAsString(); 串的 getTrace() 信息 } ?>

// 用户自定义

// 发生异常的文

// 发生异常的代码行号

// 返回异常信息 // 返回异常代码 // 返回发生异常的

// 返回发生异常的代码行号 // backtrace() 数组 // 已格成化成字符

相应的,在代码中可以合适的调用 getFile() 和 getLine() 来进行错 误定位,更方便的进行调试。 2. 使用面向过程的方法 先看代码: <? $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() 的结果都是最新的,所以我们可以 很容易自己控制错误信息显示。 从上面的使用看出,PDO 功能确实强大,另外还有一些内容我没有

讲到,比如绑定参数、预处理、存储过程、事务处理等等功能。另外 还有不同数据扩 DSN 的构造,Oracle 数据库自己很多特殊的东西, 都需要深入去学习理解,这篇文章就只是简单的描述了一些入门知 识,算是对 PDO 一个简单的了解吧。 pdo 的使用 电脑网络 /一帅 发表于 2006-06-08, 22:28 PHP 5.1 发布时附带一个全新的数据库连接层 PDO(PHP Data Obj ects)。它与 ADODB 和 Pear DB 等数据库抽象层不同,它提供的是 如何存取数据库和处理查询结果, 效率也更高, 还可以通过预处理语 句来防止 sql 注入。 目前支持的数据库: ? DBLIB: FreeTDS / Microsoft SQL Server / Sybase ? Firebird (http://firebird.sourceforge.net/): Firebird/Interbase 6 ? MYSQL (http://www.mysql.com/): MySQL 3.x/4.x ? OCI (http://www.oracle.com): Oracle Call Interface ? ODBC: ODBC v3 (IBM DB2 and unixODBC) ? PGSQL (http://www.postgresql.org/): PostgreSQL ? SQLITE (http://www.postgresql.org/): SQLite 3 and SQLite 2 1. 连接数据库 PDO 提供了统一的接口:PDO 对象。 $db=new PDO( "driver_name:dbname=db_name;host=hostname/IP;[charset=cha

r_type]", //(1)连接字符串 "db_username", //(2)db 用户名 "db_password" //(3)db 密码 ); 说明,PDO 有三个参数 (1)连接字符串: driver_name 是使用的 PDO 驱动,可以为:mysql, mssql, sybas e, dblib, firebird, oci, odbc, pgsql, sqlite, sqlite2; db_name 是数据库名称; hostname/IP 是指要连接到哪里,如果是本地则为 localhost。 [charset=char_type]是可选的,用来设置字符类型。 (2)db 用户名 (3)db 密码 example: $db = new PDO( "pgsql:dbname=pdo;host=localhost;charset=utf-8", "postgres8", "postgres8" ); echo "Successfully created a PDO object"; ?>

连接之前,应先确认已经加载了 PDO 模块。如果试图处理一个无效 的连接字符串: $db = new PDO( "this_is_not_a_pdo_module:dbname=pdo;host=localhost", "foo", "bar" ); echo "Successfully created a PDO object"; ?> PHP 将会返回以下错误: Fatal error: Uncaught exception 'PDOException' with message 'c ould not find driver' 所以,我们可以用一种优雅的方式来处理,即抛出 PDO 异常来处理 错误(但并不是所有情况都是) 。 try { $db = new PDO( "this_is_not_a_pdo_modul:dbname=pdo;host=localhost", "postgres8", "postgres8" ); }

catch( PDOException $e ) { die( $e->getMessage() ); } echo "Successfully created a PDO object"; ?> 我们会得到 could not find driver 或 SQLSTATE[HY000] [7] FATAL: database "pdo2" does not exist 如果数据库不存在(不同的错误会返回不同的提示信息) 。 2. 设置属性 1) PDO 有三种错误处理方式: ? PDO::ERRMODE_SILENT 不显示错误信息,只设置错误码 ? PDO::ERRMODE_WARNING 显示警告错 ? PDO::ERRMODE_EXCEPTION 抛出异常 可通过以下语句来设置错误处理方式为抛出异常 $db->setAttribute(PDO::ATTR_ERRMODE, ); example: $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_E XCEPTION );

当设置为 PDO::ERRMODE_SILENT 时可以通过调用 errorCode() 或 errorInfo()来获得错误信息,当然其他情 况下也可以。 2) 因为不同数据库对返回的字段名称大小写处理不同, 所以 PDO 提 供了 PDO::ATTR_CASE 设置项(包括 PDO::CASE_LOWER,PDO::CASE_NATURAL,PDO::CASE_UP PER) ,来确定返回的字段名称的大小写。 3) 通过设置 PDO::ATTR_ORACLE_NULLS 类型(包括 PDO::NU LL_NATURAL,PDO::NULL_EMPTY_STRING, PDO::NULL_TO_STRING)来指定数据库返回的 NULL 值在 php 中对应的数值。 3. 查询 为了说明清楚,我们这里定义了一个 RSS feeds 表 id integer name character varying(100) url varharacter varying(255) feed character varying(255) 不使用预处理语句的方式: $stmt=$db->query("select * from feeds"); while($f=$stmt->fetch()){ echo $f["id"]." "; echo $f["name"]." ";

echo $f["url"]." "; echo $f["feed"]." "; }; /*使用 foreach 获取数据 foreach( $db->query( "SELECT * FROM feeds" ) as $row ) { print_r( $row ); } */ ?> 得到以下结果: Array ( [id] => 1 [0] => 1 [name] => Planet-PHP [1] => Planet-PHP [url] => http://www.planet-php.net [2] => http://www.planet-php.net [feed] => http://www.planet-php.net/rdf/ [3] => http://www.planet-php.net/rdf/

) (只显示已行为了节省空间) 使用预处理语句的方式: $stmt = $db->prepare( "SELECT * FROM feeds" ); $stmt->execute(); print_r( $stmt->fetch() ); ?> 这里,$stmt 是一个 PDOStatement 对象,预处理之后会得到这样一 个对象,必须 execute 后才起作用。 fetch 函数只提取了一行数据,如果需要读取全部数据,可换成 fetch All 函数。 绑定数据 $stmt = $db->prepare( "SELECT * FROM feeds WHERE url = :url" ); $url = "http://www.planet-php.net"; $stmt->bindParam( ":url", $url ); $stmt->execute(); while( $row = $stmt->fetch() ) { print_r( $row ); } ?>

这里,bindParam 将$url 变量绑定到了:url 域,执行时会自动将改变 量载入。 插入数据 $stmt = $db->prepare( "INSERT INTO feeds ( name, url, feed ) VALUES ( :name, :url, :feed )"); $stmt->execute( array( ":name" => "Planeti Apache", ":url" => "http://www.planetapache.org", ":feed" => "http://www.planetapache.org/rss10.xml" ) ); ?> 实现插入数据也可以像绑定数据一样来 quote 数据, 这里给出通过在 execute 中给定输入参数来自动 quote 的方法。 事务处理 $dbh->beginTransaction(); try { $dbh->query("UPDATE ..."); $dbh->query("UPDATE ..."); $dbh->commit();

} catch (Exception $e) { $dbh->rollBack(); } 如果数据库支持事务处理, 调用 beginTransaction 的同时将数据库设 置为非自动提交,commit 或 rollBack 返回自动提交状态。 4. 存储过程 $stmt = $dbh->prepare("CALL sp_set_string(?)"); $stmt->bindParam(1, $str); $str = ?foo?; $stmt->execute(); 于先前的例子差不多,只是这里使用了“?”数据绑定方法,sp_set_st ring 是存储过程名称。 带有输出参数的存储过程 $stmt = $dbh->prepare("CALL sp_get_string(?)"); $stmt->bindParam(1, $ret,PDO:ARAM_STR, 4000); if ($stmt->execute()) { echo "Got $ret\n"; } 绑定列输出 $stmt = $dbh->prepare("SELECT extension, name from CREDI TS");

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"; } } 2.使用 PDO 连接数据库(注意执行结束时注消当然不注消也不会出现 错误): $dbh = new PDO("mysql:host=localhost;dbname=$dbname", $user, $pwd); $dbh = null; //(unset)

3.一些简单的执行(关键字:exec) <?php $dbname="myzjh"; $user="root"; $pwd="123456"; $dbh = new PDO("mysql:host=localhost;dbname=$dbname", $user, $pwd); $result = $dbh->exec("INSERT INTO test SET name =

"seal",age="20""); $result=$dbh->exec("delete from test where name="seal""); $dbh = null; ?> 执行失败则返回 0;

4.查询输出数据常用方法一: $rs = $dbh->query("select * from test"); while($row = $rs->fetch()){ echo $row[name].$row[age]; } 5.查询数据方法二: foreach ($dbh->query("SELECT * from test") as $row) { echo $row[name].$row[age]; } 6.查询数据方法三: $stmt = $dbh->prepare("select * from test"); if ($stmt->execute()) { while ($row = $stmt->fetch()) { print_r($row); }}

6.1 PDO::prepare 别类查询方式(execute 功能很强大): $stmt = $dbh->prepare("select * from test where name = ?");

if ($stmt->execute(array("zjh"))) { while ($row = $stmt->fetch()) { print_r($row); } } 7.执行的别种方式(也是用 execute)

//zjh 为查询的条件

$stmt = $dbh->prepare("insert into test (name, age) values (?, ?)"); //and updata $stmt->bindParam(1, $name); $stmt->bindParam(2, $age); $name = "one"; $age = 1; $stmt->execute(); $name = "two"; // insert another row with different values $age = 2; $stmt->execute(); 关键字总结 查询操作主要是 PDO::query(): 主要是用于有记录结果返回的操作, 特别是 SELECT 操作 PDO::exec(): 主要是针对没有结果集合返回的操作,比如 INSERT、 UPDATE、DELETE 等操作,它返回的结果是当前操作影响的列数 // insert one row

PDO::prepare(): 主要是预处理操作,需要通过$rs->execute()来执行 预处理里面的 SQL 语句,这个方法可以绑定参数,功能比较强大 获取结果集操作主要是: PDOStatement::fetchColumn(): 是获取结果指定第一条记录的某个 字段,缺省是第一个字段 PDOStatement::fetch():是用来获取一条记录 PDOStatement::fetchALL():是获取所有记录集到一个中,获取结果 可以通过 PDOStatement::setFetchMode 来设置需要结果集合的类 型。 另外有两个周边的操作, $bian= $dbh->lastinsertid(); $bian= $rs->rowCount(); PDO::lastInsertId():是返回上次插入操作,主键列类型是自增的最后 的自增 ID。 PDOStatement::rowCount() :主要是用于 PDO::query()和 PDO::prepare()进行 DELETE、 INSERT、 UPDATE 操作影响的结果 集,对 PDO::exec()方法和 SELECT 操作无效

PHP 5.1 发布时将附带一个全新的数据库连接层,即 PHP Data Objects (PDO)。虽然 PHP 一直都拥有很好的数据库连接,但 PDO

让 PHP 达到一个新的高度。学习如何获得、安装和使用 PDO,以 连接到 IBM? DB2? Universal Database? 和 IBM Cloudscape? 数 据库,插入和检索数据,并探索更多高级特性,

例如: 预处理语句(prepared statements) 、 绑定参数(bound parameters) 、 可滚动游标(scrollable cursors) 、 定位更新(positioned updates) 以及 LOB。

另外,本文将简要地介绍一下对多字节数据的处理。

背景 随着拥有更成熟 OO 语法的 PHP 5 的发布,PHP 越来越多地受到 越来越大的机构的关注,对于 PHP 来说,提供更加一致的和可访问 的数据访问 API 变得越来越重要。

PHP 与流行的开放源代码关系数据库管理系统(RDBMS)MySQL 之间总是很有默契。这对拍档的成功很大程度上是由于它们免费可 用, 而且进入的门槛也比较低, 这两种产品的合作使它们各自都取得 了广受推崇的地位。

很多 PHP 应用程序开发人员都习惯于 PHP-MySQL 这对组合,以 致 PHP 对其他数据库的支持常常模仿 MySQL 客户机库 API。然 而,并不是所有的数据库客户机 API 都是一样的,也不是所有的数 据库都提供相同的特性。虽然存在模仿,但不同的 PHP 数据库扩展 都有它们各自的怪僻和不同之处, 所以从一种数据库迁移到另一种数 据库时会有一些困难。虽然这不是创建 PDO 的直接原因,但是在 设计过程中还是有一定影响的。

如果您是带着想结合使用 PHP 和 DB2 的目的阅读本文, 那么您很 可能属于以下类型中的一种:

您从一家小公司开始,在 MySQL(举个例子)上运行 PHP,由于 业务增长, 您需要 DB2 所提供的可伸缩性/可靠性/支持或其他特性。

您希望移植代码,以使用 DB2,但由于 API 的变化,您需要编写 或实现一个抽象层, 以便在 DB2 上测试应用程序的同时可以继续在 旧的数据库上运行。不仅如此,您还希望能有自己的选择,并保留支 持其他 RDBMS 的可能性,因为您清楚,有些客户机可能已经和其 他平台栓在一起了。

您用 PHP 在 MySQL 之上构建了一个小型的部门应用程序(同样, 这只是举个例子,我并不是要跟 MySQL 过不去) 。事实证明这个应 用程序本身很有用,现在已经在这个部门之外使用,并且闯入了 CIO/CTO 的法眼 —— 现在需要遵从托管的标准数据库。 (是的, 这是第一点的一个变种。 )

在其他某些复杂的企业级应用程序的后台,您已经有一个 DB2 实 例; 您希望利用 PHP 的快速应用程序开发和原型设计来生成动态报 告。

目标 至此我们已经掌握了数据库及 PHP 的背景知识, 现在正好可以提及 PDO 背后的一些设计目标:

为大多数数据库 API 中的常见特性提供一致的 API。

具有可扩展性,以使数据库供应商 X 仍然可以暴露特性 Y 并保持 PDO 的兼容性。

提供大量基本的兼容性技巧, 以便能够更方便地创建跨数据库兼容的 应用程序。

不为给定数据库 API 中本来没有的特性(例如序列)提供完全抽象 或仿真。PDO 类意图为您提供对数据库本地特性的一致性访问,并 减少干扰。

通过将与 PHP 内部打交道的代码(这是最难于编写的部分)集中起 来,简化 PHP 数据库驱动程序的创建。

最后一点非常重要。PDO 是模块化结构,它被分成一个公共核心以 及一个或多个驱动程序扩展,公共核心提供了在脚本(PDO 本身) 中使用的 API,驱动程序扩展则为 PDO 和本地 RDBMS 客户机 API 库架起一座桥梁。DB2 用户将会希望使用 PDO_ODBC 驱动 程序,据称它可以提供以下特性:

它经过重新编写,能支持遵从 ODBC V3 的驱动程序和驱动程序管 理器。它还考虑了对 DB2 特定特性和优化的支持,这成为设计过程 中的一部分 —— 不是后来补充的。

它支持经过试验和测试的存储过程和大型对象。 它不仅能够工作, 而 且非常好用。

对于取 10,000 行记录这样的 DB2 访问操作,使用 PDO_ODBC 驱动程序时的性能比使用传统的 PHP Unified ODBC 扩展要快大约 10 倍。之所以有这么大的差异,是因为在 PDO 中默认的游标是轻 量级的只能向前移动的游标。

获取和安装 PDO PHP 5.1 发布时将附带 PDO,但是也可以通过 PECL 这个 PHP 扩展库 (PHP Extension Repository) 来结合使用 PDO 和 PHP 5.0.3 及以上版本。 如果您使用的是 Windows?, 那么您会欣喜地发现安装 过程要简单得多。

我将假设您已经拥有配置 PHP 5 使之使用您选择的 Web 服务器 的经验, 只有在此假设下, 我才能集中精力关注更相关的细节。 同样, 我还将假设您使用的是一个 DB2 Universal Database 服务器或网络 服务器模式下的 IBM Cloudscape 数据库,并且接受了用户为 db2inst1、密码为 ibmdb2 的默认安装选项。如果您自己编译驱动程 序,那么在进行编译的机器上,应该安装有 DB2 客户机,并且存在 应用程序开发 header,否则编译将遭到失败。

在 PHP 5.0.3 及以上版本上通过 PECL 进行安装 默认情况下,PHP 将安装 "PEAR" 包管理系统。您选择的 OS 发 行版很可能已经创建了一个包含 PEAR 的组件的包,很可能您已经 安装了这个包,并准备运行它。让我们试验一下。

如果它不能工作 如果您没能看到类似于左侧文本的输出, 那么很可能您没有安装需要 的所有包。这时应查阅您选择的 OS 发行版的文档,看看接下来应 该做什么。或者,您可以自己编译 PHP。

清单 1. 列出已安装的 PEAR 包 $ pear list Installed packages: ===================

Package Archive_Tar

Version State 1.1 stable stable stable stable

Console_Getopt 1.2 PEAR XML_RPC 1.3.4

1.1.0

这个包列表表明,我已经安装了 PEAR 1.3.4。很可能您也会安装那 个版本。为了成功地安装 PDO,需要升级到 PEAR 1.3.5;这个过 程很快,很顺利:

$ sudo pear upgrade PEAR

现在便可以放心安装 PDO 了:

$ sudo pear install PDO

您已经安装了 PDO 核心,为了使之生效,需要在 php.ini 文件中 启用它。您需要添加以下一行:

extension=pdo.so

现在安装用于 PDO 的 ODBC 驱动程序, 如果您需要连接到 DB2、 Cloudscape 或 Apache Derby,就需要这个驱动程序:

$ sudo pear install PDO_ODBC

您将看到这样的提示:flavour,dir ? (just leave blank for help)。这是 一个稍微有点隐蔽的提示, 它询问需要配置哪种类型的 ODBC 驱动

程序,以及将它安装在哪里。如果在安装 DB2 时选择了默认安装选 项,那么可以输入 ibm-db2,这相当于 ibm-db2,/home/db2inst1/sqllib。 如果您选择了不同的安装位置, 那么 应该用它置替换 /home/db2inst1/sqllib。输入了正确的细节后,按下 enter 键,这样驱动程序就会构建和安装。

您需要将驱动程序添加到 php.ini 文件中, 从而激活驱动程序。 确保 将下面这一行添加在之前所添加的 pdo.so 这一行之后,否则 PHP 不能正确地初始化。

extension=pdo_odbc.so

PHP 5.1 及以上版本上的安装 PHP 5.1 发布时附带了 PDO。为获得 DB2 支持,只需将下面的开 关添加到配置行。 您显然希望添加更多的配置选项, 以满足您自己的 Web 服务器。关于这方面的详细内容,可以查看 PHP 文档。我将 假设您使用的是一个最近的 Linux? 发行版,并运行 Apache 2:

$ tar xjf php-5.1.0.tar.bz2 $ cd php-5.1.0 $ ./configure --with-pdo-odbc=ibm-db2,/home/db2inst1/sqllib \ --with-apxs2=/usr/sbin/apxs $ make $ sudo make install

Windows 上的安装 Windows 上的安装比起 UNIX? 上的安装来要简单一些。如果您下 载了 PHP 5.1,那么就已经拥有了这个包中相关的 DLL。否则,您 需要从 PHP 快照站点(请参阅本文后面的“下载”小节)下载这些 DLL。

为了激活 PDO,将下面两行添加到 php.ini 文件:

extension=php_pdo.dll extension=php_pdo_odbc.dll

重新启动 Web 服务器 安装完毕后,应该完全重新启动 Web 服务器,以确保 PHP 装载新 的扩展,这样就可以开始使用 PDO 了。如果您使用的是 UNIX 平 台,那么需要获得 DB2 客户机的 DB2 实例环境,以便正确地初始 化。如果您使用的是 bourne shell 型的 shell,那么可以通过运行命 令 . /home/db2inst1/sqllib/db2profile 来获得。 (注意: 假定开头部分 的句号已经在那里! )您需要作出安排,使之在 Web 服务器启动脚 本中自动发生。

PDO 中的关键概念 为了掌握 PDO,需要了解 5 个关键概念。这 5 个概念是:

连接和连接管理

事务和自动提交

预处理语句和存储过程

错误和错误处理

特定于驱动程序的功能性,包括 滚动游标 和 大型对象

连接和连接管理 连接是通过创建 PDO 基类的实例而建立的。不管您想要使用哪种 驱动程序,您总是使用 PDO 类名。构造函数接受用于指定数据源 (即 DSN)的参数,可能还包括用户名和密码参数(如果有的话) 。 最后一个参数用于传递附加的调优参数到 PDO 或底层驱动程序 —— 后面很快会有更详细的论述。下面是一个简短的连接到 DB2 的示例脚本:

============ PHP 通过 PDO 链接 ODBC (二)2007-05-17 17:06 清单 2. 如何使 用 PDO 连接到 DB2 try { $dbh = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); echo "Connected\n"; } catch (Exception $e) { echo "Failed: " . $e->getMessage(); }

odbc:SAMPLE 告诉 PDO 它应该使用 ODBC 驱动程序, 并且应该 使用 "SAMPLE" 数据库。如果使用一个驱动程序管理器,那么可 以用一个 ODBC 级数据源名称替代 SAMPLE。实际上,在冒号字 符之后可以指定任何有效的 ODBC 数据源连接字符串。

如果连接成功,您将看到消息 "Connected",否则,PDO 将抛出一 个 PDOException,解释为什么连接失败。可能的原因包括无效的参 数,不正确的用户/密码,甚至是您忘了装载驱动程序。

值得注意的是, 除非您捕捉从构造函数抛出的异常, 否则, 如果 PHP 脚本未能连接到数据库,它将终止。这与传统的 PHP 数据库扩展有 很大的不同。对于不喜欢异常的人来说,只有两个“硬故障 (hard-failure) 点可能抛出异常, ” 这是其中一个点 (另一个地点是, 当您 试图使用事务时缺乏对事务的支持) 对于所有其他错误, 。 PDO 将使用您选择的 错误处理设置。

连接将保持开放状态, 直到所有对它的引用被释放。 如果在主脚本的 顶端打开连接, 并将其句柄存储在一个全局变量中,那么该连接将 一直处于开放状态,直到脚本结束,或者直到 $dbh 变量被设为 null。如果在一个函数中打开连接,并且只将句柄存储在一个本地变 量中,那么当函数返回时,连接将被关闭。这些语义对于 PHP 中的 任何对象都是一样的,没有什么特别的地方。

ODBC 连接池 如果您使用的是 Windows, 或者如果您选择在 UNIX 型平台上使用 一个 ODBC 驱动程序管理器,那么值得注意的是,PDO_ODBC 将 自动尝试使用该驱动程序管理器的 ODBC 连接池特性。 这个特性类 似于 PHP 级连接缓存,不要求专门请求一个持久的连接。此外,缓 存是在 ODBC 级进行的, 这意味着在同一个进程中运行的其他组件 (例如在 IIS 下运行的 ASP/.Net 脚本)也能利用相同的连接池。

对于流量较大的站点,让 PHP 在不同请求的间隙中缓存打开的连 接,使得每个进程(每个惟一的连接参数集)只需花费一次建立连接 的成本, 这样做常常很有益处。 虽然这听起来像是一个不错的 想法, 但您应该仔细评估这样做对系统的影响, 因为当大量缓存的连接空闲 在那里的时候,就会适得其反。

要建立一个缓存的连接 (如果您更熟悉传统的数据库扩展的话, 也可 以说是 *pconnect()) ,需要在实例化数据库连接时传递一个属性:

清单 3. 如何用 PDO 连接到 DB2,使用持久(缓存)连接 try { $dbh = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2', array(PDO_ATTR_PERSISTENT => true)); echo "Connected\n"; } catch (Exception $e) { echo "Failed: " . $e->getMessage(); }

事务和自动提交 至此,您已经通过 PDO 连接到了 DB2,在发出查询之前,您应该 理解 PDO 是如何管理事务的。如果之前没有接触过事务,那么首 先要知道事务的 4 个特征:原子性(Atomicity) 、一致性 (Consistency) 独立性 、 (Isolation) 和持久性 (Durability) 即 ACID。 , 用外行人的话说, 对于在一个事务中执行的任何工作, 即使它是分阶 段执行的, 也一定可以保证该工作会安全地应用于数据库, 并且在工 作被提交时,不 会受到来自其他连接的影响。事务性工作可以根据 请求自动撤销(假设您还没有提交它) ,这使得脚本中的错误处理变 得更加容易。

事务通常是通过把一批更改积蓄起来、 使之同时生效而实现的。 这样 做的好处是可以大大提高这些更新的效率。 换句话说, 事务可以使脚 本更快, 而且可能更健壮 (不过需要正确地使用事务才能获得这样的 好处) 。

警告

只有在通过 PDO::beginTransaction() 启动事务的情况下, 才会发生 自动回滚。如果手动地发出开始一个事务的查询,那么 PDO 就无 法知道该事务,从而不能在必要时进行回滚。

不幸的是, 并不是每种数据库都支持事务, 所以当第一次打开连接时, PDO 需要在所谓的“自动提交(auto-commit) ”模式下运行。自动 提交模式意味着, 如果数据库支持事务, 那么您所运行的每一个查询 都有它自己的隐式事 务,如果数据库不支持事务,每个查询就没有 这样的事务。如果您需要一个事务,那么必须使用 PDO::beginTransaction() 方法来启动一个事务。如果底层驱动程序 不支持事务,那么将会抛出一个 PDOException(无论错误处理设置 是怎样的:这总是一个严重错误状态) 。在一个事务中,可以使用 PDO::commit() 或 PDO::rollBack() 来结束该事务,这取决于事务 中运行的代码是否成功。

DB2 特性 虽然我认为事务通常要更快一些, 但您还是应该自己评估事务是否真 的可以加快代码。例如,在高并发环境中您可能会发现,过度使用事 务会增加锁开销。如果在应 用程序中出现这种情况,那么建议的补 救办法是在一般情况下使用自动提交,而对于真正需要全部 ACID 特征的代码部分则仍然使用事务。

当脚本结束时, 或者当一个连接即将被关闭时, 如果有一个未完成的 事务,那么 PDO 将自动回滚该事务。这是一种安全措施,有助于 避免在脚本非正常结束时出现不一致的情况 —— 如果没有显式地 提交事务,那么假设有某个地方会出现不一致,所以要执行回滚,以 保证数据的安全性。

清单 4. 在事务中执行批处理

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(); }

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

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

====================== 关于 PHP 应用程序中安全性的说明

PHP Security Consortium 虽然本文表明在使用 PDO 时不再需要引用输入,但这不是说您应 该盲目地使数据通过数据库。XSS 攻击是很实际的危险。您应该总 是确保对传入应用程序的不受信任的数据应用适当的过滤器, 并采取 措施避免让不受信任的数据在站点上发出 HTML 或 javascript。

请访问 The PHP Security Consortium 以了解关于这些危险的更多 知识,以及应该如何避免这些危险。

很多 PHP 脚本中一个常见的缺陷是缺乏输入检验。 这种缺陷可以被 利用,从而招致 XSS(Cross Site Scripting)以及 SQL 入侵攻击。 在 SQL 入侵中,不受信任的数据(例如发给 Web 网页的反馈)和 其他文本被衔接在一起, 构成一个查询。 攻击者可以蓄意地安排他们 的输入, 使之溢出引号之外, 并在您想运行的真正查询后面链接上任 意一个查询。这种攻击使攻击者可以更新、插入或删除数据,甚至可 能可以看到数据库中的任意信息。

XSS 也是一个类似的问题。不过这一次不受信任的数据瞄准的是浏 览站点的人们,而不是应用程序本身。通过提交包含 HTML 或 javascript 组合的文本, 攻击者期望您之后会将那种数据直接输出到 其他访问站点的人那里, 从而使恶意代码可以在站点访问者的浏览器 上运行。

在编写应用程序时, 需要同时考虑这两种攻击。 如果小心地检验和过 滤输入,这两种攻击都是可以防止的。对 XSS 的处理很有技巧性, 所以在这里我不便多讲(不过可以从侧栏找到有用的参考资料) 。相 比之下,SQL 入侵更容易对付。您只需在构造查询之前,适当地排 除每块不受信任的数据。 这种事情有点烦杂, 特别是当您有大量的字 段要处理时,很容易忘记做这件事。

虽然这是有用的(并且也是重要的)信息,但是您可能想知道,为什 么我要花时间提到这一点,本文的重点不是结合使用 PDO 和 DB2 吗?原因是这样的:PHP 现在得到很广泛的部署,自然地,大量流 行的基于 PHP 的应用程序也得到了广泛的部署。 每当某一种这样的 应用程序(和 PHP 本身没有联系)被发现存在漏洞时,PHP 常常 被误认为是不安全的, 可被利用的或者有缺陷的。 为了避免将来出现 这样的情况, 我们可以采取的一个措施是鼓励应用程序开发人员多考 虑安全问题,从而减少由诚实的错误导致的损害。扯远了,下面继续 介绍其他关键概念。

预处理语句和存储过程 很多更成熟的数据库都支持预处理语句的概念。 什么是预处理语句? 您可以把预处理语句看作您想要运行的 SQL 的一种编译过的模板, 它可以使用变量参数进行定制。预处理语句可以带来两大好处:

查询只需解析(或准备)一次,但是可以用相同或不同的参数执行多 次。 当查询准备好后, 数据库将分析、 编译和优化执行该查询的计划。 对于复杂的查询, 这个过程要花比较长的时间, 如果您需要以不同参

数多次重复相同的查询, 那么该过程将大大降低应用程序的速度。 通 过使用预处理语句,可以避免重复分析/编译/优化周期。简言之,预 处理语句使用更少的资源,因而运行得更快。

提供给预处理语句的参数不需要用引号括起来,驱动程序会处理这 些。如果应用程序独占地使用预处理语句,那么可以确保没有 SQL 入侵发生。 (然而,如果您仍然将查询的其他部分建立在不受信任的 输入之上,那么就仍然存在风险) 。

预处理语句是如此有用,以致 PDO 实际上打破了在目标 4 中设下 的规则:如果驱动程序不支持预处理语句,那么 PDO 将仿真预处 理语句。

下面是使用预处理语句的两个例子。第一个例子 通过替换指定占位 符的 name 和 value,执行一次插入。而 第二个例子 使用问号占 位符执行一条 select 语句。

清单 4. 使用预处理语句的重复插入

$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)"); $stmt->bindParam(':name', $name); $stmt->bindParam(':value', $value); // insert one row $name = 'one'; $value = 1; $stmt->execute(); // insert another row with different values $name = 'two'; $value = 2;

$stmt->execute();

清单 5. 使用预处理语句取数据 $stmt = $dbh->prepare("SELECT * FROM REGISTRY where name = ?"); if ($stmt->execute(array('one'))) { while ($row = $stmt->fetch()) { print_r($row); } }

如果数据库驱动程序支持, 您还可以绑定输出和输入参数。 输出参数 通常用于从存储过程获取值。 输出参数使用起来比输入参数要复杂一 些,当绑定一个给定的输出参数时,必须知道该参数的长度。如果为 参数绑定的值大于您建议的长度,那么就会产生错误。

清单 6. 带输出参数调用存储过程

$stmt = $dbh->prepare("CALL sp_returns_string(?)"); $stmt->bindParam(1, $return_value, PDO_PARAM_STR, 4000); // call the stored procedure $stmt->execute(); print "procedure returned $return_value\n";

您还可以指定同时具有输入和输出值的参数,其语法类似于输出参 数。在接下来的例子中,字符串 'hello' 被传递给存储过程,当存储 过程返回时,hello 被替换为该存储过程返回的值。

清单 7. 带输入/输出参数调用存储过程

$stmt = $dbh->prepare("CALL sp_takes_string_returns_string(?)"); $value = 'hello'; $stmt->bindParam(1, $value, PDO_PARAM_STR|PDO_PARAM_INPUT_OUTPUT, 4000); // call the stored procedure $stmt->execute(); print "procedure returned $value\n";

错误和错误处理 PDO 提供了 3 种不同的错误处理模式,以满足不同风格的编程:

PDO_ERRMODE_SILENT

这是默认模式。PDO 将只设置错误代码,以通过 errorCode() 和 errorInfo() 方法对语句和数据库对象进行检查。如果错误是由于对 语句对象的调用而产生的,那么可以在那个对象上调用 errorCode() 或 errorInfo() 方法。如果错误是由于调用数据库对象而产生的,那 么可以在那个数据库对象上调用上述两个方法。

PDO_ERRMODE_WARNING

除了设置错误代码以外,PDO 还将发出一条传统的 E_WARNING 消息。 如果您只是想看看发生了什么问题, 而无意中断应用程序的流 程,那么在调试/测试当中这种设置很有用。

PDO_ERRMODE_EXCEPTION

除了设置错误代码以外,PDO 还将抛出一个 PDOException,并设 置其属性, 以反映错误代码和错误信息。 这种设置在调试当中也很有 用, 因为它会放大脚本中产生错误的地方, 从而可以非常快速地指出 代码中有问题的潜在区域(记住,如果异常导致脚本终止,则事务将 自动回滚) 。

异常模式另一个有用的地方是,与传统的 PHP 风格的警告相比,您 可以更清晰地构造自己的错误处理, 而且, 比起以静寂方式以及显式 地检查每个数据库调用的返回值, 异常模式需要的代码/嵌套也更少。

PDO 定制了使用 SQL-92 SQLSTATE 错误代码字符串的标准;不 同 PDO 驱动程序负责将它们本地代码映射为适当的 SQLSTATE 代码。例如,SQLSTATE 是用于 DB2(以及通常的 ODBC)的本 地错误代码格式,这是多么方便啊!errorCode() 方法返回一个 SQLSTATE 代码。如果您需要关于一个错误的更多特定的信息, PDO 还提供了一个 errorInfo() 方法,该方法将返回一个数组,其 中包含 SQLSTATE 代码、特定于驱动程序的错误代码以及特定于 驱动程序的错误字符串。

分页数据、滚动游标和定位更新

在 Web 应用程序中,一种常见的范例是对查询结果进行分页。如果 您使用一个 Internet 搜索引擎,那么很可能每天都会做这样的事。 您输入搜索词,然后得到前 10-20 个匹配项。如果您想看到更多搜 索结果,可以单击 "next page" 链接。如果想回头看前面看过的结 果,可以单击 "previous page" 链接。记得在几年前,当我第一次在 Web 上使用这样的东西时,我对自己说: “为什么我不能通过滚动查 看所有数据呢?” 问题的答案说简单也简单,说复杂也复杂 ——

我只想说,HTTP 不会智能地使数据库上的可滚动游标一直处于开 放状态,即便如此,需要大量传输的 Web 应用程序也会很快地消耗 掉大量开放的可滚动游标。 因此, 最简单的解决方案是为用户显示所 有的匹配项 —— 但是用户很容易迷失在大量的结果当中。 比较符合 逻辑的措施是人工地将数据格式化到多个页面上, 使用户可以每次查 看一部分可以管理的数据。

所以人们编写可以取所有数据的 PHP 应用程序,然后只显示前 10 行。根据下一次请求,应用程序又显示 11-20 行,依此类推。这对 于只返回少量数据的查询来说很不错,但是,如果有很多匹配项(比 如多于 100) 那么先取全部数据然后丢弃其中的 90%, , 这种做法很 浪费。PHP 的创始人 Rasmus Lerdorf 就这种情形特地为 MySQL 发明了一个特殊的 "LIMIT, OFFSET" 子句。 它允许您通知数据库, 您只对一小部分行感兴趣, 这样它就不会取其他不需要的行了。 其语

法(或非常类似的东西)已经被其他流行的开放源代码数据库采纳, 但并不是所有数据库都提供了相同的语法。 Troels Arvin 收集了一 些非常有用的信息,对不同 RDBMS 所支持的语法进行了比较。

如果您想在以 DB2 为后台数据库的 PHP 应用程序中实现分页结 果,那么可以(也应该)使用下面示例中的语法。这里我们假设有一 个 books 表,表中包含书名和作者,我们现在想要每次在一页中显 示 10 个以上结果:

清单 8. 使用 SQL Standard "Window Functions" 实现数据分页

$db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); // the offset is passed in from the user when they click on a link

// this cast to integer ensures that no SQL injection can occur $offset = (int)$_GET['offset']; $stmt = $db->prepare("select * from ( select ROW_NUMBER() OVER (ORDER BY author) as rownum, * from books ) as books_window WHERE rownum > $offset AND rownum <= (10 + $offset)"); if ($stmt->execute()) { while (($row = $stmt->fetch()) !== false) { print_r($row); }

}

Cloudscape 说明 在撰写本文之际,Cloudscape 在其 SQL 实现中还不支持 ROW_NUMBER(),所以需要使用可滚动游标。

现在, 如果您要编写一个更通用的应用程序, 并希望实现分页的结果 集, 但是不想专门编写很多的代码, 并且也不想使用更重量级的抽象 层,Troels Arvin 的非常有帮助的 RDBMS 信息建议,您可以使用 游标作为更轻便(稍微慢一点)的方案。碰巧的是,PDO 具有这方

面的 API 级的支持。下面将谈到如何使用这种支持来达到与上面示 例相同的效果:

清单 9. 使用滚动游标实现数据分页

$db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); $stmt = $db->prepare("select * from books order by author", array( PDO_ATTR_CURSOR => PDO_CURSOR_SCROLL)); // the offset is passed in from the user when they click on a link // this cast to integer ensures that no SQL injection can occur

$offset = (int)$_GET['offset']; if ($stmt->execute()) { // moves the cursor to the requested offset and fetches the first for ($tofetch = 10, $row = $stmt->fetch(PDO_FETCH_ASSOC, PDO_FETCH_ORI_REL, $offset); $row !== false && $tofetch-- > 0; $row = $stmt->fetch(PDO_FETCH_ASSOC)) { print_r($row); } }

需要强调的是,虽然滚动游标对于更冗长的 window 函数方案来说 是一个很方便的替代方案, 但这种方案要慢很多。 如果在一个传输量 比较少的环境中进行测试, 您可能发现不了速度上的差异, 但当规模 扩大时,您就会开始发现速度降慢带来的痛苦。

定位更新 可滚动游标的另一个用途是, 基于 SQL 中无法表达的重大标准驱动 更新。如果您有一个 Web 页面链接的表,并且需要在每晚的批处理 过程中更新那个表,以反映 Web 页面当前大小,那么可以编写如下 代码:

清单 10. 使用滚动游标作出定位更新

$db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); // create a named, scrolling, updateable cursor

$stmt = $db->prepare("select url, size from links FOR UPDATE OF size", array( PDO_ATTR_CURSOR => PDO_CURSOR_SCROLL, PDO_ATTR_CURSOR_NAME => 'link_pos')); if ($stmt->execute()) { // a statement for applying our updates. // Notice the WHERE CURRENT OF clause mentions "link_pos", // which is the name of the cursor we're using to select the data $upd = $db->prepare("UPDATE links set size = ? WHERE CURRENT OF link_pos"); // grab each row while (($row = $stmt->fetch()) !== false) { // There are much more efficient ways to do this; // this is a brief example only: grab all the content // from the URL

$content = file_get_conents($row['url']); // and measure its length $size = strlen($content) // and pass that as a parameter to our update statement $upd->execute(array($size)); } }

大型对象 在应用程序中的某个地方,您可能发现需要在数据库中存储“大型 (large) ”数据。大型通常意味着“大约 4kb 或 4kb 以上” ,尽管 在没有“大型”数据之前 DB2 最大可以处理 32kb 的数据。 大型 对象可以是文本的,也可以是二进制的。PDO 允许在 bindParam() 或 bindColumn() 调用中通过使用 PDO_PARAM_LOB 类型代码

来使用大型数据类型。PDO_PARAM_LOB 告诉 PDO 将数据映射 为流,所以可以使用 PHP Streams API 来操纵这样的数据。下面是 一个示例:

清单 11. 从数据库取一副图像

$db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); $stmt = $db->prepare("select contenttype, imagedata from images where id=?"); $stmt->execute(array($_GET['id'])); list($type, $lob) = $stmt->fetch(); header("Content-Type: $type"); fpassthru($lob);

上面的介绍很简明扼要。 现在让我们试试另一面, 将上传的图像插入 到一个数据库中:

清单 12. 将图像插入数据库中

$db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); $stmt = $db->prepare("insert into images (id, contenttype, imagedata) values (?, ?, ?)"); $id = get_new_id(); // some function to allocate a new ID

// assume that we are running as part of a file upload form // You can find more information in the PHP documentation $fp = fopen($_FILES['file']['tmp_name'], 'rb'); $stmt->bindParam(1, $id); $stmt->bindParam(2, $_FILES['file']['type']); $stmt->bindParam(3, $fp, PDO_PARAM_LOB); $stmt->execute();

这两个例子都是宏观层次的。请记住,被取的大型对象是一个流,可 以通过所有常规的流函数来使用它,例如 fgets()、fread()、fgetcsv() 和 stream_get_contents()。 ===========================

关于全球化、NLS 和字符集的简要说明 在越来越多的 PHP 应用程序中, 越来越重要的一点是让应用程序能 够在全球范围内使用。 从实践角度来讲, 这意味着应用程序需要能够 正确地处理多种语言(例如英语和日语)中的数据,并且其功能性不 变。这是一个很大的专题,做起来很有技巧性。实现全球化要走的第 一步是采用一种适合所有数据的全球编码,例如 UTF-8。这是一种 ASCII 兼容的编码,它可以使用特殊字符序列为整个 unicode 字符 集编码。UTF-8 也是一种多字节编码。

与常规 ASCII 字符串相比, 多字节编码的字符串处理起来要棘手一 点,因为一个或多个字符对应于一个给定的字母 —— 例如,UTF-8 允许最多 6 个字符的序列映射到字符串中的一个字母。 ASCII 字符 在 UTF-8 中仍具有相同的表示,因此,如果只是处理不带任何特殊 音调的纯英文文本,则 UTF-8 看上去就像是 ASCII。这意味着类 似的字符串函数(作用于字节而不是字符位置) ,例如 strlen() 和 substr(),可能得不到预期的效果,这取决于 UTF-8 字符串中的内 容。幸运的是,PHP iconv 扩展为这些函数提供了一些编码感知的替 代函数。 例如, 您可以使用 iconv_strlen() 来得出字符串中的字符数, 而不是使用 strlen()。类似地,您可以不用 strpos() 或 substr(),而 使用 iconv_strpos() 和 iconv_substr()。

iconv 扩展为您提供了在处理多种编码下的数据时所需的基本工具。 应用程序应该尽量确保所有数据都是 UTF-8 编码的。 如果用适当的 Content-Type 标记 Web 页面,那么大多数浏览器将发送 UTF-8 编码的数据,可以确信,一定有一个编码类型属性可应用于 HTML FORM 标签。

接下来的一步是设置 DB2 实例,使它在您与之交互时使用 UTF-8。 这很容易办到,只需在 DB2 的命令行提示符中运行以下命令:

清单 14. 设置 DB2 实例,使之使用 UTF-8

$ db2set DB2CODEPAGE=1208

完成这样的更改后,从 DB2 实例取到的所有文本数据都是 UTF-8 编码的。同样,DB2 期望您输入的所有文本也是 UTF-8 编码的。 当应用程序的每个部分都使用 UTF-8 时, 应用程序就可以全球使用 了,并且能够显示任何语言的文本,只要这种语言的文本可以用 UTF-8 编码。前面我已经暗示过,这只是通往国际化大道的第一步。 还有很多其他的事情需要考虑, 例如本地化 (采用给定用户的地区设 置来显示日期、时间、重量和度量,将通用文本翻译成用户本地的语 言) 、从右到左或双向(bi-di)文本布局,等等。

值得注意的是,PDO 不对该数据做任何特殊的事情。有些驱动程序 允许更改为一个连接使用的编码,但是在 PDO 级没有处理这种事 情的特殊逻辑。其原因是,PHP 内部完全不知道 unicode,所以在 这里试图使 PDO 知道 unicode 是没有意义的。如果您对这方面的 专题感兴趣,那么您会欣喜地得知,PHP 的 unicode 支持很快就要 出现,不过我也不知道它初次露面的确切日期。

免责声明

本文包含样本代码。IBM 授予您( “被许可方” )使用这个样本代码 的非专有的、版权免费的许可证。然而,该样本代码是以“按现状” 的基础提供的, 没有任何形式的 (不论是明示的, 还是默示的) 保证, 包括对适销性、适用于特定用途或非侵权性的默示保证。IBM 及其 许可方不对被许可方由于使用该软件所导致的任何损失负责。 任何情 况下,无论损失是如何发生的,也不管责任条款怎样,IBM 或其许 可方都不对由使用该软件或不能使用该软件所引起的收入的减少、 利 润的损失或数据的丢失, 或者直接的、 间接的、 特殊的、 由此产生的、 附带的损失或惩罚性的损失赔偿负责, 即使 IBM 已经被明确告知此 类损害的可能性,也是如此。

在下载下面的文件之前请阅读 PHP 许可。

参考资料

您可以参阅本文在 developerWorks 全球站点上的 英文原文。

从 PHP 在线参考 中阅读更多关于 PDO 的信息。

PHP Iconv Documentation。

用于使用 LOB 的 PHP Streams API 和 文件系统函数(不是所有 的函数都适用) 。

下载

描述

Name

Size

Download method

PHP 5.0.3 Source Code, for UNIX platforms

php-5.0.3.tar.bz2

4500 KB

HTTP

PDO Core Source Code, for UNIX platforms

PDO-0.3.tgz

34 KB

HTTP

PDO ODBC Driver Source Code, for UNIX platforms

PDO_ODBC-0.2.tgz

11 KB

HTTP

PHP 5.0.3 for Windows

php-5.0.3.zip

7433 KB

HTTP

PDO Core for Windows

php_pdo.dll

52 KB

HTTP

PDO ODBC Driver for Windows

php_pdo_odbc.dll

28 KB

HTTP

关于下载方法的信息

关于作者 Wez Furlong 是 OmniTI, Inc. 的高级系统工程师,主要从事 the fastest MTA on Earth 方面的工作。 PHP 领域, 在 Wez 是一名 PHP Core Developer, 被誉为 "King of PECL (PHP Extension Community Library)",并帮助维护 php.net 服务器基础设施。Wez 创建和维 护了很多 PHP 扩展(包括 PDO 及其驱动程序) ,并设计和实施了 PHP Streams 层。Wez 目前和他妻子、儿子以及两条狗一起住在哥 伦比亚的马里兰州


相关文章:
PHP5中使用PDO连接数据库的方法_php技巧_脚本之家_图文.pdf
PDO(PHP Data Object) 是PHP 5 中加入的东西,是PHP 5新加入的一个重大功能,因为在PHP 5以前的 php4/php3都是一堆的数据库扩展来跟各个数据库的连接和处理...
第10章 PDO方式访问数据库.ppt
2 PDO访问异构数据库的方法 10.2.1 安装PDO 在Windows环境下PHP 5.1以上版本中,PDO主要数据库的驱动同PHP一起作 为扩展发布,要激活它们只需要简单地编辑php...
学PHP之PDO基础操作.doc
使用 PDO 在与不同数据库管理系统之间交互时,PDO 对象中的成员方法是统一各种数据库的访问接口,所以在使用 PDO 与数据库进行交互之前,首先创建一个 PDO 对象...
PHP使用PDO连接SQLITE3.doc
PHP使用PDO连接SQLITE3_IT/计算机_专业资料。PHP使用PDO连接SQLITE3[转] PHP 使用 PDO 连接 SQLITE3 (原创) 之前从网上看可以通过直接的方法对 sqlite 数据库...
pdo使用详解.txt
该扩展提供PHP内置类 PDO来对数据库进行访问,不同数据库使用相同的方法名,解决...要修改调用你的phpinfo()函数所显示的那个php.ini)把 extension=php_pdo.dll ...
php中PDO方式实现数据库的增删改查.pdf
php中PDO方式实现数据库的增删改查 PDO是mysql数据...变量、静态实例化方法、私有构造函数 DPDO.php ? ...专用的php连接各个数据库... 108页 1下载券 ...
Php中使用PDO操作MySQL数据库(查询 更新 删除).doc
Php中使用PDO操作MySQL数据库(查询 更新 删除) PDO是mysql数据库操作的一个公用类了, 我们不需要进行自定类就可以直接使用pdo来操作 数据库了,但是在php默认配置...
学习PDO的总结.txt
以往的php数据库连接都要开启相应的扩展而且不同数据库的sl语法还不相同,这样就...PDO对象的方法 a.执行sl语句的方法: $pdo->exec();//执行有影响行数的...
php PDO.txt
1.PDO简介 PDO(PHP Data Object) 是PHP 5 中加入的东西,是PHP 5新加入的一个重大功能,因为在PHP 5以前的php4/php3都是一堆的数据库扩展来跟各个数据库的...
PDO介绍 PPT课件_图文.ppt
PDO介绍 PPT课件_高等教育_教育专区。PHP与MySQL程序设计 第31章 PDO介绍 目录 ? ? ? ? 数据库抽象层 PDO的优势 使用PDO 小结 数据库抽象层 ? 为什么要用...
php-PDO.doc
POD(PHP Data Object)扩展在 PHP5 中加入,PHP6 中将默认识用 PDO 连接数据库,所有非 PDO 扩展将会在 PHP6 被从扩展中移除。该扩展提供 PHP 内置类 PDO 来...
第十四讲 pdo讲义_图文.ppt
PHP6中将默认使用PDO连接数据库,所有非PDO扩 展将会在PHP6中移除 ? PDO扩展提供PHP内置类 来对数据库进行访问,不同 数据库使用相同的方法名,解决数据库连接不...
PHP+Mysql+Dreamweaver网站开发与实践第5章02 PDO数据....ppt
PDO数据库抽象层 本讲大纲: 1 2 3 4 PDO概述 通过PDO连接数据库 执行SQL语句 获取结果集 PDO概述 PDO是PHP Date Object(PHP数据对象)的简称,它是与PHP 5....
PHP 在5.1. 和5.2.之间 PDO数据库操作中的不同.doc
PHP 在5.1. 和5.2.之间 PDO数据库操作中的不同_计算机软件及应用_IT/计算机_专业资料。介绍今天发现 php5.1.*和 php5.2.*在数据库预编译代码执行的时候...
php-PDO_图文.ppt
PDO(PHP Data Object)扩展在PHP5中 加入,PHP6中将默认识用PDO连接数据 数据 库,所有非PDO扩展将会在PHP6被从扩 展中移除。该扩展提供PHP内置类 PDO 来对数据...
PHP应用开发与实践第16章 PDO数据库抽象层_图文.ppt
该扩展提供PHP内 置类 PDO来对数据库进行访问,不同数据库使用相同的方法名,解决数据库连接不统一的问题。 16.1.2 PDO特点 PDO是一个“数据库访问抽象层”,...
11PHP访问数据库-2课案_图文.ppt
7.6 数据库接口 层PDO 7.6 数据库接口层PDO ? PDO(PHP Data Object)是为PHP访问数据库 定义的一个轻量级的、一致性的数据库接口 ? 提供了一个数据库访问...
11 PHP访问数据库-2_图文.ppt
提供了一个数据库访问抽象层,作用是统一各种 数据库的访问接口 ? PDO是未来PHP在数据库处理方面的主要发展 方向 数据库接口层PDO的位置 PHP应用程序 数据库接口层...
第3章 PHP操作数据库_教学设计.doc
? ? ? 掌握 PHP 访问数据库的基本步骤,能够对访问过程进行描述; 掌握 mysql ...? PDO 扩展:是 PHP Data Objects 的缩写,是 PHP 应用中的一 个数据库抽象...
PHP_PDO应用详解版.doc
PDO(PHP Data Object) 是 PHP 5 新出来的东西,在 PHP 6 都要出来的时候,...展来跟各个数据库的连接和处理,什么 php_mysql.dll、php_pgsql.dll、php_ms...
更多相关文章: