PHP8.1.21版本已发布
vue8.1.21版本已发布
jquery8.1.21版本已发布

博客列表 > 【PHP 面向对象】面向对象(OOP)编程之PDO对象操作数据库知识点归纳总结(五)

【PHP 面向对象】面向对象(OOP)编程之PDO对象操作数据库知识点归纳总结(五)

 一纸荒凉* Armani
 一纸荒凉* Armani 原创
2021年05月13日 17:18:21 1137浏览

PDO 是什么

PDO 是 PHP Date Object(PHP 数据对象)的简称,它是 PHP 为访问数据库定义的一个轻量级的、一致性的接口,它提供了一个数据访问抽象层,这样无论你使用什么数据库,都可以通过同一函数执行查询和获取数据,大大简化了数据库的操作,并能够屏蔽不同数据库之间的差异。

PDO 是与 PHP5.1 版本一起发行的,使用 PDO 可以很方便地进行跨数据库程序的开发,以及不同数据库间的移植,目前 PDO 支持的数据库包括 Firebird、FreeTDS、Interbase、MySQL、SQL Server、ODBC、Oracle、Postgre SQL、SQLite 和 Sybase 等。

PDO 的特点

我们可以将 PDO 看作是一个“数据库访问抽象层”,作用是统一各种数据库的访问接口。与 MySQL 和 MSSQL 函数库相比,PDO 让跨数据库的使用更具有亲和力,与 ADODB 和 MDB2 相比,PDO 更加高效。

PDO 将通过一种轻型、清晰、方便的函数,统一各种不同的数据库的共有特性,实现 PHP 脚本在最大程度上的抽象性和兼容性。

PDO 吸取了现有数据库扩展成功和失败的经验教训,利用 PHP5 的最新特性,可以轻松地与各种数据库进行交互。

PDO 扩展是模块化的,能够在运行时为用户数据库后端加载驱动程序,而不必重新编译或重新安装整个 PHP 程序。例如,PDO_MySQL 扩展会替代 PDO 扩展实现 MySQL 数据库 API,它还有一些用于 Oracle、Postgre SQL、ODBC 和 Firebird 的驱动程序。

开启 PDO扩展

默认情况下,PDO 在 PHP 中为开启状态,但是要启用对某个数据库驱动程序的支持,仍需要进行相应的配置操作。

以 Windows 系统下为例,在配置文件 php.ini 中有关 PDO 相关的配置信息如下所示:

;extension=pdo_firebird;extension=php_pdo_mysql.dll;extension=pdo_oci;extension=pdo_odbc;extension=pdo_pgsql;extension=pdo_sqlite

提示:开启相应的配置只需要去除配置项前面的分号;,然后重启 Apache 服务器即可。

验证相关的配置是否开启成功,只需要执行 phpinfo() 函数就行,在输出的页面中搜索配置的名称,如果存在则说明开启成功

使用PDO连接数据库

在使用 PDO 与不同数据库之间交互时,PDO 对象中的成员方法是统一各种数据库的访问接口,所以在使用 PDO 与数据库交互之前,首先要创建一个 PDO 对象,然后再通过对象的构造函数来连接数据库。

new PDO(string $dsn[, string $username [, string $password [, array $driver_options]]]);

参数说明如下:

  • $dsn:数据源名称或叫做 DSN(Data Source Name 的缩写),包含了请求连接到数据库的信息。通常一个 DSN 是由 PDO 驱动程序的名称,紧随其后是一个冒号,再后面是可选的驱动程序的数据库连接信息,比如主机名、端口和数据库名。以 MySQL 数据库为例 $dsn 可以定义为:mysql:host=localhost;port=3306;dbname=dbname;charset=utf8,分别定义了数据库类型、端口号、数据库名和字符集;

  • $username:可选参数,用来表示 DSN 字符串中的用户名;

  • $password:可选参数,用来表示 DSN 字符串中的密码;

  • $driver_options:可选参数,一个具体驱动的连接选项的键/值数组。

PHP 数据对象:https://www.php.net/manual/zh/book.pdo.php

预定义常量:https://www.php.net/manual/zh/pdo.constants.php

