PDO数据库抽象层总结

  PDO(PHP Data Objects)是一种在PHP里连接数据库的使用接口。PDO与mysqli曾经被建议用来取代原本PHP在用的mysql相关函数,基于数据库使用的安全性,因为后者欠缺对于SQL注入的防护。PDO的出现让PHP达到了一个新的高度。PDO扩展类库为PHP访问数据库定义了一个轻量级、一致性的接口,它提供了一个数据访问抽象层,这样,无论使用什么数据库,都可以通过一致的函数执行查询和获取数据,这大大简化了数据库的操作,并能够屏蔽不同数据库之间的差异。使用 PDO 可以很方便地进行跨数据库程序的开发,以及不同数据库间的移植,是将来PHP在数据库处理方面的主要发展方向。

   一、pod安装:

   php5.1以上的源代码包环境中,向configure命令中添加如下代码:

--with-pdo-MySQL=/url/local/MySQL    //其中“/url/local/MySQL”为MySQL安装目录

   二、php.ini 开启 PDO

extension = php_pdo.so

#下面开启其中之一
extension = php_pdo_MySQL.so    #使用 MySQL 驱动程序
;extension = php_pdo_mssql.so    #使用 SQL server 驱动程序
;extension = php_pdo_odbc.so    #使用 ODBC 驱动程序
;extension = php_pdo_ocl.so    #使用 oracle 驱动程序

  三、PDO 连接数据库:

<?php

/**
  * 连接 Oracle 数据库示例 -------------------------------------------------------------
  */
try {

    //第一个参数是 DSN 字符串加载 Oracle 数据库,第一参数指定数据库,第二个参数指定字符集
    $dbh = new PDO("OCI:dbname=accounts;charset=UTF-8", "userName", "password");
} catch(PDOException $e) {
    exit('数据库连接失败' . $e->getMessage());
}

/**
  * 连接 MySQL 数据库示例 -------------------------------------------------------------
  */
