17.3 PDO<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>抽象类
17.3.1 PDO简介
PDO(PHP Data Objects Layer)提供一个公共的<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>系统接口,它使用C语言做底层开发,运行速度比较快。
PDO以PHP 5.1为基础进行设计,设计沿承PHP的特点,以简洁易用为准,从严格意义上讲,PDO应该归为PHP 5的SPL库之一,而不应该归于数据抽象层,因为其本身和MySQL和MySQLi扩展库的功能类似。
17.3.2 PDO的安装
PDO本身结果是模块化的,它被分成一个公共核心,以及一个或多个驱动程序扩展,公共核心提供了在脚本(PDO本身)中使用的API,驱动程序扩展则为PDO和本地RDBMS客户机API库架起一座桥梁,用来访问指定的<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>系统。比如,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连接<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>
其实,PDO与其他<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>接口和<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>抽象层使用区别不大,首先创建一个连接句柄:
<?php
// 连接MySQL<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>的账号
$login = "root";
$passwd = "root";
$db = new PDO('mysql:host=localhost;dbname=test',$login, $passwd);
//假如连接失败,则抛出异常
try {
foreach($db->query('select * from test') as $row){ //查询<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>
print_r($row);
}
$db=null;//关闭<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>连接
} catch (PDOException $e) {
echo $e->getMessage();
}
?>
(1)使用持久连接pconnect
持久连接的好处是能够避免在每个页面命中时都打开和关闭<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>服务器连接,速度更快,如Oracle<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>的一个进程创建了两个连接,PHP则会把原有连接与新的连接合并共享为一个连接。pdo_connect.php脚本如下:
<?php
//连接MySQL<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>的账号
$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方式连接<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>
一个DSN(数据源名称)是一个标识符,定义为一个ODBC的数据源驱动。格式为:
Database name<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>名称
Directory目录
Database driver<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>驱动
User ID登录
Password<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=密码 target="_blank"><font color=red>密码</font></a></b>
在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);
第二种方法就是直接执行。
直接执行常见于直接查询操作或更新<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>操作,可以使用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提供两个方法来取得错误信息:
Ø errorCode()??SQL语句错误,如:42000 == 语法错误;
Ø 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最大的特点之一是它的灵活性,本节将介绍如何取得查询结果,包括:
Ø 数组(数值或关联数组);
Ø 字符串(单列的结果集);
Ø 对象;
Ø 回调函数。
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
$res = $db->query(
“SELECT * FROM users”,
PDO::FETCH_ASSOC
);
foreach ($res as $row) {
// $row是一个关联数组,可以直接显示,如$row['id']
}
?>
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,就是使用关联索引获取数据集时,关联索引是大写还是小写,有如下几个选择:
Ø 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取得关联数组。具体使用哪种类型,应按照自己的实际应用选择。
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查询行每次都会直接提交给<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>,假如查询较多,每次频频查询将导致效率降低。
另外,在安全问题上,没有过滤一些非凡字符轻易产生SQL注入。
11.过滤字符
下面我们来看看如何使用PDO进行过滤引起SQL注入的方法,即过滤非凡字符。我们在PDO中使用quote()方法,使用例子如下:
$query = “SELECT * FROM users WHERE
login=“.$db->quote($_POST[‘login’]).”
AND
passwd=“.$db->quote($_POST[‘pass’]);
12.事务处理
PDO驱动程序支持所有的事务<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>,并且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.3.1 PDO简介
PDO(PHP Data Objects Layer)提供一个公共的<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>系统接口,它使用C语言做底层开发,运行速度比较快。
PDO以PHP 5.1为基础进行设计,设计沿承PHP的特点,以简洁易用为准,从严格意义上讲,PDO应该归为PHP 5的SPL库之一,而不应该归于数据抽象层,因为其本身和MySQL和MySQLi扩展库的功能类似。
17.3.2 PDO的安装
PDO本身结果是模块化的,它被分成一个公共核心,以及一个或多个驱动程序扩展,公共核心提供了在脚本(PDO本身)中使用的API,驱动程序扩展则为PDO和本地RDBMS客户机API库架起一座桥梁,用来访问指定的<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>系统。比如,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连接<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>
其实,PDO与其他<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>接口和<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>抽象层使用区别不大,首先创建一个连接句柄:
<?php
// 连接MySQL<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>的账号
$login = "root";
$passwd = "root";
$db = new PDO('mysql:host=localhost;dbname=test',$login, $passwd);
//假如连接失败,则抛出异常
try {
foreach($db->query('select * from test') as $row){ //查询<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>
print_r($row);
}
$db=null;//关闭<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>连接
} catch (PDOException $e) {
echo $e->getMessage();
}
?>
(1)使用持久连接pconnect
持久连接的好处是能够避免在每个页面命中时都打开和关闭<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>服务器连接,速度更快,如Oracle<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>的一个进程创建了两个连接,PHP则会把原有连接与新的连接合并共享为一个连接。pdo_connect.php脚本如下:
<?php
//连接MySQL<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>的账号
$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方式连接<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>
一个DSN(数据源名称)是一个标识符,定义为一个ODBC的数据源驱动。格式为:
Database name<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>名称
Directory目录
Database driver<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>驱动
User ID登录
Password<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=密码 target="_blank"><font color=red>密码</font></a></b>
在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);
第二种方法就是直接执行。
直接执行常见于直接查询操作或更新<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>操作,可以使用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提供两个方法来取得错误信息:
Ø errorCode()??SQL语句错误,如:42000 == 语法错误;
Ø 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最大的特点之一是它的灵活性,本节将介绍如何取得查询结果,包括:
Ø 数组(数值或关联数组);
Ø 字符串(单列的结果集);
Ø 对象;
Ø 回调函数。
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
$res = $db->query(
“SELECT * FROM users”,
PDO::FETCH_ASSOC
);
foreach ($res as $row) {
// $row是一个关联数组,可以直接显示,如$row['id']
}
?>
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,就是使用关联索引获取数据集时,关联索引是大写还是小写,有如下几个选择:
Ø 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取得关联数组。具体使用哪种类型,应按照自己的实际应用选择。
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查询行每次都会直接提交给<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>,假如查询较多,每次频频查询将导致效率降低。
另外,在安全问题上,没有过滤一些非凡字符轻易产生SQL注入。
11.过滤字符
下面我们来看看如何使用PDO进行过滤引起SQL注入的方法,即过滤非凡字符。我们在PDO中使用quote()方法,使用例子如下:
$query = “SELECT * FROM users WHERE
login=“.$db->quote($_POST[‘login’]).”
AND
passwd=“.$db->quote($_POST[‘pass’]);
12.事务处理
PDO驱动程序支持所有的事务<b><a href=http://www.baidu.com/s?tn=piglet&ct=&lm=&z=&rn=&word=数据库 target="_blank"><font color=red>数据库</font></a></b>,并且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";
}
}
?>