3-6-4 PHP之PDO
1.PDO的作用
PDO(php data object)扩展类库为PHP访问数据库定义了一个轻量级的、一致性的接口。
它提供了一个数据访问抽象层,这意味着,不管使用哪种数据库,都可以用相同的函数(方法)来查询和获取数据,能够屏蔽不同数据库之间的差异,使用PDO可用很方便地进行跨数据库程序开发以及不同数据库间的移植,支持mysql,postgresql,oracle,mssql等多种数据库。
理解:
-
首先,既然它是类库,那么在使用时候就必须要把类实例化为对象才能使用。
-
其次,PDO的作用就是为了屏蔽不同数据库之间的差异,即不管我们web开发时底层使用的是什么数据库,我们只需要知道标准SQL语句语法进行操作即可,PDO像一个懂很多国语言的翻译,负责翻译给底层不同的SQL数据库执行。
2. PDO的安装
一般来说只要是php的集成环境,PDO都是自带的,故默认phpstudy已经安装好了,我们只需要在phpstudy面板上,选项菜单,php扩展选项里勾选所需支持的数据库的PDO即可。然后查看phpinfo.php探针,查看是否有pdo,且还可以查看开启了哪些数据库的PDO。
3. 创建PDO对象与连接相关设置
1. 创建PDO对象:
try{
$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
//DSN(数据库来源名称/数据库驱动),即让网页知道数据库所在的位置以及数据库相关的属性。格式:数据库类型:库名字;数据库所在主机
$name = 'dbuser'; //数据库登录账号
$pwd = 'dbpass'; //数据库登录密码
$pdo = new PDO($dsn,$user,$password); // 实例化PDO类,生成PDO对象。
//以下为设置与连接相关的选项
//设置错误模式,推荐为异常处理模式。
$pdo -> setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMOODE_EXCEPTION);
//或简写方式:setAttribute(3,2);
//echo $pdo -> getAttribute(PDO::ATTR_ERRMODE); //得到属性的值。此处返回结果为2
//设置是否关闭自动提交功能,以下为关闭,默认为开启
//$pdo -> setAttribute(PDO::ATTR_AUTOCOMMI,0);
//设置查询数据库后返回的结果集的格式
//$pdo -> setAttribute(PDO::ATTR_ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC);
}catch(PDOException $e){ //将包含了PDO异常信息的PDOException类实例化为$e对象
//从$e对象中输出PDO的异常信息
echo $e -> getMessage(); //输出PDO异常信息
#echo $e -> getFile(); //输出PDO异常产生的文件
#echo $e -> getLine(); //输出PDO异常产生的行
#echo $e -> getCode(); //输出PDO异常码
} //try-catch结构,php面向异常处理常用的语法结构
异常处理示例(若以上异常反馈全输出):
2. 设置php连接数据库时,客户端字符串和连接字符串的字符集编码为:
$pdo->exec("set names utf8");
或者
$pdo->query("set names utf8");
//在项目开发中一般不使用,因为每次客户连接数据库时,我们后端php就会执行一次该语句,在实际运用中就会出现运行大量的set names utf8,导致后端环境和数据库效率降低。在开发项目中,我们只要保证脚本编辑器的字符集编码、数据库的字符集编码、HTML页面的编码三者一致便不会出现乱码现象。
PDO与连接数据库有关的设置及其选项:
PDO::ATTR_ERRMODE:(三种错误模式)
PDO::ERRMODE_SILENT // 默认,不报错
PDO::ERRMODE_WARNING // 出错则报出警告
PDO::ERRMODE_EXCEPTION // 推荐使用,一旦出错则抛出一个异常,可进行异常处理
PDO::ATTR_AUTOCOMMIT:(自动提交)
0 // 关闭自动提交,事务处理时要关闭自动提交
1 // 开启自动提交,事务处理完毕后要开启自动提交,MySQL中默认为开启状态。
PDO::ATTR_DEFAULT_FETCH_MODE:(设置查询数据库后返回的结果集的格式)
PDO::FETCH_ASSOC // 返回关联数组
PDO::FETCH_NUM // 返回索引数组,即数字数组
PDO::FETCH_BOTH // 返回关联数组和索引数组
PDO::FETCH_OBJ // 返回对象格式
4. 使用PDO对象
1. 调整PDO对象的行为属性:
setAttribute() //设置PDO行为属性的值
getAttribute() //得到PDO行为属性的值
2. 使用pdo对象执行sql语句:
$pdo->exec($sql); //执行语句对数据库及其表的结构和数据有影响时使用,如:增添删改
或者
$pdo->query($sql); //执行语句仅返回查询结果集时使用,如:查询
-
实例:
try{
$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$name = 'dbuser';
$pwd = 'dbpass';
$pdo = new PDO($dsn,$user,$password);
$pdo -> setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMOODE_EXCEPTION);
//echo $pdo -> getAttribute(PDO::ATTR_ERRMODE);
$username = $_GET['usr'];
$pwd = md5($_GET['pwd']);
$sql = "INSERT INTO users(username,password) VALUE('{$username}','{$pwd}')";
$pdo -> exec($sql);
//$sql = "SELECT * FROM users WHERE username='{$username}' and pwd='{$pwd}'";
//$pdo -> query($sql);
}catch(PDOException $e){
echo $e -> getMessage();
}
5. 使用PDO预处理对象
5.1 PDO预处理对象简介
在项目中,更多的使用到PDO预处理对象,而是不直接使用PDO对象,因为使用PDO预处理对象更加高效和安全。
未使用PDO预处理对象之前:每次客户端提交相应参数进行数据库查询时,php都要把用户参数与嵌入脚本的SQL语句进行拼接,然后每次都要发送一条完整的SQL语句给数据库,由数据库来编译和执行整条SQL语句。这样存在两个缺点:
-
低效:由于每次都要提交一条完整的SQL语句给数据库端,数据库端再进行一次完整的编译,这会占用太多的运行资源。
-
不安全:由于每次数据库都是编译和执行一条完整的SQL语句,这样如果用户在输入的参数中恶意破坏了原有SQL语句的结构,而数据库又完整地编译和执行了这整条SQL语句,很轻易就能造成SQL注入。
使用了PDO预处理对象之后:后端脚本先把嵌入的SQL语句发送给数据库编译,等待后续传参,而后续每次客户端提交相应参数进行数据库查询时,数据库对于这些后续的传参都不再进行编译,这样可以避免编译过程中会产生的逻辑运算,从而使那些客户端恶意输入依靠逻辑运算来破坏原有SQL语句的恶意参数失效,基本上能够避免SQL注入攻击。同时,仅一次SQL语句的编译也缓解了服务器运行压力,释放了一定的运行资源。
所谓编译,此处可简单理解为,将高级编程语言翻译为机器语言,使计算机认识我们所写的代码的意思。而此处我们使用PDO预处理对象后,数据库对后续传参都不进行编译,这些传参就将会被当作字符串来处理而不是转化成计算机所认识的机器语言去执行,故基本上能够防止SQL注入攻击。
5.2 PDOStatement对象的方法
5.2 PDOStatement对象的方法
5.2.1 采用占位符方式的PDO预处理对象
try{
$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$name = 'dbuser';
$pwd = 'dbpass';
$pdo = new PDO($dsn,$user,$password);
$pdo -> setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMOODE_EXCEPTION);
$sql = "INSERT INTO users(username,password,email) VALUE(?,?,?)";
//在嵌入的SQL语句里加入占位符“?”,通过这种方式来实现预编译、等待传参的功能。
//$sql = "SELECT * FROM users WHERE user = ? and password = ? ";
$stmt = $pdo -> prepare($sql); //定义一个PDO预处理对象
/*
注意:
1.PDO预处理对象不是手工实例化出来的,而是由方法执行后返回的。
即先通过prepare()方法预处理(编译)嵌入的SQL语句,再将其返回的结果作为PDO预处理对象。
2.说白了,PDO预处理对象就是一个已经预先在数据库端编译好了的、等待传参的SQL语句。
*/
/* ###### 传参方法一:参数绑定 ###### */
//为PDO预处理对象的变量赋值
$username = $_GET['usr'];
$pwd = md5($_GET['pwd']);
$email = $_GET['email'];
//为PDO预处理对象中的占位符绑定变量
$stmt -> bindParam(1,$username); //为第一个占位符“?”绑定变量
$stmt -> bindParam(2,$pwd); //为第二个占位符“?”绑定变量
$stmt -> bindParam(3,$email); //为第三个占位符“?”绑定变量
$stmt -> execute(); //执行SQL语句(此时变量的值传入数据库不再进行编译,直接代入编译好的SQL语句并执行)
/*
当SQL语句用SELECT进行查询时,如何获取查询结果。(此处设置返回结果集为关联数组)
$result = $stmt -> fetch(PDO::FETCH_ASSOC); //获取单条查询结果
$result = $stmt -> fetchAll(PDO::FETCH_ASSOC); //获取全部查询结果(二维数组)
$result = $stmt -> rowCount(); //返回查询结果的数量/或增添删改操作的影响行数
*/
/*
将查询结果(将数组元素)绑定变量,优化输出结果的显示样式
$stmt -> bindColumn(1,$id);
$stmt -> bindColumn(2,$uname);
$stmt -> bindColumn(3,$upass);
$stmt -> bindColumn(4,$ueml);
while($stmt -> fetch(PDO::FETCH_ASSOC)){
echo "--{$id}---{$uname}---{$upass}---{$ueml}--<br />"
}
*/
/* ###### 传参方法二:索引数组(省去了用bindParam()函数来绑定参数) ###### */
//为PDO预处理对象的变量赋值
$username = $_GET['usr'];
$pwd = md5($_GET['pwd']);
$email = $_GET['email'];
$stmt -> execute(array($username,$pwd,$email)); //执行SQL语句(三个参数分别对应SQL语句里的三个占位符)
}catch(PDOException $e){
echo $e -> getMessage();
}
5.2.2 采用别名方式的PDO预处理对象
try{
$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$name = 'dbuser';
$pwd = 'dbpass';
$pdo = new PDO($dsn,$user,$password);
$pdo -> setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMOODE_EXCEPTION);
$sql = "INSERT INTO users(username,password,email) VALUE(:username,:pwd,:email)";
//在嵌入的SQL语句里加入别名(注意别名需以:开头),通过这种方式来实现预编译、等待传参的功能。
//$sql = "SELECT * FROM users WHERE user = (:username) and password = (:pwd) ";
$stmt = $pdo -> prepare($sql); //定义一个PDO预处理对象
/* ###### 传参方法一:参数绑定 ###### */
//为PDO预处理对象的变量赋值
$username = $_GET['usr'];
$pwd = md5($_GET['pwd']);
$email = $_GET['email'];
//为PDO预处理对象中的占位符绑定变量
$stmt -> bindParam(":username",$username); //为第一个别名:username绑定变量
$stmt -> bindParam(":pwd",$pwd); //为第二个别名:pwd绑定变量
$stmt -> bindParam(":email",$email); //为第三个:email绑定变量
$stmt -> execute(); //执行SQL语句(此时变量的值传入数据库不再进行编译,直接代入编译好的SQL语句并执行)
/*
当SQL语句用SELECT进行查询时,如何获取查询结果。(此处设置返回结果集为关联数组)
$result = $stmt -> fetch(PDO::FETCH_ASSOC); //获取单条查询结果
$result = $stmt -> fetchAll(PDO::FETCH_ASSOC); //获取全部查询结果(二维数组)
$result = $stmt -> rowCount(); //返回查询结果的数量/或增添删改操作的影响行数
*/
/*
将查询结果(将数组元素)绑定变量,优化输出结果的显示样式
$stmt -> bindColumn(1,$id); //原表中第一列
$stmt -> bindColumn(2,$uname);
$stmt -> bindColumn(3,$upass);
$stmt -> bindColumn(4,$ueml);
while($stmt -> fetch(PDO::FETCH_ASSOC)){
echo "--{$id}---{$uname}---{$upass}---{$ueml}--<br />"
}
*/
/* ###### 传参方法二:关联数组(省去了用bindParam()函数来绑定参数)###### */
//为PDO预处理对象的变量赋值
$username = $_GET['usr'];
$pwd = md5($_GET['pwd']);
$email = $_GET['email'];
$arr = array("username"=>$username,"pwd"=>$pwd,"email"=>$email); //将数组默认键名改为"别名",此处别名不要:
$stmt -> execute($arr); //执行SQL语句
}catch(PDOException $e){
echo $e -> getMessage();
}
6. 使用PDO对象进行事务处理
6.1 事务处理简介
Transaction(事务、业务),通俗讲就是完成一件完整的事。事务处理是将多个操作或者命令一起执行,所有命令全部成功执行才意味着该事务的成功,任何一个命令失败都意味着该事务的失败,当事务失败时,我们需要将所有数据还原到初始的状态。任何一个子流程失败,都导致整个事物失败。
事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你既需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这些所有的数据库操作语句就构成一个事务!
此处MySQL中的事务处理为例,如转账操作:
A给B转账500元,就简单实现可分为两步,从A账户里扣除500元,再往B账户里添加500元。
-
如果整个过程顺利的完成了,则完成一次”汇款“事务的处理;
-
如果过程中A账户扣款了但B未收到、或A账户没有扣款等意外发生,则这个事务处理就是失败的,我们需要将数据还原回初始状态。
begin; # 开启事务处理
# start stransaction; # 开启事务处理(方法二)
update cash set money = money - 500 where username = 'A';
update cash set money = money + 500 where username = 'B';
# rollback; # 若是过程中事务处理失败,则通过回滚将数据还原回初始状态。
commit; # 结束事务处理
6.2 PDO对象进行事务处理
try{
$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$name = 'dbuser';
$pwd = 'dbpass';
$pdo = new PDO($dsn,$user,$password);
$pdo -> setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMOODE_EXCEPTION);
//echo $pdo -> getAttribute(PDO::ATTR_ERRMODE);
$pdo -> setAttribute(PDO::ATTR_AUTOCOMMIT,0);
// 关闭自动提交,事务处理时要关闭自动提交
// 通常数据库在我们开启事务处理就会关闭自动提交,但为了以防万一,此处手动添加自动提交为关闭。
$pdo -> beginTransaction(); // 开启事务处理
$sql = 'UPDATE cash SET money = money + 500 WHERE username = "A"';
$affectedRow = $pdo -> exec($sql); // A转出500元
if(!$affectedRow){
throw new PDOException("A转出失败!"); // 若转出操作过程中出现意外,则抛出异常,并转入异常处理模块。
}
$sql = 'UPDATE cash SET money = money + 500 WHERE username = "B"';
$affectedRow = $pdo -> exec($sql); // B转入500元
if(!$affectedRow){
throw new PDOException("B转入失败!"); // 若转入操作过程中出现意外,则抛出异常,并转入异常处理模块。
}
$pdo -> commit(); // 结束事务处理
echo "汇款成功!";
}catch(PDOException $e){
echo $e -> getMessage();
$pdo -> rollback(); // 回滚功能。事务处理中发生异常而失败时,需把所有数据恢复初始状态。
}
$pdo -> setAttribute(PDO::ATTR_AUTOCOMMIT,1);
// 开启自动提交,事务处理完毕后要开启自动提交。