创建 PDO 对象

$pdo = new PDO($dsn, $user, $pwd); 参数:dsn,数据库用户名,数据库密码

<?php    $type = 'mysql';     //数据库类型    $host = 'localhost'; //数据库主机名    $dbname = 'test';    //使用的数据库名称    $username = 'root';  //数据库连接用户名    $username  = 'root'; //数据库连接密码    $dsn="$type:host=$host;dbname=$dbname";     try{        // 可选,设置错误提示级别为WARNING        $params = array(PDO::ATTR_ERRMODE=>PDO::ERRMODE_WARNING);         //初始化一个PDO对象        $pdo= new PDO($dsn,$user,$pwd,$params);         # 设置结果集的默认获取的方式 (默认索引数组和关联数组)           $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC);    }catch(Exception $e){        die('数据库连接失败:'.$e -> getMessage());    }?>

使用PDO执行SQL语句

在 PDO 中,我们可以使用三种方式来执行 SQL 语句,分别是 exec() 方法,query() 方法,以及预处理语句 prepare() 和 execute() 方法。

执行sql语句: exec() 、query()、 perpare();

1、query用来处理有结果集的,如select, 返回 PDOStatement 对象,失败返回false(当为 PDO::ERRMODE_SILENT,这也是默认的值)

2、exec用来处理有返回影响行数的(int),如  insert(插入的行数)、 delete(删除的行数) 、update(和原数值不等才算), 失败返回false (当为 PDO::ERRMODE_SILENT,这也是默认的值)

3、prepare 执行所有sql,可以完全替代 query,exec的功能,并且可以防止SQL注入

错误报告是针对执行的sql出错时

PDO::ERRMODE_SILENT(0) :默认 不提示任何错误 ,连接时无论如何都会提示,只有在执行后面的方法时才会起作用
PDO::ERRMODE_WARNING(1) : 警告
PDO::ERRMODE_EXCEPTION(2):异常(推荐使用) 用try catch捕获,也可以手动抛出异常 new PDOException($message, $code, $previous)

1) exec() 方法

当执行 INSERT、UPDATE 和 DELETE 等不需要返回结果集的 SQL 语句时,可以使用 PDO 对象中的 exec() 方法。该方法成功执行后,将返回受影响的行数

主要思路:
             (1)连接数据库、数据库的用户名、数据库的密码
               (2)生成PDO对象
             (3)执行查询

<?php    # $stmt = $pdo->exec($sql); 执行SQL增删改语句,返回值是false或 受影响的整型数量    $dsn  = 'mysql:host=localhost;dbname=users;';    $username = 'root';    $password  = 'root';    // 生成PDO对象      $pdo = new PDO($dsn,$user,$pwd);    $sql = "insert into user(name,age,sex) values('zhang','18','男')";    $res = $pdo -> exec($sql);    if($res) echo '成功添加 '.$res.' 条数据!';?>
# exec用法try {    $sql = "insert into users (`user_name`, `user_pwd`) values('zhang', '123'),('admin', 'admin')";    $rows = $pdo->exec($sql);  // 影响的条数 2    $id = $pdo->lastInsertId(); //最后插入的id,有多条时返回的是第一条的id} catch (Exception $e) {   echo $pdo->errorInfo();}

注释:exec主要用于执行没有返回结果集的操作,比如insert、delete、update,返回的是影响的记录条数

2) query() 方法

当执行需要返回结果集的 SELECT 查询语句时,可以使用 PDO 对象中的 query() 方法。如果该方法执行成功,则会返回一个 PDOStatement 对象。如果使用了query() 方法,并想了解获取的数据行总数,可以使用 PDOStatement 对象中的 rowCount() 方法获取。

PDO::query(string $sql)# 执行SQL查询语句,返回值是false或 PDOStatement 类型的对象$stmt = $pdo->query($sql); # 读取结果中的数量echo $stmt->rowCount();# 返回结果集第一条数据$stmt->fetch(PDO::FETCH_ASSOC);# 返回结果集全部数据$stmt->fetchAll(PDO::FETCH_ASSOC);# 直给pdo对象全局设置返回类型$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC);

