如何在 Linux 上配置和使用 PDO 进行数据库访问
客观的
了解如何配置和使用 PDO 进行数据库访问:从错误模式到获取方法。
要求
MySQL 和 mysql 命令行客户端的标准知识;
熟悉面向对象编程的基本概念
PHP >= 5.1
拥有可用的 MySQL/MariaDB 数据库
困难
中等的
惯例
# – 需要以root权限执行给定的linux命令
直接以 root 用户身份或使用 sudo 命令$ – 要求以常规非特权用户身份执行给定的 Linux 命令
介绍
PDO 是 PHP Data Objects 的缩写:它是通过使用对象与数据库进行交互的 PHP 扩展。它的优势之一在于它并不严格依赖于某些特定的数据库:它的接口提供了一种访问多种不同环境的通用方法,其中包括:
MySQL
SQLite
PostgreSQL
微软SQL服务器
本指南旨在提供 PDO 的相当完整的概述,逐步指导读者从建立数据库连接到选择最合适的获取模式,展示如何创建准备好的语句并描述可能的错误模式。
创建测试数据库和表
我们要做的第一件事是为本教程创建一个数据库:
CREATE DATABASE solar_system;
GRANT ALL PRIVILEGES ON solar_system.* TO 'testuser'@'localhost'
IDENTIFIED BY 'testpassword';
我们使用 testpassword
作为密码,授予用户 testuser
对 solar_system
数据库的所有权限。现在让我们创建一个表并用一些数据填充它(没有天文精度的意图):
USE solar_system;
CREATE TABLE planets (
id TINYINT(1) UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY(id),
name VARCHAR(10) NOT NULL,
color VARCHAR(10) NOT NULL
);
INSERT INTO planets(name, color) VALUES('earth', 'blue'), ('mars', 'red'), ('jupiter', 'strange');
DSN:数据源名称
现在我们有了一个数据库,我们必须定义一个DSN
。 DSN 代表数据源名称
,它基本上是连接到数据库所需的一组信息,以字符串的形式表示。根据您要连接的数据库,语法可能会有所不同,但由于我们正在与 MySQL/MariaDB 交互,因此我们将提供:
用于连接的驱动程序类型
托管数据库的计算机的主机名
用于连接的端口(可选)
数据库名称
字符集(可选)
在我们的例子中,字符串的格式如下(我们将其存储在 $dsn
变量中):
$dsn = "mysql:host=localhost;port=3306;dbname=solar_system;charset=utf8";
首先,我们提供了数据库前缀
。在本例中,由于我们要连接到 MySQL/MariaDB 数据库,因此我们使用 mysql
。然后,我们用冒号将前缀与字符串的其余部分分隔开,并用分号将其他每个部分分隔开。
在接下来的两节中,我们指定了托管数据库的计算机的主机名
以及用于连接的端口
。如果未提供后者,则将使用默认值,在本例中为 3306
。在我们提供数据库名称
之后,紧接着提供要使用的字符集
。
创建 PDO 对象
现在我们的 DSN 已准备就绪,我们将构建 PDO 对象
。 PDO 构造函数将 dsn 字符串作为第一个参数,将数据库中的用户名作为第二个参数,将其密码作为第三个参数,并可选择将选项数组作为第四个参数:
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
];
$pdo = new PDO($dsn, 'testuser', 'testpassword', $options);
但是,也可以在构造对象后通过 SetAttribute()
方法指定选项:
$pdo->SetAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
设置错误时的 PDO 行为
让我们看一下 PDO::ATTR_ERRMODE 的一些可用选项。该选项非常重要,因为定义了发生错误时的 PDO 行为。可能的选项有:
PDO::ERRMODE_SILENT
这是默认设置。 PDO只会设置错误代码和错误消息。可以使用 errorCode()
和 errorInfo()
方法检索它们。
PDO::ERRMODE_EXCEPTION
在我看来,这是推荐的一种。使用此选项,除了设置错误代码和信息之外,PDO 还会抛出 PDOException
,这将中断脚本流程,并且在 PDO 事务
的情况下特别有用(我们将在本教程后面看到什么是交易)。
PDO::ERRMODE_WARNING
使用此选项,PDO 会将错误代码和信息设置为索引 PDO::ERRMODE_SILENT
,但也会输出 WARNING
,这不会中断脚本的流程。
设置默认获取模式
另一个重要的设置可以通过 PDO::DEFAULT_FETCH_MODE 指定。持续的。它允许您指定从查询检索结果时要使用的默认获取方法。这些是最常用的选项:
PDO::FETCH_BOTH:
这是默认设置。有了它,提取查询检索的结果将按整数和列名进行索引。从 Planets 表中检索行时应用此获取模式将给出以下结果:
$stmt = $pdo->query("SELECT * FROM planets");
$results = $stmt->fetch(PDO::FETCH_BOTH);
Array
(
[id] => 1
[0] => 1
[name] => earth
[1] => earth
[color] => blue
[2] => blue
)
PDO::FETCH_ASSOC:
使用此选项,结果将存储在关联数组
中,其中每个键将是列的名称,每个值将是一行中的对应值:
$stmt = $pdo->query("SELECT * FROM planets");
$results = $stmt->fetch(PDO::FETCH_ASSOC);
Array
(
[id] => 1
[name] => earth
[color] => blue
)
PDO::FETCH_NUM
此获取模式将获取的行返回到 0 索引数组:
Array
(
[0] => 1
[1] => earth
[2] => blue
)
PDO::FETCH_COLUMN
当仅检索列的值时,此 fetch 方法非常有用,并将返回普通的一维数组内的所有结果。例如这个查询:
$stmt = $pdo->query("SELECT name FROM planets");
将返回这个结果:
Array
(
[0] => earth
[1] => mars
[2] => jupiter
)
PDO::FETCH_KEY_PAIR
当仅检索 2 列的值时,此获取方法非常有用。它将以关联数组的形式返回结果,其中从数据库检索到的查询中第一个指定列的值将用作数组键,而为第二列检索的值将表示关联数组数组值:
$stmt = $pdo->query("SELECT name, color FROM planets");
$result = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
将返回:
Array
(
[earth] => blue
[mars] => red
[jupiter] => strange
)
PDO::FETCH_OBJECT:
使用 PDO::FETCH_OBJECT
常量时,将为检索的每一行创建一个匿名对象
。其(公共)属性将以列命名,查询结果将用作它们的值。将此获取模式应用于上面的同一查询将返回以下形式的结果:
$results = $stmt->fetch(PDO::FETCH_OBJ);
stdClass Object
(
[name] => earth
[color] => blue
)
PDO::FETCH_CLASS:
这种获取模式与上面一样,会将列的值分配给对象的属性,但在这种情况下,我们应该指定一个用于创建对象的现有类。让我们演示一下,首先我们要创建一个类:
class Planet
{
private $name;
private $color;
public function setName($planet_name)
{
$this->name = $planet_name;
}
public function setColor($planet_color)
{
$this->color = $planet_color;
}
public function getName()
{
return $this->name;
}
public function getColor()
{
return $this->color;
}
}
请忽略上面代码的幼稚之处,只需注意 Planet 类的属性是 private
并且该类没有构造函数。现在让我们尝试获取结果。
当将 fetch()
与 PDO::FETCH_CLASS
一起使用时,您必须在尝试检索数据之前在语句对象上使用 setFechMode()
方法,例如:
$stmt = $pdo->query("SELECT name, color FROM planets");
$stmt->setFetchMode(PDO::FETCH_CLASS, 'Planet');
我们提供了获取选项常量 PDO::FETCH_CLASS
作为 setFetchMode() 方法的第一个参数,以及用于创建对象的类的名称(在本例中为“Planet”)作为第二个。现在我们运行:
$planet = $stmt->fetch();
应该已经创建了一个 Planet 对象:
var_dump($planet);
Planet Object
(
[name:Planet:private] => earth
[color:Planet:private] => blue
)
请注意,从查询中检索到的值是如何分配给对象的相应属性的,即使它们是私有的。
对象构造后分配属性
行星类没有显式定义构造函数,因此在分配属性时不会出现问题;但是如果该类有一个构造函数,其中的属性被分配或操作呢?由于这些值是在调用构造函数之前分配的,因此它们将被覆盖。
PDO 帮助提供FETCH_PROPS_LATE
常量:使用它时,这些值将在构造对象之后分配给属性。例如:
class Planet
{
private $name;
private $color;
public function __construct($name = moon, $color = grey)
{
$this->name = $name;
$this->color = $color;
}
public function setName($planet_name)
{
$this->name = $planet_name;
}
public function setColor($planet_color)
{
$this->color = $planet_color;
}
public function getName()
{
return $this->name;
}
public function getColor()
{
return $this->color;
}
}
我们修改了 Planet 类,提供了一个带有两个参数的构造函数:第一个是 name
,第二个是 color
。这些参数的默认值分别为 moon
和 gray
:这意味着如果没有显式提供值,则将分配默认值。
在这种情况下,如果我们不使用 FETCH_PROPS_LATE ,无论从数据库中检索到什么值,属性都将始终具有默认值,因为它们在构造对象时将被覆盖。我们来验证一下。首先我们运行查询:
$stmt = $pdo->query("SELECT name, color FROM solar_system WHERE name = 'earth'");
$stmt->setFetchMode(PDO::FETCH_CLASS, 'Planet');
$planet = $stmt->fetch();
然后我们转储 Planet
对象并检查其属性的值:
var_dump($planet);
object(Planet)#2 (2) {
["name":"Planet":private]=>
string(4) "moon"
["color":"Planet":private]=>
string(4) "gray"
}
正如预期的那样,从数据库检索到的值已被默认值覆盖。现在,我们演示如何使用 FETCH_PROPS_LATE 解决此问题(查询与上面相同):
$stmt->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, 'Planet');
$planet = $stmt->fetch();
var_dump($planet);
object(Planet)#4 (2) {
["name":"Planet":private]=>
string(5) "earth"
["color":"Planet":private]=>
string(4) "blue"
}
最后我们得到了想要的结果。但是,如果类构造函数没有默认值并且必须提供默认值怎么办?简单:我们可以在 setFetchMode() 方法中以数组的形式指定构造函数参数作为第三个参数,位于类名之后。例如,让更改构造函数:
class Planet
{
private $name;
private $color;
public function __construct($name, $color)
{
$this->name = $name;
$this->color = $color;
}
[...]
}
构造函数参数现在是强制性的,因此我们将运行:
$stmt->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, 'Planet', ['moon', 'gray']);
在这种情况下,我们提供的参数仅用作默认值,需要在没有错误的情况下初始化对象:它们将被从数据库检索到的值覆盖。
获取多个对象
当然,可以在 while 循环中使用 fetch() 方法来获取多个结果作为对象:
while ($planet = $stmt->fetch()) {
// do stuff with the results
}
或者一次获取所有结果。在这种情况下,如上所述,使用 fetchAll() 方法,您不必在调用方法本身之前指定获取模式,但在调用它时:
$stmt->fetchAll(PDO::FETCH_CLASS|PDO_FETCH_PROPS_LATE, 'Planet', ['moon', 'gray']);
PDO::FETCH_INTO
设置此获取方法后,PDO 将不会创建新对象,而是会更新现有对象的属性,但前提是它们是 public
,或者如果您使用 __set
对象内部的魔术方法。
准备好的陈述与直接陈述
PDO 有两种执行查询的方式:一种是直接的一步方式。另一种更安全的方法是使用准备好的语句
。
直接查询
使用直接查询时,有两个主要方法:query()
和 exec()
。前者返回一个 PDOStatemnt
对象,您可以使用该对象通过 fetch()
或 fetchAll()
方法访问结果:您将其用于不修改表的语句,例如SELECT
。
相反,后者返回查询更改的行数:我们将其用于修改行的语句,例如 INSERT
、DELETE
或 UPDATE
。仅当查询中没有变量并且您绝对相信它是安全的且正确转义时,才应使用直接语句。
准备好的报表
PDO 还支持两阶段准备好的语句:这在查询中使用变量时非常有用,而且通常更安全,因为 prepare()
方法将为我们执行所有必要的转义。让我们看看变量是如何使用的。想象一下,我们想要将 Planet 对象的属性插入到 Planets
表中。首先我们准备查询:
$stmt = $pdo->prepare("INSERT INTO planets(name, color) VALUES(?, ?)");
如前所述,首先我们将使用 prepare()
方法,该方法将 sql 查询作为参数,并使用变量的占位符。现在占位符可以有两种类型:
位置占位符
当使用 ?
位置占位符时,我们可以获得更简洁的代码,但是我们必须在作为 execute 的参数提供的数组中提供要以与列名相同的顺序替换的值()
方法:
$stmt->execute([$planet->name, $planet->color]);
命名占位符
使用命名占位符
,我们不必遵守特定的顺序,但我们将创建更详细的代码。执行execute()
方法时,我们应该以关联数组
的形式提供值,其中每个键都是所使用占位符的名称和关联值将是查询中要替换的那个。例如上面的查询将变为:
$stmt = $pdo->prepare("INSERT INTO planets(name, color) VALUES(:name, :color)");
$stmt->execute(['name' => $planet->name, 'color' => $planet->color]);
在执行修改或仅从数据库检索数据的查询时,可以使用准备和执行方法。在前一种情况下,我们使用上面看到的 fetch 方法来检索数据,而在后一种情况下,我们可以使用 rowCount()
方法检索受影响的行数。
bindValue() 和 bindParam() 方法
为了提供要在查询中替换的值,我们还可以使用 bindValue()
和 bindParam()
方法。第一个将提供的变量值绑定到准备查询时使用的相关位置或命名占位符。使用上面的例子我们会这样做:
$stmt->bindValue('name', $planet->name, PDO::PARAM_STR);
我们将 $planet->name
的值绑定到 :name
占位符。请注意,使用bindValue() 和bindParam() 方法,我们可以使用相关的PDO 常量(在本例中为PDO::PARAM_STR<)指定变量的type
作为第三个参数。 /代码>。
相反,我们可以使用 bindParam()
将变量绑定到准备查询时使用的相关占位符。请注意,在这种情况下,变量由 reference
绑定,并且其值只会在调用 execute()
方法时替换为占位符。语法与上面相同:
$stmt->bindParam('name', $planet->name, PDO::PARAM_STR)
我们将 $planet->name 变量绑定到 :name
占位符,而不是它的当前值!如上所述,转换将在调用 execute()
方法时执行,因此占位符将被变量当时的值替换。
PDO 交易
事务提供了一种在发出多个查询时保持一致性的方法。所有查询都是“批量”完成的,只有全部成功后才会提交到数据库。事务不适用于所有数据库,也不适用于所有 sql
构造,因为其中一些事务会导致隐式提交(此处有完整列表)
举一个极端而奇怪的例子,想象用户必须选择一个行星列表,每次他提交新的选择时,您都希望在插入新的行星之前从数据库中删除前一个行星。如果删除成功但插入不成功会怎样?我们会有一个没有行星的用户!通常情况下,交易是这样实现的:
$pdo->beginTransaction();
try {
$stmt1 = $pdo->exec("DELETE FROM planets");
$stmt2 = $pdo->prepare("INSERT INTO planets(name, color) VALUES (?, ?)");
foreach ($planets as $planet) {
$stmt2->execute([$planet->getName(), $planet->getColor()]);
}
$pdo->commit();
} catch (PDOException $e) {
$pdo->rollBack();
}
首先,PDO 对象的 beginTransaction() 方法禁用查询自动提交,然后在 try-catch 块内,查询按所需顺序执行。此时,如果没有引发 PDOException
,则使用 commit()
方法提交查询,否则,通过 rollBack()
方法提交,事务被恢复并且自动提交被恢复。
这样,在发出多个查询时始终会保持一致性。很明显,只有当 PDO::ATTR_ERRMODE
设置为 PDO::ERRMODE_EXCEPTION
时,您才能使用 PDO 事务。