try {

    //第一个参数是 DSN 字符串加载 mysql 数据库,DSN 的第一参数指定数据库,第二个参数指定地址
//"mysql:dbname=DBtest;host=127.0.0.1" 就是 DSN 字符串,“mysql”就是驱动且必须小写,“dbname”指数据库,“host”指地址,DSN字符串内不能有空格。
$dbh = new PDO("mysql:dbname=DBtest;host=127.0.0.1", "username", "password"); } catch(PDOException $e) { exit('数据库连接失败' . $e->getMessage()); } /** * 将参数放入文件 -------------------------------------------------------------------------- */ try { //只要将文件/usr/local/dbconnect中的DSN驱动改变,就可以在多个数据库系统之间切换,但要确保该文件由负责执行PHP脚本的用户所拥有,而且此用户拥有必要的权限。 $dbh = new PDO("uri:file:///usr/local/dbconnect", "webuser", "password"); } catch(PDOException $e) { exit('数据库连接失败' . $e->getMessage()); } ?>

  

  只要在php.ini文件中把DSN信息赋给一个名为pdo.dsn.aliasname的配置参数,就可以在PHP服务器的配置文件中维护DSN信息,这里aliasname是后面将提供给构造函数的DSN别名。
  示例:

[PDO]
pdo.dsn.oraclepdo = "OCI:dbname=//localhost:1521/mysqldb;charset=UTF-8";

  四、PDO 操作数据库:

  1.示例数据库:

//结构:
CREATE table contactInfo (
    uid mediumint(8) unsigned NOT NULL AUTO_INCREMENT,  #联系人ID
    name varchar(50) NOT NULL,                          #姓名
    departmentId char(3) NOT NULL,                      #部门编号
    address varchar(80) NOT NULL,                       #联系地址
    phone varchar(20),                                  #联系电话
    email varchar(100),                                 #联系人的电子邮箱
    PRIMARY KEY(uid)                                    #设置用户ID为主键
);

//插入的示例数据:
INSERT INTO contactInfo (name, departmentId, address, phone, email) VALUES ('孙先生', 'D01', '北京市海淀区', '1580168001', 'shunsir@188.com');
INSERT INTO contactInfo (name, departmentId, address, phone, email) VALUES ('猪先生', 'D02', '北京市朝阳区', '1580168002', 'zhusir@188.com');
INSERT INTO contactInfo (name, departmentId, address, phone, email) VALUES ('沙先生', 'D03', '北京市东城区', '1580168003', 'shasir@188.com');
INSERT INTO contactInfo (name, departmentId, address, phone, email) VALUES ('唐先生', 'D01', '北京市西城区', '1580168004', 'tangsir@188.com');
INSERT INTO contactInfo (name, departmentId, address, phone, email) VALUES ('白先生', 'D01', '北京市昌平区', '1580168005', 'baisir@188.com');

  2.当执行INSERT、UPDATE和DELETE等没有结果集的查询时,使用PDO对象中的exec()方法。该方法成功执行后,将返回受影响的行数。注意,该方法不能用于SELECT查询。

<?php

try {
    $dbh = new PDO('mysql:dbname=testdb;host=localhost', 'dbuser', 'dbpassword');
} catch (PDOException $e) {
    exit('数据库连接失败' . $e->getMessage());
}

$query = 'UPDATE contactInfo SET phone = 13812345678 WHERE name = "孙先生"';

//使用 exec() 方法可以执行INSERT、UPDATE和DELETE等操作
$affected = $dbh->exec($query);

if($affected){
    echo '数据表 contactInfo 受影响的行数为' . $affected;
} else {
    print_r($dbh->errorInfo());
}
?>

  3.当执行返回结果集的SELECT查询,或者所影响的行数无关紧要时,应当使用PDO对象中的query()方法。

<?php

$dbh = new PDO('mysql:dbname=testdb;host=localhost', 'dbuser', 'dbpassword');
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$query = 'SELECT name, phone, email FROM contactInfo WHERE departmentId = "D01"';

try {

    //执行 SELECT 查询,并返回 PDOStatement 对象
    $pdostatement = $dbh->query($query);
    echo '一共从表中获取到' . $pdostatement->rowCount() . '条数据:<br>';
    foreach($pdostatement as $row){
        echo $row['name'] . '&nbsp;&nbsp;';
        echo $row['phone'] . '&nbsp;&nbsp;';
        echo $row['email'] . '<br>';
    }
} catch (PDOException $e) {
    echo $e->getMessage();
}
?>

  4.可以使用PDO过滤一些特殊字符,以防一些能引起SQL注入的代码混入。我们在PDO中使用quote()方法实现,示例如下:

<?php

$dbh = new PDO('mysql:dbname=testdb;host=localhost', 'dbuser', 'dbpassword');
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$query = 'SELECT * FROM users WHERE login = "' . $dbh->quote($_POST['user']) . ' AND password = ' . $dbh->quote($_POST['pass']);

?>

  五、PDO对预处理语句的支持

  SQL注入的最大原因是SQL语言的命令和数据混编,造成数据中被非法植入命令,特别是SQL语言的拼接是SQL注入的一大危害来源。PDO预处理语句使SQL语言的命令和数据分离,能有效减少SQL注入的危害,但没人打包票说是绝对有效的,因为技术每时每刻都在进步,就像发明SQL语言的人没有意识到还有SQL注入这个鬼一样。

  1.使用命名参数作为占位符的INSERT查询。

<?php

try {
    $dbh = new PDO('mysql:dbname=testdb;host=localhost', 'dbuser', 'dbpassword');
} catch (PDOException $e) {
    exit('数据库连接失败' . $e->getMessage());
}

$query = 'INSERT INTO contactInfo (name, address, phone) VALUES ( :name, :address, :phone)';

$stmt = $dbh->prerare($query);
$stmt->bindParam(':name', $name);
$stmt->bindParam(':address', $address);
$stmt->bindParam(':phone', $phone);

$name = '齐天大圣';
$address = '花果山水帘洞';
$phone = '15800000001';

$stmt->execute();    //执行绑定参数后的 SQL 语句

$name = '猪八戒';
$address = '高老庄';
$phone = '15800000003';

$stmt->execute();    //再次执行绑定参数后的 SQL 语句
?>

  2.使用 ? 号数作为占位符的INSERT查询。

<?php

try {
    $dbh = new PDO('mysql:dbname=testdb;host=localhost', 'dbuser', 'dbpassword');
} catch (PDOException $e) {
    exit('数据库连接失败' . $e->getMessage());
}

$query = 'INSERT INTO contactInfo (name, address, phone) VALUES ( ?, ?, ?)';

$stmt = $dbh->prerare($query);
$stmt->bindParam(1, $name);         //1是第一个?号
$stmt->bindParam(2, $address);     //2是第二个?号
$stmt->bindParam(3, $phone);        //3是第三个?号

$name = '齐天大圣';
$address = '花果山水帘洞';
$phone = '15800000001';

$stmt->execute();    //执行绑定参数后的 SQL 语句

$name = '猪八戒';
$address = '高老庄';
$phone = '15800000003';

$stmt->execute();    //再次执行绑定参数后的 SQL 语句
?>

  3.预处理查询在执行中替换输入参数的方式。此语法能够省去对$stmt->bindParam()的调用。此方法要在 execute() 中传入关联数组。

<?php

try {
    $dbh = new PDO('mysql:dbname=testdb;host=localhost', 'dbuser', 'dbpassword');
} catch (PDOException $e) {
    exit('数据库连接失败' . $e->getMessage());
}

$query = 'INSERT INTO contactInfo (name, address, phone) VALUES ( :name, :address, :phone);
$stmt->execute(":name"=>"齐天大圣", ":address"=>"花果山水帘洞", ":phone"=>"15800000001");
$stmt->execute(":name"=>"猪八戒", ":address"=>"高老庄", ":phone"=>"15800000002");
?>

  4.如果使用的是问号(?)参数,则需要传递一个索引数组,数组中每个值的位置都要对应每个问号参数。

<?php

try {
    $dbh = new PDO('mysql:dbname=testdb;host=localhost', 'dbuser', 'dbpassword');
} catch (PDOException $e) {
    exit('数据库连接失败' . $e->getMessage());
}

$query = 'INSERT INTO contactInfo (name, address, phone) VALUES ( ?, ?, ?)';
$stmt->execute("齐天大圣", "花果山水帘洞", "15800000001");
$stmt->execute("猪八戒", "高老庄", "15800000002");

?>

  5.使用 fetch() 查询数据库

<?php

try {
    $dbh = new PDO('mysql:dbname=testdb;host=localhost', 'dbuser', 'dbpassword');
} catch (PDOException $e) {
    exit('数据库连接失败' . $e->getMessage());
}

$query = 'SELECT uid, name, address, phone, email FROM contactInfo';

echo <<<Eof
    <table border="1" align="center" width="90%">
        <caption><h1>联系人信息表</h1></caption>
        <tr bgcolor="#ccc">
            <th>UID</th>
            <th>姓名</th>
            <th>地址</th>
            <th>电话</th>
            <th>电子邮箱</th>
Eof;

//使用 query() 方式查询数据库,建议用 prepare() 和 execute() 预处理语句执行查询
$stmt = $dbh->query($query);

while(list($uid, $name, $address, $phone, $email) = $stmt->fetch(PDO::FETCH_NUM)){
    echo '<tr>';
    echo '<td>' . $uid . '</td>';
    echo '<td>' . $name . '</td>';
    echo '<td>' . $address . '</td>';
    echo '<td>' . $phone . '</td>';
    echo '<td>' . $email . '</td>';
    echo '</tr>';
}

echo <<<Eof
    <table>
Eof;

?>

  6.fetchAll()方法与fetch()方法类似,但是该方法只需要调用一次就可以获取结果集中的所有行,并赋给返回的二维数组。使用fetchAll()方法代替fetch()方法,在很大程度上是出于方便的考虑。然而,使用fetchAll()方法处理特别大的结果集时,会给数据库服务器资源和网络带宽带来很大的负担。

<?php

try {
    $dbh = new PDO('mysql:dbname=testdb;host=localhost', 'dbuser', 'dbpassword');
} catch (PDOException $e) {
    exit('数据库连接失败' . $e->getMessage());
}

$query = 'SELECT uid, name, address, phone, email FROM contactInfo';

echo <<<Eof
    <table border="1" align="center" width="90%">
        <caption><h1>联系人信息表</h1></caption>
        <tr bgcolor="#ccc">
            <th>UID</th>
            <th>姓名</th>
            <th>地址</th>
            <th>电话</th>
            <th>电子邮箱</th>
Eof;

$stmt = $dbh->prepare($query);
$stmt->execute();
$allRows = $stmt->fetchall(PDO::FETCH_ASSOC);

while($allRows as $row){
    echo '<tr>';
    echo '<td>' . $row['uid'] . '</td>';
    echo '<td>' . $row['name'] . '</td>';
    echo '<td>' . $row['address'] . '</td>';
    echo '<td>' . $row['phone'] . '</td>';
    echo '<td>' . $row['email'] . '</td>';
    echo '</tr>';
}

echo <<<Eof
    <table>
Eof;

/* 以下是 fetchall() 方法中使用两个特别参数的示例 */
$stmt->execute();
$rows = $stmt->fetchall(PDO::FETCH_COLUMN, 1);
echo '所有联系人的性名:';
print_r($rows);

?>

  7.bindColumn() 方法,使用该方法可以将一个列和一个指定的变量名绑定,这样在每次使用fetch()方法获取各行记录时,会自动将相应的列值赋给该变量,但前提是 fetch()方法的第一个参数必须设置为 PDO::FETCH_BOTH的值。

<?php

try {
    $dbh = new PDO('mysql:dbname=testdb;host=localhost', 'dbuser', 'dbpassword');
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    exit('数据库连接失败' . $e->getMessage());
}

$query = 'SELECT uid, name, phone, email FROM contactInfo WHERE departmentId = "D01"';

try {
    $stmt = $dbh->prepare($query);
    $stmt->execute();

    $stmt->bindColumn(1, $uid);                     //通过列位置偏移量绑定变量 $uid
    $stmt->bindColumn(2, $name);                //通过列位置偏移量绑定变量 $name
    $stmt->bindColumn('phone', $phone);     //通过列名称绑定变量 $phone
    $stmt->bindColumn('email', $email);       //通过列名称绑定变量 $email

    while($stmt->fetchall(PDO::FETCH_BOUND)){
        echo $uid . '&nbsp;&nbsp;' . $name . '&nbsp;&nbsp;' . $phone . '&nbsp;&nbsp;' . $email . '<br>';
    }
} catch (PDOException $e) {
    echo $e->getMessage();
}

?>

8.在进行项目开发时,有时需要在数据库中存储“大型”数据。大型对象可以是文本数据,也可以是二进制数据形式的图片、视频等。PDO 允许在 bindParam()或 bindColumn()调用中通过使用PDO::PARAM_LOB类型代码来使用大型数据类型。PDO::PARAM_LOB告诉PDO将数据映射为流,所以可以使用PHP中的文件处理函数来操纵这样的数据。

  示例:

  数据写入代码:

<?php
/**
 * 数据库:testdb
 *
 * 数据表创建的结构
 *
 * CREATE TABLE `images`(
 *    `id` mediumint(8) unsigned NOT NULL AOTU_INCREMENT,
 *    `contenttype` varchar(50) NOT NULL,
 *    `imagedata`    blob NOT NULL,
 *    PRIMARY KEY(`id`)
 * )
 */
 
if(filter_has_var(INPUT_POST, "isFile")){
    try {
        $dbh = new PDO('mysql:dbname=testdb;host=localhost', 'mysqlUser', 'mysqlPass');
        $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    } catch (PDOException $e) {
        exit('数据库连接失败' . $e->getMessage());
    }

     $stmt = $dbh->prepare("INSERT INTO images(contenttype, imagedata) VALUE (?, ?)");
 
     $fp = fopen($_FILES['file']['tmp_name'], 'rb');
 
     $stmt->bindParam(1, $_FILES['file']['type']);
     $stmt->bindParam(2, $fp, PDO::PARAM_LOB);
     $stmt->execute();
}
?>
    
<form action="" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <input type="hidden" name="isFile">
    <input type="submit" value="提交">
</form>

  数据读取代码:

<?php
try {
    $dbh = new PDO('mysql:dbname=testdb;host=localhost', 'mysqlUser', 'mysqlPass');
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    exit('数据库连接失败' . $e->getMessage());
}

$stmt = $dbh->prepare("SELECT contenttype, imagedata FROM images WHERE id=?");
$stmt->execute(array($_GET["id"]));
list($type, $lob) = $stmt->fetch(PDO::FETCH_NUM);
if(!empty($type)){
    header("Content-Type: $type");
    echo $lob;
}else{
    echo 'No pictures';
}
?>

  9.PDO 对 MySQL 数据库的事务处理:

<?php
/**
  * use testdb
  *
  * CREATE TABLE account(
  *    id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
  *    name VARCHAR(50) NOT NULL,
  *    cash DECIMAL(9,2) NOT NULL,
  *    PRIMARY KEY(id)
  * ) engine=InnoDB;
  *
  * INSERT INTO account (name, cash) VALUES ("userA", 1000);
  * INSERT INTO account (name, cash) VALUES ("userB", 9000);
  *
  * 以下是 userA 用户转 80 元给 userB 用户
  */
  
try {
    $dbh = new PDO('mysql:dbname=testdb;host=localhost', 'mysqlUser', 'mysqlPassword');
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    exit('数据库连接失败' . $e->getMessage());
}

$dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, 0);    //关闭数据库自动提交

try{
    $price = 80;    //转账金额 userA -> userB
    $dbh->beginTransaction();    //开启数据库事务
    $affected_rows = $dbh->exec("UPDATE `account` SET `cash` = `cash` - {$price} WHERE `name` = 'userA'");    //转出
    
    if($affected_rows > 0){
        echo "userA用户成功转出{$price}元人民币<br>";
    }else{
        throw new PDOException("userA用户转出失败");
    }
    
    $affected_rows = $dbh->exec("UPDATE `account` SET `cash` = `cash` + {$price} WHERE `name` = 'userB'");    //转入
    if($affected_rows > 0){
        echo "userB用户成功转入{$price}元人民币<br>";
    }else{
        throw new PDOException("userB用户转入失败");
    }
    
    $dbh->commit();    //事务处理
    echo '交易成功!';
} catch (PDOExcaption $e){
    echo '交易失败' . $e->getMessage();
    $dbh->roolback();    //SQL 提交失败,数据库回滚
}

$dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, 1);    //重启数据库自动提交功能
?>
原文地址:https://www.cnblogs.com/qingsong/p/13982909.html