PDO
PDO类:数据库初始化,包括连接认证和执行sql指令
PDOStatement类:数据解析操作,针对数据结果操作(有数据结果返回)
PDOException类:数据库异常类
加载PDO扩展:php5以后自动加载,无须手动加载
开启PDO MYSQL扩展-修改php.ini:extension=pdo_mysql;重启web服务器.使用phpinfo()函数查看是否加载成功.
PDO常用方法
PDO::__construct():实例化PDO对象 PDO::exec():执行一个写操作SQL指令,返回受影响的行数
PDO::query():执行一个读操作SQL指令,返回一个PDOStatement类对象(后者进行数据解析操作).
PDO::errorCode()和PDO::errorInfo():获取上次错误的信息(错误码和错误描述数组)
实例化PDO对象
使用PDO::__construct(string $dsn, string $user, string $password[, array $drivers])实例化PDO对象
$dsn:数据库基本信息
驱动名字-数据库产品,使用英文:分割,比如mysql:
驱动选项-主机地址,使用 host=主机地址,跟着驱动名字之后,如’mysql:host=localhost’
驱动选项-端口,使用port=端口号,默认3306可不写.可写为为’mysql:host=localhost;port=3306;’或’mysql:port=3306;host=localhost;’
驱动选项-数据库,使用dbname=数据库名字(可以事先没有)
$user:数据库用户名(如果数据库允许匿名用户,那么可以没有该参数)
$password:密码(若允许匿名用户,和用户名一样…)
$drivers:PDO属性设置.是关联数组.利用PDO内部常量进行设置
示例:
$pdo = new PDO(‘mysql:host=localhost;port=3306;dbname=mydb’, ‘root’, ‘123456’);
或
$dsn= ‘mysql:host=localhost;port=3306;dbname=mydb’;
$user=’root’;
$password=’123456′;
$pdo=new PDO($dsn,$user,$password);
PDO写操作和读操作
建立pdo对象后,我们可以使用exec方法执行写操作;query方法执行读操作.
写操作
$sql = “delete from tab_student order by s_id desc limit 1”; //写操作sql指令
$rows= $pdo->exec($sql); //执行,得到受影响的行数.如果没有受影响的行,则返回0
读操作
$sql=’select * from tab_student’; //读操作sql
$stmt = $pdo -> query($sql); //执行读操作.成功返回PDOStatement对象,失败返回false
PDO错误处理errorCode()和errorInfo()
$pdo = new PDO(‘mysql:host=localhost;port=3306;dbname=mydb’, ‘root’, ‘123456’);//实例化PDO对象
$sql = “delete fromstudent”; //一条错误sql语句
$rows= $pdo->exec($sql); //执行
//若sql执行成功exec()也可能返回0.SQL执行错误则返回false.
//所以判断sql是否错误,需要判断结果为false
if(false===$rows){//无论sql读/写错误,都返回false
echo ‘sql执行出错:<b+r>’;
echo ‘错误代码为:’ . $pdo->errorCode() . ‘<b+r>’;
echo ‘错误信息为:’ . $pdo->errorInfo()[2] . ‘<b+r>’;//errorInfo返回数组,下标2代表具体错误信息
exit;//退出-错误不要继续执行代码
}
PDO简单封装
//初始化PDO
//mypdo.php
function pdo_init(){
$pdo= @new PDO(‘mysql:host=localhost;port=3306;dbname=mydb’,’root’,’123456′);
if(!$pdo){
exit(‘数据库连接认证失败!’);
}
//字符集初始化-好像新版本不需要?
$pdo->exec(‘set names utf8’);// $pdo->exec(‘set names utf8mb4’);
//返回得到的pdo对象
return $pdo;
}
//PDO写操作
function pdo_exec($pdo,$sql){
$res=$pdo->exec($sql);
if(false===$res){//错误判定
echo ‘sql执行出错:<b+r>’;
echo ‘错误代码为:’ . $pdo->errorCode() . ‘<b+r>’;
echo ‘错误信息为:’ . $pdo->errorInfo()[2] . ‘<b+r>’;//errorInfo返回数组,下标2代表具体错误信息
exit;//退出-错误不要继续执行代码
}
return $res;//返回结果,受影响的行数
}
//写操作获取自增长id
function pdo_id($pdo){
return $pdo->lastInsertId();
}
//PDO读操作-查询
function pdo_query($pdo, $sql){
$stmt = $pdo->query($sql);
if(false===$stmt){//错误判定
echo ‘sql执行出错:<b+r>’;
echo ‘错误代码为:’ . $pdo->errorCode() . ‘<b+r>’;
echo ‘错误信息为:’ . $pdo->errorInfo()[2] . ‘<b+r>’;//errorInfo返回数组,下标2代表具体错误信息
exit;//退出-错误不要继续执行代码
}
return $stmt;//返回查询结果
}
//PDO读操作后获取查询结果
function pdo_get($stmt, $only = true){
if($only){//
//fetch()默认返回关联数组+索引数组;FETCH_ASSOC返回关联数组;FETCH_NUM返回索引数组
return $stmt->fetch(PDO::FETCH_ASSOC);//获取一条记录:返回一维数组
} else{
//fetchAll()默认返回关联数组+索引数组;FETCH_ASSOC返回关联数组;FETCH_NUM返回索引数组
return $stmt->fetchAll(PDO::FETCH_ASSOC);//返回二维数组,一个维度代表一个记录
}
}
//实际编程并调用封装PDO的代码
include ‘mypdo.php’;
$pdo=pdo_init();//初始化
$sql=”select * from student limit 1″;//查询语句
$stmt = pdo_query($pdo,$sql);//执行读操作查询
$row=pdo_get($stmt);//获取一条记录
$rows=pdo_get($stmt,false);//获取多条记录
PDO事务
mysql事务
开启事务:start transaction,记录到事务日志
事务操作:具体的写操作,通常为多个指令
提交事务:本次事务结束
成功提交:commit,所有事务日志内容同步到数据库,并清空当前事务日志
失败回滚:rollback,直接清空当前事务日志
PDO实现事务
PDO::beginTransaction():开启事务
PDO::exec():执行事务(写操作)
PDO::rollBack():回滚所有事务
PDO::commit():成功提交所有事务
示例代码:
$pdo=new PDO(‘mysql:host=localhost;prot=3306;db=mydb’, ‘root’, ‘123456’);
$pdo->beginTransaction() or die(‘事务开启失败’);
$pdo->exec(‘insert into tab_students values()’);
回滚点(手写)
mysql没有提供回滚点,我们可以手写,以下为示例
//接上述代码
$pdo->exec(‘savepoint sp1′); //mysql存回滚点命令
//继续执行事务/语句…
//回滚到指定回滚点
$pdo->exec(‘rollback to sp1’);
//…….事务/语句
//终止事务:成功/失败
$pdo->commit();//成功提交事务
$pdo->rollback();//失败回滚
PDO异常
异常
代码示例:
$n1=5;
$n2=0;
//要求$n1/$n2
if($n2==0){//除数为0
throw new Exception(‘除数不能为0’);//抛出自定义异常信息
}
$res=$n1/$n2;//安全的做除法运算
异常处理机制
使用try-catch和set_error_handler
代码示例:
set_error_handler(function(){ //必须的回调函数,抛出自己设置的自定义错误
throw new Exception(‘错误’)
});
//也可以不采用匿名函数,用以下普通形式书写
function exception_handler(){
throw new Exception(‘错误’);
}
set_error_handler(‘exception_handler’);
echo ‘hello, world!’;
$n1= $_GET[‘n1’];
$n2= $_GET[‘n2’];
//可能出错的代码 使用try-catch结构
try{
$res=$n1/$n2; //可能会出错的语句
}catch(Exception $e){ //捕获错误-没有错误流程不会进入catch结构
//die(‘发生错误!’); //提示错误并退出
//更详细的错误,调用Exception的方法或属性
echo ‘代码发生错误!<b+r>’;
echo ‘错误文件为:’ . $e->getFile() . ‘<b+r>’;
echo ‘错误行号为:’ . $e->getLine() . ‘<b+r>’;
echo ‘错误描述为:’ . $e->getMessage() . ‘<b+r>’;
}
注意:没有被try结构包裹的语句发生错误将不会被我们自定义的错误捕获,此时仍由php系统处理错误.即PHP报错并提示信息.
有时候代码如果走到一个’死胡同’,即代码执行没有任何问题,但是不符合我们的逻辑,以前我们是跳转提示,现在可以抛出异常,交给异常来处理,例如
try{
if($n !=0 ){
$res=10/$n;
}else{
//业务没法发展了,直接抛出异常
throw new Exception (‘0不能做除数!’);
}
}catch(Exception $e){
//更详细的错误,调用Exception的方法或属性
echo ‘代码发生错误!<b+r>’;
echo ‘错误文件为:’ . $e->getFile() . ‘<b+r>’;
echo ‘错误行号为:’ . $e->getLine() . ‘<b+r>’;
echo ‘错误描述为:’ . $e->getMessage() . ‘<b+r>’;
die();
}
PDO错误机制
PDO中提供了三种错误机制,是通过PDO的常量PDO::ATTR_ERRMODE来选择的.
PDO::ERRMODE_SILENT:静默模式,出错了不处理(默认) PDO::ERRMODE_WARNING:警告模式,出错了立马给出错误提示 PDO::ERRMODE_EXCEPTION:异常模式,出错了把错误交给异常PDOExeption对象
PDO::ERRMODE_SILENT举例:静默模式
$pdo=new PDO(‘msyql:host=localhost;port=3306;dbname=mydb’ , ‘root’ , ‘123456’);
$pdo->exec(‘insert into students values()’);//错误sql,但系统不会报错
PDO::ERRMODE_WARNING举例:警告模式
$pdo=new PDO(‘msyql:host=localhost;port=3306;dbname=mydb’ , ‘root’ , ‘123456’);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$pdo->exec(‘insert into students values()’);//错误sql,但系统不会报错
PDO::ERRMODE_EXCEPTION举例:异常模式
$pdo=new PDO(‘msyql:host=localhost;port=3306;dbname=mydb’ , ‘root’ , ‘123456’);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try{
$pdo->exec(‘insert into students values()’);//错误sql,但系统不会报错
}catch(PDOException $e){
echo ‘sql语句发生错误!<b+r>’;
echo ‘错误文件为:’ . $e->getFile() . ‘<b+r>’;
echo ‘错误行号为:’ . $e->getLine() . ‘<b+r>’;
echo ‘错误描述为:’ . $e->getMessage() . ‘<b+r>’;
die();
}
//似乎抓取PDOException不需要设置set_error_handler
PDOException异常处理
两种方法设置PDOException
初始化PDO时,指定第四个参数,例如:
//初始化PDO时设定错误模式
$drivers = array( //可以设置多种驱动(属性设置) PDO::ATTR_ERRMODE =>PDO::ERRMODE_EXCEPTION ); $pdo = new PDO(‘mysql:host=localhost;port=3306;dbname=mydb’ , ‘ root ‘ , ‘ root ‘ , $drivers);
初始化PDO后,通过PDO的常量PDO::ATTR_ERRMODE设置为PDO::ERRMODE_EXCEPTION,例如:
$pdo=new PDO(‘msyql:host=localhost;port=3306;dbname=mydb’ , ‘root’ , ‘123456’);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
PDOException也可以捕获实例化PDO时的错误
$drivers = array( //可以设置多种驱动(属性设置) PDO::ATTR_ERRMODE =>PDO::ERRMODE_EXCEPTION );
try{
//如果PDO实例化出错,也可以捕捉到.
$pdo = new PDO(‘mysql:host=localhost;port=3306;dbname=unknowndb’ , ‘ root ‘ , ‘ root ‘ , $drivers);
}catch(PDOException $e){
echo ‘sql语句发生错误!<b+r>’;
echo ‘错误文件为:’ . $e->getFile() . ‘<b+r>’;
echo ‘错误行号为:’ . $e->getLine() . ‘<b+r>’;
echo ‘错误描述为:’ . $e->getMessage() . ‘<b+r>’;
die();
}
当语法没问题但业务逻辑有问题,可以主动抛出异常
function my_exception(PDOException $e){ //封装异常处理
echo ‘sql语句发生错误!<b+r>’;
echo ‘错误文件为:’ . $e->getFile() . ‘<b+r>’;
echo ‘错误行号为:’ . $e->getLine() . ‘<b+r>’;
echo ‘错误描述为:’ . $e->getMessage() . ‘<b+r>’;
die();
}
try{
$res= $pdo->exec(‘delete from student where s_id=100’);
if(!$res) throw new PDOException(‘删除失败!’);//如果删除结果为0(没有删除成功)则主动抛出异常
}catch(PDOException $e){
my_exception($e);//自行处理异常
}
PDO预处理
mysql预处理
发送预处理:prepare 预处理名字 from ‘重复执行的sql指令’;
执行预处理:execute 预处理名字
prepare stu_select from ‘select * from student’;//重复执行的sql
execute stu_select;//执行预处理
预处理占位
预处理占位符:sql语句使用?代替未知数据部分
预处理执行using:执行预处理时,将对应数据携带到预处理指令中
prepare stu_select_id from ‘select * from student where s_id=?’; #查询时ID不确定,使用占位符
#查询时确认id
set @id=10; #使用变量.执行预处理时不能直接使用数据,必须通过变量传入
execute stu_select_id using @id; #执行查询
预处理可使用多个占位符
多个占位符必须按顺序匹配
prepare stu_select_age from ‘select * from student where s_age between ? and ?’; #查询年龄区间学生
#执行预处理,提供2个参数
set @min=10;
set @max=20;
execute stu_selectage using @min, @max; #注意变量是按顺序传入预处理的,不可混淆顺序
删除预处理
drop prepare stu_select_age ;
PDO预处理方法机制
思考:其实所有sql指令都可以通过PDO的exec()和query()方法执行.
PDO中预处理提供了—套方法机制,主要由以下几个方法组成 PDO::prepare():发送预处理指令,只需要提供要执行的指令即可,不需要prepare 名字from。成功得到一个PDOStatement类对象,失败得到一个false (或者异常错误) PDOStatement::bindParam():绑定预处理所需要的参数,按引用传递,只能绑定变量(引用传递) PDOStatement::bindvalue():绑定预处理所需要的参数,值传递,可以绑定值(值传递) PDOStatement:.execute():执行预处理,成功返回true,失败返回false
PDO预处理代码示例
$drivers=array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
$pdo= new PDO(‘mysql:host=localhost;port=3306;dbname=mydb’, ‘root’, ‘123456’);//初始化PDO
$pre_sql=’select * from student’; //无数据预处理
$pre_sql=’select * from student where s_id= ?’; //MySQL式有参数预处理指令
$pre_sql=’select * from student where s_id=:id’; //PHP PDO特定预处理参数指令(:+字符串),更明确
$stmt=$pdo->prepare($pre_sql);//PDO发送带参数预处理指令
if(!$stmt) die(‘发送预处理指令失败!’); //判断是否错误
$stmt->bindValue(‘:id’, 1);//值传递,直接绑定数据
$id=2;
$stmt->bindValue(‘:id’, $id); //值传递,绑定变量的值
注意:如果发送预处理指令时,使用的是?占位符,那么我们在进行绑定数据的时候,是按照顺序进行绑定的,起始位置的占位符序号为1,如果有多个占位符,依此类推
//预处理指令: select * from student where s_id = ?
$stmt->bindValue(1, $id); //此处1指第一个占位符,将$id按值传递绑定
//以下是按引用传递,只能绑定变量
$stmt->bindParam((‘:id’, $id);
$res=$stmt->execute(); //执行预处理
if (!$res) die(‘预处理执行失败!’);//判断是否错误
$row=$stmt->fetch(PDO::FETCH_ASSOC);//如果是查询,还需要使用PDOStatement::fetch()方法获取查询来的数据
PDO预处理数据绑定
数据绑定,是指在进行预处理指令定义时使用了占位符,为保证后续执行预处理时能够正确执行,将实际数据替换占位符的过程
PDO中,PDOStatement::execute()本身是可以直接进行数据绑定的,即在参数中增加对应的占位符数据,以数组形式传入
//预处理使用原始占位符?
$stmt=$pdo->prepare(‘select * from student where s_id = ?’);
$stmt->execute(array(10)); //array代表数组, 10代表替换值. 如果有多个元素,顺序放入即可
//预处理使用PDO形式占位符:+名字
$stmt=$pdo->prepare(‘select * from students where s_id = :id’);
$stmt->execute(array(‘:id’=>10)); //使用占位符作为数组元素下标
bindValue()和bindParam()区别:值传递和按引用传递
$stmt = $pdo->prepare( ‘select * from student where s_id = :id ‘ ); $id = 1; $stmt->bindvalue( ‘:id’ ,$id);//值传递 for( ; $id < 10; $id++){ $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC) ; } //上述所有结果$row都是s_id为1的数据
$id = 1; $stmt->bindParam( ‘:id’, $id); //按引用传递 for( ; $id < 10; $id++){ $stmt->execute(); $row = $stmt->fetch(PDO : :FETCH_ASSOC); } //上述所有结果$row是s_id从1-9的数据
总结:如果数据本身是变化的,而且有规则的(数组遍历出来的结果),那么可以使用bindParam()按引用传递.
封装PDO
//增加命名空间
//命名空间:PDO通常属于核心类 namespace core; //有了命名空间,PDO3个类不能再直接使用,必须限定名称 use \PDO, \PDOStatement, \PDOException
//定义类
class Dao{ private $pdo; private $fetch_mode;
//增加初始化方法-构造方法
//默认采用PDO异常和获取关联数组设定 public function __construct($database_info = array( ) , $drivers = array()){ //如果要考虑细致,可以看看是否存在 $type = $database_info[ ‘type’ ] ?? ‘mysql’;//默认mysql数据库 $host = $database_info[ ‘host’ ] ?? ‘localhost’; $port = $database_info[ ‘port’ ] ?? ‘3306’; $user = $database_info[ ‘user’ ] ?? ‘root’; $pass = $database_info[ ‘pass’] ?? ‘root’ ; $dbname = $database_info[ ‘dbname’] ?? ‘test’; $charset = $database_info[ ‘charset’ ] ?? ‘utf8’ ; //fetchmode不能在初始化的时候实现,需要在得到PDOStatement类对象设置 $this->fetch_mode = $database_info[ ‘fetch_mode’ ] ?? PDO::FETCH_ASSOC;
//驱动控制:pdo异常模式处理
$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); } $this->pdo->exec(“set names {$charset}”);//设置字符集-本句也可以用try抓取错误 }
//SQL执行错误的异常处理
private function dao_exception(PDOException $e){ echo ‘SQL执行错误!<b+r/> ‘; echo‘错误文件为:’.$e->getFile(l ‘<b+r/>’; echo‘错误行号为:’. $e->getLine() . ‘<b+r/>’; echo ‘错误描述为:‘. $e->getMessage(); die(); }
//写方法-被外部调用
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->fetch_mode ) ; //数据解析 if($only) //考虑到查询结果为0(没有有效结果),我们可主动抛出异常 $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->my_exception($e); } } }
//测试
$dao=new Dao(); $res=$dao->dao_query(‘select * from student where age>18’, false); //调用读方法查询多条数据 vardump($res);