通过设置第二个参数可以调整返回值的样式:

  • PDO::FETCH_BOTH 关联+索引数组(默认值)

  • PDO::FETCH_ASSOC 关联数组

  • PDO::FETCH_NUM  索引数组

  • PDO::FETCH_OBJ 对象类型

<?php      $dsn  = 'mysql:host=127.0.0.1;dbname=mydb';    $user = 'root';    $pwd  = 'root';    $pdo = new PDO($dsn,$user,$pwd);    $sql = "SELECT * FROM users ";    // 获取PDOStatement对象    $stmt = $pdo -> query($sql,PDO::FETCH_ASSOC);    echo "<pre>";    print_r($stmt);    // 获取结果集全部数据    print_r($stmt->fetchAll());?>

设置结果集参数也可以通过fetch和fetchAll()传入

<?php      $sql = "SELECT * FROM users ";    // 获取PDOStatement对象    $stmt = $pdo -> query($sql);    // 获取结果集中第一条数据 返回关联数组    $res = $stmt->fetchAll(PDO::FETCH_ASSOC);    echo "<pre>";    print_r($res);?>

为了避免每次获取结果集都反复的设置参数, setAttribute()方法是设置部分属性我们可以直接在连接数据库时给pdo对象统一的来设置

<?php      $dsn  = 'mysql:host=127.0.0.1;dbname=mydb';    $user = 'root';    $pwd  = 'root';    $pdo = new PDO($dsn,$user,$pwd);    # 设置结果集的默认获取的方式 (默认索引数组和关联数组)       $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC);    $sql = "SELECT * FROM users ";    // 获取PDOStatement对象    $stmt = $pdo -> query($sql);    // 获取结果集中第一条数据 返回关联数组    $res = $stmt->fetchAll();    echo "<pre>";    print_r($res);?>

使用 query() 和 exec() 方法有以下几点需要注意:

  • query() 和 exec() 都可以执行所有的 SQL 语句,只是返回值不同而已;

  • query() 可以实现所有 exec() 的功能;

  • 当把 select 语句应用到 exec() 时,总是返回 0;

  • 如果要看查询的具体结果,可以通过 foreach 语句完成循环输出。

3) 预处理语句方式

当同一个查询需要多次执行时(有时需要迭代传入不同的条件参数),使用预处理语句的方式来实现效率会更高。使用预处理语句就需要使用 PDO 对象中的 prepare() 方法去准备一个将要执行的查询,再使用 PDOStatement 对象中的 execute() 方法来执行。

$stmt = $pdo -> prepare($sql);$stmt -> execute([参数1,参数2]);$args = $stmt->fetchAll(参数):读取查询结果数组$arg = $stmt->fetch(参数):读取一条查询结果数组,参数同上$obj = $stmt->fetchObject():直接读取一条结果为对象类型$pdo->errorInfo():读取错误信息,返回值是数组类型

SQL 语句模板中可以包含零个或多个参数占位标记,格式可以是命名(:name)或问号(?)的形式,当它执行时将用真实数据取代。在同一个 SQL 语句里,命名和问号形式不能同时使用,只能选择其中一种参数形式。如果使用命名形式的占位标记,那么标记的命名必须是唯一的。使用预处理语句可以有效的避免传统的方式中SQL注入问题

使用命名形式的参数占位符,查询指定的 SQL 语句

<?php      #无序方式 命名占位符    $dsn  = 'mysql:host=127.0.0.1;dbname=mydb';    $user = 'root';    $pwd  = 'root';    $pdo = new PDO($dsn,$user,$pwd);    $sql = "SELECT user_name,user_email FROM users WHERE user_sex = :sex";    $sth = $pdo -> prepare($sql);    $sth -> execute([':sex'=>0]);    $res = $sth -> fetchAll();    echo '<pre>';    print_r($res);?>

使用问号形式的参数占位符,查询指定的 SQL 语句

