首页    新闻    下载    文档    论坛     最新漏洞    黑客教程    数据库    搜索    小榕软件实验室怀旧版    星际争霸WEB版    最新IP准确查询   
名称: 密码:      忘记密码  马上注册
安全知识 :: 黑客教程

PDO数据库抽象类


http://www.gipsky.com/
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提供两个方法来取得错误信息:



&Oslash; errorCode()??SQL语句错误,如:42000 == 语法错误;



&Oslash; 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最大的特点之一是它的灵活性,本节将介绍如何取得查询结果,包括:



&Oslash; 数组(数值或关联数组);



&Oslash; 字符串(单列的结果集);



&Oslash; 对象;



&Oslash; 回调函数。



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,就是使用关联索引获取数据集时,关联索引是大写还是小写,有如下几个选择:



&Oslash; PDO::CASE_LOWER??强制列名是小写;



&Oslash; PDO::CASE_NATURAL??列名按照原始的方式;



&Oslash; PDO::CASE_UPPER??强制列名为大写。



我们使用setFetchMode方法来设置获取结果集的返回值的数据类型,类型有:



&Oslash; PDO::FETCH_ASSOC??关联数组形式;



&Oslash; PDO::FETCH_NUM??数字索引数组形式;



&Oslash; PDO::FETCH_BOTH??两种数组形式都有,这是默认的;



&Oslash; 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";



}



}



?>
<< 优化linux磁盘性能 关于PHP你可能不知道的-PHP的事件驱动化设计 >>
评分
10987654321
API:
gipsky.com& 安信网络
网友个人意见,不代表本站立场。对于发言内容,由发表者自负责任。

系统导航

 

Copyright © 2001-2010 安信网络. All Rights Reserved
京ICP备05056747号