共计 8911 个字符,预计需要花费 23 分钟才能阅读完成。
PDO 曾经是 PHP 中操作数据库事实上的规范。包含当初的框架和各种类库,都是以 PDO 作为数据库的连贯形式。基本上只有咱们本人在写简略的测试代码或者小的性能时会应用 mysqli 来操作数据库。留神,一般的 mysql 扩大曾经过期了哦!
PDO 实例
首先来看看一个 PDO 实例是如何初始化的。
$dns = 'mysql:host=localhost;dbname=blog_test;port=3306;charset=utf8';
$pdo = new PDO($dns, 'root', '');
一般状况下,咱们间接实例化的时候传递结构参数就能够取得一个 PDO 对象。这样,咱们就和数据库建设了连贯。如果连贯失败,也就是参数写得有问题的时候,在实例化时间接就会报异样。
PDO 对象的参数包含 DNS 信息、用户名、明码,另外还有一个参数就是能够设置 PDO 连贯的一些属性,咱们将在前面看到它的应用。
dns 参数
PDO 结构参数的第一个参数是一个 DNS 字符串。在这个字符串中应用分号 ; 分隔不同的参数内容。它外面能够定义的内容包含:
- DSN prefix,也就是咱们要连贯的数据库类型,MySQL 数据库个别都是间接应用 mysql: 这样来定义即可。
- host,连贯的地址,在这里咱们连贯的是本地数据库 localhost
- port,端口号,MySQL 默认为 3306,能够不写
- dbname,要连贯的数据库名称
- unix_socket,能够指定 MySQL 的 Unix Socket 文件
- charset,连贯的字符集
咱们能够通过一个函数来查看以后 PHP 环境中所反对的数据库扩大都有哪些:
print_r(PDO::getAvailableDrivers());exit;
// Array
// (// [0] => dblib
// [1] => mysql
// [2] => odbc
// [3] => pgsql
// [4] => sqlite
// )
PDO 对象属性
PDO 结构参数的最初一个参数能够设置连贯的一些属性,如:
$pdo = new PDO($dns, 'root', '', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
showPdoAttribute($pdo);
// ……
// PDO::ATTR_ERRMODE: 2
// ……
showPdoAttribute() 办法是咱们本人封装的一个展现所有连贯属性的函数。
// 显示 pdo 连贯属性
function showPdoAttribute($pdo){
$attributes = array(
"DRIVER_NAME", "AUTOCOMMIT", "ERRMODE", "CASE", "CLIENT_VERSION", "CONNECTION_STATUS",
"ORACLE_NULLS", "PERSISTENT", "SERVER_INFO", "SERVER_VERSION"
);
foreach ($attributes as $val) {
echo "PDO::ATTR_$val:";
echo $pdo->getAttribute(constant("PDO::ATTR_$val")) . "\n";
}
}
在这个函数中,咱们应用 PDO 实例的 getAttribute() 办法来获取相应的属性值。在没有设置 PDO::ATTR_ERRMODE 时,它的默认值为 0,也就是 PDO::ERRMODE_SILENT 常量所对应的值。在上述代码中,咱们将它设置为了 PDO::ERRMODE_EXCEPTION,查看属性输入的后果就变成了 2。
除了在构造函数的参数中设置属性外,咱们也能够应用 PDO 实例的 setAttribute() 办法来设置 PDO 的属性值。
pdo2 = new PDO($dns, 'root', '', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
echo $pdo2->getAttribute(PDO::ATTR_DEFAULT_FETCH_MODE), PHP_EOL;
// 4
// 设置属性
$pdo2->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
echo $pdo2->getAttribute(PDO::ATTR_DEFAULT_FETCH_MODE), PHP_EOL;
// 2
在这段代码中,咱们设置 PDO::ATTR_DEFAULT_FETCH_MODE 为 PDO::FETCH_ASSOC。这样,在应用这个 $pdo2 的连贯进行查问时,输入的后果都会是以数组键值对模式返回的内容。咱们马上就进入查问方面相干函数的学习。
查问语句
大多数状况下,应用 PDO 咱们都会用它的预处理能力来编写 SQL 语句,一来是性能更好,二来是更加平安。不过咱们明天先不讲预处理方面的问题,还是以最原始的间接操作 SQL 语句的形式学习相干的一些函数。
一般查问及遍历
// 一般查问 - 遍历 1
$stmt = $pdo->query('select * from zyblog_test_user limit 5');
foreach ($stmt as $row) {var_dump($row);
}
// array(8) {// ["id"]=>
// string(3) "204"
// [0]=>
// string(3) "204"
// ["username"]=>
// string(5) "three"
// [1]=>
// string(5) "three"
// ["password"]=>
// string(6) "123123"
// [2]=>
// string(6) "123123"
// ["salt"]=>
// string(3) "ccc"
// [3]=>
// string(3) "ccc"
// }
// ……
// 一般查问 - 遍历 2
$stmt = $pdo->query('select * from zyblog_test_user limit 5');
while ($row = $stmt->fetch()) {var_dump($row);
}
// array(8) {// ["id"]=>
// string(3) "204"
// [0]=>
// string(3) "204"
// ["username"]=>
// string(5) "three"
// [1]=>
// string(5) "three"
// ["password"]=>
// string(6) "123123"
// [2]=>
// string(6) "123123"
// ["salt"]=>
// string(3) "ccc"
// [3]=>
// string(3) "ccc"
// }
// ……
PDO 实例的 query() 办法就是执行一条查问语句,并返回一个 PDOStatement 对象。通过遍历这个对象,就能够取得查问进去的数据后果集。
在代码中,咱们应用了两种形式来遍历,其实它们的成果都是一样的。在这里,咱们要关注的是返回的数据格式。能够看出,数据是以数组格局返回的,并且是以两种模式,一个是数据库定义的键名,一个是以下标模式。
查问后果集(数组、对象)
其实大部分状况下,咱们只须要数据库键名的那种键值对模式的数据就能够了。这个有两种形式,一是间接应用上文中咱们定义好默认 PDO::ATTR_DEFAULT_FETCH_MODE 属性的 $pdo2 连贯,另一个就是在查问的时候为 query() 办法指定属性。
$stmt = $pdo2->query('select * from zyblog_test_user limit 5');
foreach ($stmt as $row) {var_dump($row);
}
// array(4) {// ["id"]=>
// string(1) "5"
// ["username"]=>
// string(3) "two"
// ["password"]=>
// string(6) "123123"
// ["salt"]=>
// string(3) "bbb"
// }
// ……
$stmt = $pdo->query('select * from zyblog_test_user limit 5', PDO::FETCH_ASSOC);
foreach ($stmt as $row) {var_dump($row);
}
// array(4) {// ["id"]=>
// string(1) "5"
// ["username"]=>
// string(3) "two"
// ["password"]=>
// string(6) "123123"
// ["salt"]=>
// string(3) "bbb"
// }
// ……
当然,咱们也能够间接让数据返回成对象的格局,同样的也是应用预约义的常量来指定 query() 或者 PDO 实例连贯的属性就能够了。
$stmt = $pdo->query('select * from zyblog_test_user limit 5', PDO::FETCH_OBJ);
foreach ($stmt as $row) {var_dump($row);
}
// object(stdClass)#4 (4) {// ["id"]=>
// string(1) "5"
// ["username"]=>
// string(3) "two"
// ["password"]=>
// string(6) "123123"
// ["salt"]=>
// string(3) "bbb"
// }
// ……
查问后果集(类)
下面返回对象模式的后果集中的对象是 stdClass 类型,也就是 PHP 的默认类类型。那么咱们是否能够本人定义一个类,而后在查问实现后间接生成它的后果集呢?就是像是 ORM 框架一样,实现数据到对象的映射。既然这么说了,那当然是能够的啦,间接看代码。
class user
{
public $id;
public $username;
public $password;
public $salt;
public function __construct()
{echo 'func_num_args:' . func_num_args(), PHP_EOL;
echo 'func_get_args:';
var_dump(func_get_args());
}
}
class user2
{
}
// 返回指定对象
$u = new user;
$stmt = $pdo->query('select * from zyblog_test_user limit 5', PDO::FETCH_INTO, $u);
foreach ($stmt as $row) {var_dump($row);
}
// object(user)#3 (4) {// ["id"]=>
// string(1) "5"
// ["username"]=>
// string(3) "two"
// ["password"]=>
// string(6) "123123"
// ["salt"]=>
// string(3) "bbb"
// }
// ……
// 空类测试
$u = new user2;
$stmt = $pdo->query('select * from zyblog_test_user limit 5', PDO::FETCH_INTO, $u);
foreach ($stmt as $row) {var_dump($row);
}
// object(user2)#2 (4) {// ["id"]=>
// string(1) "5"
// ["username"]=>
// string(3) "two"
// ["password"]=>
// string(6) "123123"
// ["salt"]=>
// string(3) "bbb"
// }
// ……
在这段代码中,咱们定义了两个类,user 类有残缺的和数据库字段对应的属性,还定义了一个构造方法(前面会用到)。而 user2 类则是一个空的类。通过测试后果来看,类的属性对于 PDO 来说并不重要。它会默认创立数据库查问到的字段属性,并将它赋值给对象。那么如果咱们定义了一个 const 常量属性并给予雷同的字段名称呢?大家能够本人尝试一下。
对于 user 和 user2 来说,咱们将它实例化了并传递给了 query(),并且指定了后果集格局为 PDO::FETCH_INTO,这样就实现了获取对象后果集的能力。然而 PDO 远比你设想的弱小,咱们还能够间接用类模板来获取查问后果集。
// 依据类返回指定对象
$stmt = $pdo->query('select * from zyblog_test_user limit 5', PDO::FETCH_CLASS, 'user', ['x1', 'x2']);
foreach ($stmt as $row) {var_dump($row);
}
// func_num_args: 2
// func_get_args: array(2) {// [0]=>
// string(2) "x1"
// [1]=>
// string(2) "x2"
// }
// object(user)#4 (4) {// ["id"]=>
// string(1) "5"
// ["username"]=>
// string(3) "two"
// ["password"]=>
// string(6) "123123"
// ["salt"]=>
// string(3) "bbb"
// }
// ……
query() 办法间接应用查问后果集模式为 PDO::FETCH_CLASS,并传递一个类模板的名称,PDO 就会在以后代码中查找有没有对应的类模板,取得的每个后果都会实例化一次。在这里,咱们又多了一个参数,最初一个参数是一个数组,并且给了两个元素。预计有不少小伙伴曾经看进去了,这个参数是传递给类的构造方法的。记住,应用这个模式,每个元素都会实例化一次,后果集中的每个元素都是新创建的类(object(user2)#3,# 号前面的数字是不同的对象句柄 id),而 PDO::FETCH_INTO 则是以援用的模式为每个元素赋值(object(user2)#3,# 号前面的数字是雷同的对象句柄 id)。也就是说,咱们应用 PDO::FETCH_INTO 模式的时候,批改一个元素的值,其它的元素也会跟着扭转,如果应用一个数组去记录遍历的元素值,最初数组的后果也会是雷同的最初一个元素的内容。
$stmt = $pdo->query('select * from zyblog_test_user limit 5', PDO::FETCH_INTO, $u);
$resArr = [];
foreach ($stmt as $row) {var_dump($row);
$resArr[] = $row;}
$resArr[0]->id = 55555;
print_r($resArr);
// Array
// (// [0] => user2 Object
// (// [id] => 55555
// [username] => two
// 此处含有隐藏内容,需要正确输入密码后可见!
=> 123123
// [salt] => bbb
// )
// [1] => user2 Object
// (// [id] => 55555
// [username] => two
// 此处含有隐藏内容,需要正确输入密码后可见!
=> 123123
// [salt] => bbb
// )
// [2] => user2 Object
// (// [id] => 55555
// [username] => two
// 此处含有隐藏内容,需要正确输入密码后可见!
=> 123123
// [salt] => bbb
// )
// [3] => user2 Object
// (// [id] => 55555
// [username] => two
// 此处含有隐藏内容,需要正确输入密码后可见!
=> 123123
// [salt] => bbb
// )
// [4] => user2 Object
// (// [id] => 55555
// [username] => two
// 此处含有隐藏内容,需要正确输入密码后可见!
=> 123123
// [salt] => bbb
// )
// )
如何解决这个问题呢?最简略的形式就是在数组赋值的时候加个 clone 关键字呗!
查问后果集(指定字段)
最初轻松一点,咱们看下 query() 办法还能够指定查问的某一个字段。
// 只返回第几个字段
$stmt = $pdo->query('select * from zyblog_test_user limit 5', PDO::FETCH_COLUMN, 2);
foreach ($stmt as $row) {var_dump($row);
}
// string(32) "bbff8283d0f90625015256b742b0e694"
// string(6) "123123"
// string(6) "123123"
// string(6) "123123"
// string(6) "123123"
增、删、改操作
除了查问之外的操作,咱们也能够应用 exec() 办法来执行其余一些相应的 SQL 语句。
减少操作
$count = $pdo->exec("insert into zyblog_test_user(`username`, `password`, `salt`) value('akk','bkk','ckk')");
$id = $pdo->lastInsertId();
var_dump($count); // int(1)
var_dump($id); // string(3) "205"
exec() 返回的是影响的行数,如果咱们执行这一条 SQL,返回的就是胜利增加了一行数据。如果要取得新减少数据的 id,就要应用 lastInserId() 办法来获取。
$count = $pdo->exec("insert into zyblog_test_user(`username`, `password`, `salt`) value('akk','bkk','ckk','dkk')");
// Fatal error: Uncaught PDOException: SQLSTATE[21S01]: Insert value list does not match column list: 1136 Column count doesn't match value count at row 1
执行谬误的 SQL 语句,就像依据 PDO::ATTR_ERRMODE 属性的设置来返回错误信息。咱们在最下面的实例化 PDO 代码中指定了谬误模式是异样解决模式,所以这里间接就会报 PDOException 异样。
批改操作
// 失常更新
$count = $pdo->exec("update zyblog_test_user set `username`='aakk'where id='{$id}'");
var_dump($count); // int(1)
// 数据不变更新
$count = $pdo->exec("update zyblog_test_user set `username`='aakk'where id='{$id}'");
var_dump($count); // int(0)
// 条件谬误更新
$count = $pdo->exec("update zyblog_test_user set `username`='aakk'where id='123123123123'");
var_dump($count); // int(0)
echo '===============', PHP_EOL;
同样的,在执行更新操作的时候,exec() 返回的也是受影响的行数。很多小伙伴会以这个进行判断是否更新胜利,但如果数据没有批改,那么它返回的将是 0,SQL 语句的执行是没有问题的,逻辑上其实也没有问题。比方咱们在后盾关上了某条数据查看,而后并不想更新任何内容就间接点了提交,这时候不应该呈现更新失败的提醒。也就是说,在前端判断更新操作的时候,须要判断字段是否都有扭转,如果没有扭转的话那么不应该提醒更新失败。这一点是业务逻辑上的思考问题,如果你认为这样也是更新失败的话,那么这么报错也没有问题,所有以业务模式为主。
删除操作
$count = $pdo->exec("delete from zyblog_test_user where id ='{$id}'");
var_dump($count); // int(1)
// 条件谬误删除
$count = $pdo->exec("delete from zyblog_test_user where id ='5555555555'");
var_dump($count); // int(0)
删除操作须要留神的问题和更新操作是一样的,那就是同样的 exec() 只是返回影响行数的问题,不过绝对于更新操作来说,没有受影响的行数那必定是删除失败的,没有数据被删除。同样的,这个失败的提醒也请依据业务状况来具体分析。
总结
不学不晓得,一学吓一跳吧,简简略的一个 PDO 的创立和语句执行居然有这么多的内容。对于咱们的日常开发来说,把握这些原理可能防止很多莫名其妙的问题,比方下面 exec() 只是返回影响行数在业务开发中如何判断操作是否胜利的问题就很典型。好了,这只是第一篇,前面的学习不要落下了哦!
测试代码:
https://github.com/zhangyue0503/dev-blog/blob/master/php/202008/source/PHP%E4%B8%AD%E7%9A%84PDO%E6%93%8D%E4%BD%9C%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%B8%80%EF%BC%89%E5%88%9D%E5%A7%8B%E5%8C%96PDO%E5%8F%8A%E5%8E%9F%E5%A7%8BSQL%E8%AF%AD%E5%8F%A5%E6%93%8D%E4%BD%9C.php
参考文档:
https://www.php.net/manual/zh/pdo.construct.php
https://www.php.net/manual/zh/pdo.query.php
https://www.php.net/manual/zh/pdo.exec.php
https://www.php.net/manual/zh/pdo.lastinsertid.php
https://www.php.net/manual/zh/ref.pdo-mysql.connection.php
===========
各自媒体平台均可搜寻【硬核项目经理】