<?php     #有序方式 问号占位符    $dsn  = 'mysql:host=127.0.0.1;dbname=mydb';    $user = 'root';    $pwd  = 'root';    $pdo = new PDO($dsn,$user,$pwd);    $sql = "SELECT user_name,user_email FROM users WHERE user_sex= ?";    $sth = $pdo -> prepare($sql);    $sth -> execute([0]);    $res = $sth -> fetchAll(PDO::FETCH_ASSOC);    echo '<pre>';    print_r($res);?>

使用PDOStatement::bindParam() 提前引用绑定参数,而不是执行的时候绑定参数。

// 连接数据库$dsn = "mysql:host=127.0.0.1;port=3306;dbname=mydb";$opts = array(PDO::ATTR_AUTOCOMMIT=>0, PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION, PDO::ATTR_AUTOCOMMIT=>0);try {    $pdo = new PDO($dsn, 'root', 'root', $opts);}catch(PDOException $e){    echo $e->getMessage();}/* pdo中有两种占位符号 **  ? 参数占位符            --- 索引数组, 按索引顺序使用*  命名参数占位符           ----关联数组, 按名称使用,和顺序无关*///准备好了一条语句,并入到服务器端,也已经编译过来了,就差为它分配数据过来//同样适用于更新操作// 命名参数占位符$stmt=$pdo->prepare("insert into users (`user_name`, `user_pwd`,'user_email','create_time') values(:name,:pwd,:email,:time)");//绑定参数,引用方式传递$stmt->bindParam(":name", $name);$stmt->bindParam(":pwd", $pwd);$stmt->bindParam(":email", $email);$stmt->bindParam(":time", $time);#变量放到 bindParam 前后都可$name="zhang";$pwd = '1234';$email = 'zhang@qq.com';$time = time();// 问号参数占位符$stmt=$pdo->prepare("insert into users (`user_name`, `user_pwd`,'user_email','create_time') values(?,?,?,?)");//绑定参数,引用方式传递$stmt->bindParam(1, $name, PDO::PARAM_STR); #起始值为 1 $stmt->bindParam(2, $pwd, PDO::PARAM_STR);$stmt->bindParam(3, $email, PDO::PARAM_INT);$stmt->bindParam(4, $time, PDO::PARAM_INT);// 执行预处理语句if($stmt->execute()){    echo "执行成功";    echo "最后插入的ID:".$pdo->lastInsertId();}else{    echo "执行失败!";}
  • PDO类  数据库连接有关(连接、执行sql)

    • PDO::prepare — 准备要执行的语句,并返回语句对象

    • PDO::query — 执行 SQL 语句,以 PDOStatement 对象形式返回结果集

  • PDOStatement 处理结果集 ($pdo->query()和$pdo->prepare()可以返回PDOStatement)

  • PDOException 异常处理类

https://www.php.net/manual/zh/class.pdostatement.php

总结:

  1、query和exec都可以执行所有的sql语句,只是返回值不同而已。

  2、query可以实现所有exec的功能。

  3、当把select语句应用到 exec 时,总是返回 0

预处理语句(prepare)示例,sql只编译一次,执行相同的sql效率会高。单个相比exec,query效率也高。

注意:批量插入时,依次插入当遇到错误时后面的插入失败,但是前面的会插入成功。

pdo 预处理中 bindParam() 和 bindValue() 的不同之处:

  • PDOStatement::bindParam — 绑定一个参数到指定的变量名

  • PDOStatement::bindValue — 把一个值绑定到一个参数

  • 区别就是前者使用一个php变量绑定参数,而后者使用一个值。说白了一个是变量,一个是固定值

所以使用bindParam是第二个参数只能用变量名,而不能用变量值,而bindValue至可以使用具体值。

$stm = $pdo->prepare("select * from users where user = :user");  $user = "jack";  //正确  $stm->bindParam(":user",$user);  //错误  //$stm->bindParam(":user","jack");  //正确  $stm->bindValue(":user",$user);  //正确  $stm->bindValue(":user","jack");  // 另外在存储过程中,bindParam可以绑定为input/output变量,如下面$stm = $pdo->prepare("call func(:param1)");  $param1 = "abcd";  $stm->bindParam(":param1",$param1); //正确  $stm->execute();

存储过程执行过后的结果可以直接反应到变量上。

对于那些内存中的大数据块参数,处于性能的考虑,应优先使用前者


