PHP面向对象04:PDO
一、PDO概述
- PDO 提供了一套帮助用户实现多数据库操作的统一接口,通过使用PDO,开发人员不需要额外自定义数据库对应的操作类,从而简化开发过程。
- PDO 类:实现统一的数据库的初始化操作,包括连接认证和执行SQL指令。
- PDOStatement 类:数据解析操作,主要针对数据结果操作(有数据结果返回)。
- PDOException 类:异常处理操作,针对所有PDO操作可能出现的错误采用的异常处理模式。
二、PDO操作
1. PDO类基本应用
-
PDO::__construct()
:实例化PDO对象。__construct(string $dsn, string $user, string $pass[, array $drivers])
实现实例化对象。- 构造方法由4个参数组成,前三个必填,第四个可选。
$dsn
:一个数据库基本信息字符串,包含数据库产品、主机地址等。- 驱动名字(数据库产品),使用
:
分隔。 - 驱动选项(主机地址):使用
host=具体主机地址
,跟在驱动名字之后 - 驱动选项(端口):使用
port=端口号
,默认为3306可以不写,拼凑在驱动名字后,不区分先后顺序。 - 驱动选项(数据库名字):使用
dbname=数据库名字
(可以事先没有)
- 驱动名字(数据库产品),使用
$drivers
:PDO属性设置,是关联数组,利用PDO内部的常量进行设置。
-
PDO::exec()
:执行写操作SQL指令,返回受影响的行数 -
PDO::query()
:执行一个读操作SQL指令,返回一个 PDOStatement 类对象。 -
PDO::errorCode()
和PDO::errorInfo()
:获取上次错误的信息。 -
SQL 执行的结果不论是写操作还是读操作都有可能出错,因此需要进行错误处理。
<?php
$dsn = 'mysql:host=localhost;dbname=test';
$user = 'root';
$pass = "";
$pdo = new PDO($dsn, $user, $pass);
# 写数据
$sql = "update test set id = 2 where id = 1";
$res = $pdo->exec($sql);
var_dump($res);
# 读数据
$sql = "select * from test";
$res = $pdo->query($sql);
# 判定结果,错误处理
if ($res === false) {
echo "SQL错误" . "<br>";
echo "错误代码为:" . $pdo->errorCode() . "<br>";
echo "错误原因为:" . $pdo->errorInfo()[2] . "<br>";
exit;
}
var_dump($res);
2. 写操作
- 实际使用 PDO 的时候,都会进行二次封装,因为PDO的操作有很多本身不够完善。
- PDO可以独立完成写操作功能,而不需要其他两个工具类。
- PDO的写操作其实本质要注意的是执行SQL时可能出现的错误处理(注意外部数据)。
- 写操作中唯一不同的是插入操作,因为可能需要获取自增长ID,此时需要多一个步骤(功能)。
<?php
# 初始化封装函数
function pdo_init() {
$dsn = 'mysql:host=localhost;dbname=test';
$pdo = @new PDO($dsn, 'root', "");
if (!$pdo) {
die("数据库连接认证失败");
}
# 字符集
$pdo->exec('set names utf8');
return $pdo;
}
# 写操作封装函数
function pdo_exec(PDO $pdo, $sql) {
$res = $pdo->exec($sql);
# 错误判定
if ($res === false) {
echo "SQL错误" . "<br>";
echo "错误代码为:" . $pdo->errorCode() . "<br>";
echo "错误原因为:" . $pdo->errorInfo()[2] . "<br>";
exit;
}
return $res;
}
$pdo = pdo_init();
$res = pdo_exec($pdo, "insert test values(4, 'wwww')");
var_dump($res);
3. 查询操作
- 通过
PDO::query()
执行查询SQL得到PDOStatement对象,然后PDOStatement对象下有一系列fetch
方法可以实现数据查询,得到PHP可以识别的数组数据。 - PDO实现查询通常也需要进行二次封装,保证SQL执行安全,也方便用户获取目标数据。
<?php
# 初始化封装函数
function pdo_init() {
$dsn = 'mysql:host=localhost;dbname=test';
$pdo = @new PDO($dsn, 'root', "");
if (!$pdo) {
die("数据库连接认证失败");
}
# 字符集
$pdo->exec('set names utf8');
return $pdo;
}
# 查询操作封装
function pdo_query(PDO $pdo, string $sql) {
$stmt = $pdo->query($sql);
# 错误判定
if ($stmt === false) {
echo "SQL错误" . "<br>";
echo "错误代码为:" . $pdo->errorCode() . "<br>";
echo "错误原因为:" . $pdo->errorInfo()[2] . "<br>";
exit;
}
return $stmt;
}
# 读取数据,默认只获取一条记录
function pdo_get(PDOStatement $stmt, $only = true, $fetchStyle = PDO::FETCH_ASSOC) {
if ($only) {
return $stmt->fetch($fetchStyle);
} else {
return $stmt->fetchAll($fetchStyle);
}
}
$pdo = pdo_init();
$stmt = pdo_query($pdo, "select * from test");
var_dump(pdo_get($stmt));
var_dump(pdo_get($stmt, false));
4. PDO事务功能
- 事务执行是否成功由MySQL对应的存储引擎是否支持决定。
- 事务功能:事务是指将默认的机制(一次操作,系统就会有一次写入数据表)改变为通过事务日志记录操作,最后通过一次性操作写入到数据表。
- 开启事务:start transaction,写操作停止直接写入数据表,而是记录到事务日志。
- 事务操作:具体的写操作,通常多个步骤多条指令。
- 提交事务:即事务操作结束。成功提交:commit,所有事物日志内容同步到数据表,并清空当前事务日志。失败回滚:rollback,直接清空当前事务日志。
- PDO类提供的事务操作:
PDO::beginTransaction()
:开启事务PDO::exec()
:执行事务(写操作)PDO::rollback()
:回滚所有事务PDO::commit()
:成功提交所有事务
<?php
# 实例化PDO对象
$pdo = @new PDO('mysql:host=localhost;dbname=test', 'root', "");
# 开启事务
$pdo->beginTransaction() or die("事务开启失败");
# 执行事务
$pdo->exec("insert into test values (5,'4444')");
# 设置回滚点
$pdo->exec('savepoint sp1');
# 终止事务
$pdo->commit();
$pdo->rollBack();
三、PDO异常
1. PHP异常机制
- 如果想要实现异常处理,必须借助于系统提供的
set_error_handler
来告知系统我们想采用的处理模式。 - 异常机制是利用
try{}catch(Exception $e){}
来进行捕捉和处理的。
<?php
set_error_handler(function () {
throw new Exception('错误回调修改默认异常处理模式');
});
try {
$res = $n1 / $n2;
} catch (Exception $exception) {
echo "代码运行出错<br>";
echo "错误文件为:" . $exception->getFile() . '<br>';
echo "错误行号为:" . $exception->getLine() . '<br>';
echo "错误描述为:" . $exception->getMessage() . '<br>';
die();
}
2. PDO错误机制
- PDO错误机制是指PDO在使用过程中出现了错误(SQL指令执行错误)的时候,PDO处理错误的方式。
- PDO提供了三种错误机制,是通过PDO的常量PDO::ATTR_ERRMODE来选择的
- PDO::ERRMODE_SILENT:静默模式,出错了不处理(默认的)
- PDO::ERRMODE_WARNING:警告模式,出错了马上给出错误提示
- PDO::ERRMODE_EXCEPTION:异常模式,出错了将错误交给异常PDOException对象。
<?php
$pdo = @new PDO('mysql:host=localhost;dbname=test', 'root', "");
$pdo->exec('set names utf8');
# 警告模式
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$pdo->exec('insert into test values');
# 异常模式
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try {
$pdo->exec('insert into test values');
} catch (Exception $e) {
echo "SQL运行错误" . "<br>";
echo "错误代码为:" . $pdo->errorCode() . "<br>";
echo "错误原因为:" . $pdo->errorInfo()[2] . "<br>";
exit;
}
3. PDOException异常处理
- 在初始化PDO对象的时候,利用第四个参数来设定。
- 初始化PDO后,利用PDO::setAttribute()方法来修改错误模式。
# 初始化PDO时设定错误模式
$drivers = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
try {
$pdo = @new PDO('mysql:host=localhost;dbname=test', 'root', "", $drivers);
} catch (Exception $e) {
echo "数据库连接失败!<br>";
echo "错误文件为:" . $e->getFile() . "<br>";
echo "错误行号为:" . $e->getLine() . "<br>";
echo "错误描述为:" . $e->getMessage();
die();
}
四、预处理
1. MySQL预处理
- prepare 预处理:是指客户端将要执行的SQL先发送给服务器,服务器先进行编译,不执行,等到客户端需要服务器端执行的时候,发送一条执行指令,让服务器再执行已经提前处理好(预处理)的SQL指令。
- 实现预处理
- 发送预处理:prepare 预处理名字 from ‘要重复执行的SQL指令’;
- 执行预处理:execute 预处理名字
prepare student_select from 'select * from students'; execute student_select;
- 预处理占位
- 预处理占位符:在预处理指令中要执行的SQL指令,使用
?
来代替未知数据部分。 - 预处理执行(using):在执行预处理的时候将对应的数据携带到预处理指令中。
prepare student_select from 'select * from students where id=? and name=?'; set @id = 1; set @name = "张三"; execute student_select using @id,@name;
- 预处理占位符:在预处理指令中要执行的SQL指令,使用
- 删除预处理
drop prepare student_select;
2. PDO预处理
- PDO 提供了一套方法机制
PDO::prepare()
:发送预处理指令,只需要提供要执行的指令即可,不需要prepare名字from。成功返回PDOStatement类对象,失败返回false(或异常错误)PDOStatement::bindParam()
:绑定预处理所需要的参数,只能绑定变量(引用传递)PDOStatement::bindValue()
:绑定预处理所需要的参数,只能绑定值(值传递)PDOStatement::execute()
:执行预处理,成功返回true,失败返回false
- bindValue 和 bindParam 的区别
- bindValue 绑定数据的方式灵活,可以是变量也可以是数据常量。
- bindParam 只能是变量。
<?php
$drivers = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
$pdo = @new PDO('mysql:host=localhost;dbname=test', 'root', "", $drivers);
# 发送预处理指令
$pre_sql = "select * from test where id = :id";
$stmt = $pdo->prepare($pre_sql);
# 绑定参数
$stmt->bindValue(":id", 4);
# 执行预处理
$res = $stmt->execute();
if (!$res) die("执行失败");
# 获取结果
$line = $stmt->fetch(PDO::FETCH_ASSOC);
var_dump($line);
五、封装PDO
- PDO的使用时经常性的,需要将PDO变得更加灵活,实现代码复用,所以需要对PDO进行二次封装。
<?php
namespace core;
use \PDO, \PDOStatement, \PDOException;
class Dao {
private $pdo;
private $fetchMode;
public function __construct($databaseInfo = array(), $drivers = array()) {
$type = $databaseInfo['type'] ?? 'mysql';
$host = $databaseInfo['host'] ?? 'localhost';
$port = $databaseInfo['port'] ?? '3306';
$user = $databaseInfo['user'] ?? 'root';
$pass = $databaseInfo['pass'] ?? '';
$dbname = $databaseInfo['dbname'] ?? 'test';
$charset = $databaseInfo['charset'] ?? 'utf8';
# 驱动控制
$this->fetchMode = $databaseInfo['fetch_mode'] ?? PDO::FETCH_ASSOC;
$drivers[PDO::ATTR_ERRMODE] = $drivers[PDO::ATTR_ERRMODE] ?? PDO::ERRMODE_EXCEPTION;
# 实例化PDO对象
try {
$this->pdo = @new PDO($type . ":host=" . $host . ";port=" . $port . ";dbname=" . $dbname, $user, $pass, $drivers);
} catch (PDOException $e) {
$this->dao_exception($e, "数据库连接失败!");
}
# 设定字符集
try {
$this->pdo->exec("set names {$charset}");
} catch (PDOException $e) {
$this->dao_exception($e);
}
}
# 写操作
public function dao_exec($sql) {
try {
return $this->pdo->exec($sql);
} catch (PDOException $e) {
$this->dao_exception($e);
}
}
# 获取自增长ID
public function dao_insert_id() {
return $this->pdo->lastInsertId();
}
# 读操作
public function dao_query($sql, $only = true) {
try {
$stmt = $this->pdo->query($sql);
$stmt->setFetchMode($this->fetchMode);
if ($only) {
$row = $stmt->fetch();
if (!$row) throw new PDOException("当前查询没有数据");
return $row;
} else {
$rows = $stmt->fetchAll();
if (!$rows) throw new PDOException("当前查询没有数据");
return $rows;
}
} catch (PDOException $e) {
$this->dao_exception($e);
}
}
private function dao_exception($e, $tip = "SQL执行错误!") {
echo $tip . "<br>";
echo "错误文件为:" . $e->getFile() . "<br>";
echo "错误行号为:" . $e->getLine() . "<br>";
echo "错误描述为:" . $e->getMessage();
die();
}
}
$dao = new Dao();
$line = $dao->dao_query("select * from test", false);
var_dump($line);
版权声明:本文为realoser原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。