PHP之PDO的理解和实践

󰃭 2016-08-02

概念

什么是PDO, 即PHP DATA OBJECT,是一种抽象的数据库接口, 其本身并不会有任何的数据库功能, 只是定义了一些数据操作的接口规范。

如果需要针对某个数据库的操作, 需要对应的数据库类型的PDO驱动。这样才能真正的操作数据库.

同时, PDO 是需要PHP的面向对象的支持

优点

那么PDO 有什么优点呢

灵活

PDO 提供的是一种抽象的接口定义, 每种实际的PDO驱动都要实现这套定义。

既然接口一样, 那我们再代码中就能通过很少的代价对数据库进行替换了。 实际的操作只是更改了数据库驱动

安全

通过使用PDO的预处理语句,我们可以直接将查询参数传递进来, 而无需担心sql注入的问题

快速

当需要执行多个相似的sql查询语句, 而仅仅是查询参数不同的操作时, 通过预处理语句, 可以避免数据库对查询语句的重复 解析/编译/优化 的过程, 从而提高运行速度

PDO 类说明

PDO 的实现有主要有两个类, 在此说明下

PDO 类

这个类主要用于保存用户的数据库连接, 并设置连接参数,操作事务

通过这个类的实例, 我们还可以获取一些当前使用的数据库驱动的参数 比如: 当前连接是否处于事务状态,当前使用的是哪种数据库驱动

通过这个类实例的prepare方法, 我们可以预处理要执行的sql语句,这个函数返回的结果实际上就是下面要说的另外一个类PDOStatement类的实例

此外, 这个类的实例也可以直接对数据库发起请求, 比如execquery 函数

PDOStatement 类

代表一条预处理语句,并在该语句被执行后代表一个相关的结果集。

这是官方的一个说明。太过简单

上面 PDO 类实例的prepare query函数的返回结果, 实际上也是PDOStatement的实例。

这个类的实例拥有对数据库请求结果的多种操作和设置, 特别是,为PDO连接预处理sql语句时,其拥有的快速安全的特性,很受推崇

实践

PDO 类实践

连接数据库

try{
    $dbh = new PDO("mysql:host=localhost;dbname=test_db;charset=utf8","root","1");
}catch(PDOException $e){
    die("Error: $e");
}

这里我们连接上数据库后创建了一个PDO的类实例$dbh

获取数据库驱动类型

var_dump($dbh->getAvailableDrivers());

可以看到输出是

array(1) {
  [0]=>
  string(5) "mysql"
}

执行query

var_dump($stm = $dbh->query("select id,title from article",PDO::FETCH_NAMED));

这里我们直接使用PDOquery 函数,第二个参数控制返回结果集, 比如返回的是一个关联数组, 用字段名作key,用查询结果做value时用PDO::FETCH_NAMED 参数, 或者要返回一个索引数组, 直接用偏移量做key时, 用PDO::FETCH_NUM 参数

在这里, 我们可以通过var_dump得到$stm的结果实际上就是PDOStatement类的实例

输出如下

object(PDOStatement)#2 (1) {
  ["queryString"]=>
  string(28) "select id,title from article"
}

结果处理部分参考 PDOStatement 类实践

预处理

当我们有多个相似的语句, 这些语句唯一的区别就是参数不同, 或者需要执行多次的语句, 这个时候我们就比较适合使用预处理语句了

预处理语句可以预先解析,编译,优化sql语句, 返回一个PDOStatement类实例, 再调用该实例的execute函数, 传递不同的参数就能得到不同的结果, 同时, 因为避免了多次解析编译优化的过程, 可以使得后面的query请求更快

 $stm = $dbh->prepare("select id,title from article where cid = :cid");

至于其中的:cid,其实只是一个占位符, 后面通过调用PDOStatementbindParambindValue方法就可以通过变量将参数传递进去, 这里的:cid是关键词占位符, 每个关键词不可重复

或者这里也可以是

 $stm = $dbh->prepare("select id,title from article where cid = ?");

? 也是一个占位符, 通过调用PDOStatementbindParambindValue 方法也能传递参数进去, 也可以通过在调用execute时传递一个索引数组,数组里面的元素与?占位符一一对应, 也能传递参数到sql的请求里

PDOStatement 类实践

在上面我们通过两种方式得到了这个类实例

PDOquery 函数 和 PDO的预处理函数prepare

我们先说对调用prepare 后的这个类实例

bindParam

prepare当我们使用占位符时,我们可以通过bindParam 将关键字与某个变量关联起来, 然后调用execute, 就能得到传递了参数的查询结果

$cid = 6;
var_dump($stm = $dbh->prepare("select id,title from article where cid = :cid"));
$stm->bindParam(":cid",$cid);
$stm->execute();

注意这里的绑定是引用绑定, 如果后面我们继续如下

$cid = 5;
$stm->execute();

实际的查询将是直接传递新的cid参数而无须再次绑定

对使用?来说, 绑定的关系只是从关键词变成了偏移量,

$cid = 6;
var_dump($stm = $dbh->prepare("select id,title from article where cid = ?"));
$stm->bindParam(0,$cid);
$stm->execute();

bindValue

bindValuebindParam 其实很类似, 也是为占位符填充具体的参数,绑定的方式基本上也与bindParam一致,只要把bindParam 换成 bindValue 即可

但其余bindParam有什么区别呢

bindParam 是引用方式绑定的变量, 只要变量做了修改, 那么query 传递的参数就发生了变化。bindParam的例子里有说明

bindValue 只是绑定具体的值, 而非引用方式, 所以query 传递的参数一旦需要修改,就要重新bindValue,所以要注意这点

execute

prepare并绑定好参数后, 我们就可以直接调用execute 去执行query请求了

$stm->bindParam(":cid",$cid);
$stm->execute();

假如prepare的query 中用的是?占位符, 还可以直接使用 execute 传递一个数组, 数组元素与query中的?一一对应, 也是可以传递请求参数的

setFetchMode

设置返回结果的格式, 比如是返回关联数组(PDO::FETCH_NAMED)结果还是索引数组(PDO::FETCH_NUM)结果

$stm->setFetchMode(PDO::FETCH_NAMED);

fetch 系列函数

然后我们就可以通过fetch系列函数返回结果了

如:

$stm->fetchall();//返回所有查询结果
$stm->fetch();//从结果集的下一行返回结果
$stm->fetchColumn();//从结果集中的下一行返回单独的一列。
$stm->fetchObject();// 从结果集的下一行返回结果并以对象的方式返回

PDOquery函数的执行结果就可以通过这些函数返回

closeCursor

关闭游标,使语句能再次被执行。

在有些数据库驱动中, 如果查询结果不被fetch 完, 后续的查询就不能继续执行, 所以再查询完成后, 我们要调用这个函数,防止这种问题发生

总结

以上一个完整的例子如下:

try{
    $dbh = new PDO("mysql:host=localhost;dbname=test_db;charset=utf8","root","1");
}catch(PDOException $e){
    die("Error: $e");
}

var_dump($dbh->getAvailableDrivers());
$cid = 6;
$stm = $dbh->prepare("select id,title from article where cid = :cid");
$stm->bindParam(":cid",$cid);
$stm->execute();
$stm->setFetchMode(PDO::FETCH_NAMED);
while($row = $stm->fetch()){
    var_dump("hello");
    var_dump($row);
}
$stm->closeCursor();