下面来演示一下SQL注入:

<?php      $dsn  = 'mysql:host=127.0.0.1;dbname=mydb';    $user = 'root';    $pwd  = 'root';    $pdo = new PDO($dsn,$user,$pwd);    # 设置结果集的默认获取的方式 (默认索引数组和关联数组)       $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC);       // 模拟用户输入的用户名和密码       $name = "' or 1=1 # ";       $pwd = '1234';    $sql = "select user_name,user_pwd from users where user_name = '{$name}' and user_pwd = '{$pwd}'";    // 获取PDOStatement对象    $stmt = $pdo -> query($sql);    echo "<pre>";    print_r($stmt);    // 获取结果集中第一条数据 返回关联数组    $res = $stmt->fetchAll();    print_r($res);?>

生成的sql语句是这样的,存在严重问题

select user_name,user_pwd from users where user_name='zhang' and user_pwd='1234';# 但用户如果输入的用户名是 ' or 1=1 # select user_name,user_pwd from users where user_name='' or 1=1 #' and user_pwd='1234';

下面我们用预处理语句方式来查询看一下生成的SQL语句和执行结果

<?php      $dsn  = 'mysql:host=127.0.0.1;dbname=mydb';    $user = 'root';    $pwd  = 'root';    $pdo = new PDO($dsn,$user,$pwd);    # 设置结果集的默认获取的方式 (默认索引数组和关联数组)       $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC);       // 模拟用户输入的用户名和密码       $name = "' or 1=1 # ";       $pwd = '1234';    $sql = "select user_name,user_pwd from users where user_name=? and user_pwd=?";    $stmt = $pdo -> prepare($sql);    $stmt->execute([$name,$pwd]);    echo "<pre>";    print_r($stmt);    $res = $stmt->fetchAll();    print_r($res);?>

PDO预处理能防止sql注入的原因:

1、先看预处理的语法

#无序方式 命名占位符预处理$pdo->prepare('select user_name from users where user_name=:user_name');$pdo->execute([':user_name'=>'admin']); #有序方式 问号占位符预处理$pdo->prepare('select user_name from users where user_name=?');$pdo->execute(['admin']);
  • prepare 服务器发送一条sql给mysql服务器,mysql服务器会解析这条sql。

  • execute 服务器发送一条sql给mysql服务器,mysql服务器不会解析这条sql,只会把execute的参数当做纯参数赋值给语句一。哪怕参数中有sql命令也不会被执行,从而实现防治sql注入。

实战:完善登录注册功能

文件夹基本结构如下:

common.php

<?php  /** * 公共模型文件 */// 连接数据库服务require "../../config/connect.php";function checkName($uname){    global $pdo;    // 默认数据库中用户名不存在同名    $isOccupied = false;    // 编写查询用户名SQL语句    $sql = "select user_name from users where user_name='{$uname}'";    // 返回 pdo Statement对象    $stmt =  $pdo->query($sql);    // 以关联数组方式返回结果集    $res = $stmt->fetchAll(PDO::FETCH_ASSOC);    // 如果不想每次获取时都设置返回方式,也可以直接给pdo对象设置返回类型    // $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC);    if($res){        $isOccupied = true;    }    return $isOccupied;}function insertData(Array $user){    global $pdo;    // 获取当前时间戳    $create_time = time();    // 将密码进行加密处理    $password = password_hash($user['pwd'],PASSWORD_BCRYPT);        // 准备插入的sql语句    $sql  = "insert into users (user_name,user_pwd,create_time,user_email) values ('{$user['userName']}','{$password}',{$create_time},'{$user['email']}')";    // 执行插入SQL语句    $count = $pdo->exec($sql);    return $count;}?>

regist_check.php

<?php  // 导入公共函数库require "./common.php";// 接受用户登陆时提交的验证码session_start();// 接收PSOT传递过来的数据$user = $_POST;// 非表单提交访问页面给予提醒if(empty($user)) die('请勿非法访问!!!');// 判断请求的类型if(isset($_POST['type']) && $_POST['type']==1 ){    if(!empty($_POST["uname"])){        // 调用函数判断用户名是否被占用        $res = checkName($_POST["uname"]);        if($res){            echo json_encode(['status'=>0,'msg'=>'该用户名已被占用']);        }else{            echo json_encode(['status'=>1,'msg'=>'该用户名合法']);        }    }}else if(isset($_POST['type']) && $_POST['type']==2 ){    if(!empty($_POST["captcha"])){        if(strtolower($_SESSION["captcha"]) === strtolower($_POST["captcha"])){            echo json_encode(['status'=>1,'msg'=>'验证码正确']);        }else{            echo json_encode(['status'=>0,'msg'=>'验证码不正确']);        }    }}else{    if(strlen($user['userName'])<4 || !preg_match("/^[A-Za-z]/i",$user['userName']) ){        echo json_encode(['status'=>0,'msg'=>'用户名长度需不小于四位且以字母开头']);    }else if(strcmp($user['pwd'],$user['cpwd'])!== 0){        echo json_encode(['status'=>0,'msg'=>'两次密码输入不一致']);    }else if(strcasecmp($_SESSION["captcha"],$user["captcha"])!== 0){        echo json_encode(['status'=>0,'msg'=>'验证码不正确']);    }else if(checkName($user['userName'])){        echo json_encode(['status'=>0,'msg'=>'请勿重复多次提交']);    }else{        // 调用添加数据函数 返回受影响行数        $count= insertData($user);        if($count):            echo json_encode(['status'=>1,'msg'=>'注册成功,请稍后……']);        else:            echo json_encode(['status'=>0,'msg'=>'注册失败~~~']);        endif;    }}?>

login_check.php

<?php  // 连接数据库服务require "./../config/connect.php";// 接受用户登陆时提交的验证码session_start();if(empty($_POST)) die("请勿非法访问");$username = $_POST['userName'];$password = $_POST['pwd'];$captcha = $_POST['captcha'];// 预处理SQL模板$sql = "select user_name,user_pwd from users where user_name = ?";// prepare()方法-准备一条预处理语句 返回 pdo statement对象$stmt = $pdo->prepare($sql);// 绑定参数到指定的变量名$stmt->bindParam(1,$username,PDO::PARAM_STR);// 执行预处理SQL$stmt->execute();// 获取结果集$res = $stmt->fetch();// 判断查询出的数据条数if($stmt->rowCount()==0){    echo json_encode(['status'=>0,'msg'=>'该用户未注册……']); }else if(!password_verify($password,$res['user_pwd'])){    echo json_encode(['status'=>0,'msg'=>'用户名或密码不正确']); }else if(strcasecmp($_SESSION["captcha"],$captcha)!== 0){    echo json_encode(['status'=>0,'msg'=>'验证码错误……']); }else{    echo json_encode(['status'=>1,'msg'=>'登录成功……']); }?>

database.php

<?phpnamespace pdo_edu;return [    'type'=> $type ?? 'mysql',    'host'=> $host ?? 'localhost',    'dbname'=> $dbname ?? 'mydb',    'username'=> $username ?? 'root',    'password'=> $password ?? 'root',    'charset'=> $charset ?? 'utf8mb4',    'port'=> $port ?? '3306'];

connect.php

<?php  namespace pdo_connect;// 数据库配置信息$config = require __DIR__."./database.php";extract($config);$dsn = sprintf("%s:host=%s;dbname=%s",$type,$host,$dbname);try{    // 可选,设置错误提示级别为WARNING    $params = [\PDO::ATTR_ERRMODE=>\PDO::ERRMODE_WARNING];    $pdo = new \PDO($dsn,$username,$password,$params);}catch(Exception $e){    die('数据库连接失败:'.$e -> getMessage());}?>

浏览效果:



浏览链接:

【PHP 面向对象】面向对象(OOP)编程之解读命名空间使用知识点归纳总结(四)
【PHP 面向对象】面向对象(OOP)编程之魔术方法实现重载知识点归纳总结(三)
【PHP 面向对象】面向对象(OOP)编程知识点归纳总结(二)
【PHP 面向对象】面向对象(OOP)编程知识点归纳总结(一)

声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议