(惟一合适) PDO 教程

PDO是什么

首先思考, 为何选择PDOphp

PDO 是一个数据访问抽象层(Database Access Abstraction Layer). 抽象是双重的: 一个是众所周知但不过重要的. 另外一个是模糊的可是是最重要的.
众所周知 PDO 为不一样的数据库提供了统一的接口. 虽然这个功能自己很庞大, 可是对于固定程序来讲不是过于重要的事情, 基本全部的程序都是使用统一的后端数据库. 尽管有一些谣言, 可是经过改变单行 PDO 配置来切换后端数据库是不可能的-因为不一样的 SQL 风格(为此, 须要使用像 DQL 这样的平均查询语言). 所以对于普通的 LAMP 开发者来讲, 这一点是微不足道的, 而且对他而言, PDO只是熟悉的 mysql(i)_query() 函数的另外一个更复杂版本. 但实际上它不是, 它有丰富的其余功能.
PDO 不只抽象了数据库API, 还抽象了基本操做, 不然必须在每一个应用程序中重复数百次, 使您的代码很是WET. 不一样于 mysqlmysqli , 两个都不能直接使用低级裸 APIs(但仅做为某些更高级别抽象层的构建材料), PDO就是这样的抽象. 虽然还是不完整的, 可是至少可用.
真正的PDO好处是:mysql

  • 安全性 (可用的准备语句)
  • 可用性 (许多辅助函数能够自动执行平常操做)
  • 可重用性 (用于访问大量数据库的统一API, 从SQLITE到oracle)

请注意, 尽管 PDO 是原生数据库驱动程序中最好的, 但对于现代WEB应用程序来讲, 请考虑将使用有查询构建器的 ORM 或者与其余更高抽象级别的库一块儿使用, 只是偶尔使用原生的PDO. 好的ORM好比 Doctrine, Eloquent, RedBeanYii::AR. Aura.SQL 是具备不少附加功能的使用PDO包装器的一个很好的例子.
不管哪一种方式, 首先要了解基本工具是件好事. 那么, 让咱们开始吧:sql

connection DSN

PDO有一个叫 DSN 的预想接方式. 它并不复杂-PDO须要你在三个不一样的位置输入不一样的配置, 而不是一个简单的选项列表.数据库

  • database driver, host, db(schema) namecharset, 以及不常使用的 portunix_socket 设置 DSN
  • user_namepassword 设置构造方法
  • 其余全部的配置在options数组

其中 DSN 是以分号分隔的字符串, 由 param=value 键值对组成, 从驱动程序名称和冒号开始:后端

mysql:host=localhost;dbname=test;port=3306;charset=utf8mb4
driver^  ^colon           ^param=value pair   ^semicolon

注意, 遵循正确的格式是很是重要的- DSN中不能使用 空格, 引号, 和其余的符号, 只能使用参数, 值和定界符. 就像手册上展现的.数组

这里有一个例子:安全

$host = '127.0.0.1';
$db = 'test';
$pass = 'root';
$charset = 'utf8mb4';

$dsn = "mysql:host={$host};dbnamej={$db};charset={$charset}";
$options = [
    PDO::ATR_ERRMODE => PDO::ERRMODE_EXECPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES => false,
];
try {
    $pdo = new \PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
}

设置了全部上述变量属性, 咱们将在 $pdo 变量中获得一个正确的 PDO 实例.
使用旧mysql扩展用户重要通知oracle

  1. 不一样于 mysql_* 函数, 能够在代码的任意位置使用, pdo 实例被存储在一个变量中, 那就意味着只能在函数内部进行访问. 所以, 必须经过函数参数传递或使用更高级的技术, 好比IOC容器.
  2. 链接只用建立一次. 不要在函数, 类构造函数建立链接, 不然, 会建立多个链接, 最终致使数据库服务宕机. 所以必须建立惟一的 PDO 实例, 让整个脚本使用.(适用于FPM模式)
  3. 经过DSN设置字符集是很是重要的-这是惟一正确的方式由于它会告诉PDO哪一个字符集会被使用. 所以, 忘记经过 Query 运行SET NAMES 或者经过 PDO::MYSQL_ATTR_INIT_COMMAND. 只有当PHP版本太低时(低于5.3.6), 才可使用 SET NAMES 查询, 而且关闭仿真模式.

更多关于链接的内容能够在 链接MySQL查看socket

运行查询 PDO::query()

使用 PDO 有两种方式运行查询. 若是查询中没有使用变量, 可使用 PDO::query 方法. 它会运行查询并返回一个 PDOStatement 类的对象, 该类与 mysql_query 返回的资源大体相同, 特别时从中获取实际记录的操做:ide

$stmt = $pdo->query('SELECT name FROM users');
while ($row = $stmt->fetch()) {
    echo $row['name'] . "\n";
}

而且 query() 方法容许咱们使用一个整洁的方法链接 SELECT 查询, 以下所示.

预处理, 防止SQL注入

放弃熟悉的 mysql_query()函数 并进入严格数据对象领域的主要缘由是 PDO 已经准备好了开箱即用的预处理语句. 若是要在语句中使用变量, 预处理语句是惟一正确运行的方式. 它如此重要的缘由在 The Hitchhiker's Guide to SQL Injection prevention.有详细的解释.
对于运行的查询, 若是至少使用一个变量, 你必须使用占位符替换它. 准备执行语句, 而后分别传入变量执行.
长话短说, 它不像感受的那么困难. 在大多数例子中, 你只须要使用函数 prepareexecute .
首先, 须要修改查询, 在使用变量的位置添加占位符, 就像这样

$sql = "SELECT * FROM users WHERE email = '{$email}' AND status = '{$status}'";

改成

$sql = "SELECT * FROM users where email = ? and status = ?";

或者

$sql = "SELECT * FROM users where email = :email AND status = :status";

注意 PDO 支持位置(?)和命名(:email)占位符, 后者始终以冒号开始,而且只能使用字母, 数字和下划线. 还须要注意 占位符周围不能使用引号 .
一个查询使用了占位符, 就必须使用PDO::prepare()方法预处理. 这个方法返回一个和咱们上边讨论的相同的 PDOStatement 对象, 可是没有绑定任何数据.
最后, 必须使用 PDOStatement 对象的 execute() 方法执行查询, 而且经过数组形式传递参数. 以后, 就能够从语句中获得结果数据(若是适用).

$stmt = $pdo->prepare("SELECT * FROM users WHERES email = ? AND status = ?");
$stmt->execute([$email, $status])
$user = $stmt->fetch();

// or
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email AND status = :status");
$stmt->execute(['email' => $email, 'status' => $status]);
$user = $stmt->fetch();

能够看到, 位置占位符, 你须要提供一个索引数组. 命名占位符, 须要提供一个关联数组, 而且键要匹配查询中的占位符. 同一个查询中不能混合位置占位符和命名占位符.
位置占位符可让你写更简短的代码, 可是对参数顺序是敏感的(必须于查询中参数的顺序一致). 虽然命名占位符使代码更冗长, 可是容许随机参数绑定.
另外须要注意, 虽然存在广泛的误解, 可是数组键中 : 不是必须的.
执行后就可使用支持的方法获取结果.

更多的例子能够查看(respective article)[https://phpdelusions.net/pdo_...].

参数绑定

将数据传入 execute() (如上所示)方法中应被视为默认的最方便的方式. 若是使用这个方法, 全部参数都将会绑定为字符串(若是使用NULL值, 将会使用SQL NULL发送给查询), 大多数时候都没有问题.
可是, 有时候最好明确设置类型. 可能状况以下:

  • 开启仿真模式的 LIMIT 子句(或者其余不能接受字符串操做数的 SQL 子句)
  • 可能受到错误操做数类型影响具备特殊查询计划的复杂特殊查询
  • 特有的列类型, 像 bigint boolean 必须绑定精确的操做数(为了将 BIGINT 绑定为 PDO::PARAM_INT 须要基于 mysqlnd)

这种状况下, 必须使用显式绑定, 能够从 bindvalue()bindParam() 两个函数中选择一个. 前者是推荐使用的, 它不像 bindParam()具备必定的反作用.

查询能够绑定的部分

了解哪些查询部分可使用参数绑定哪些部分不能使用是很是重要的. 事实上, 这个列表是很是短的: 只有字符串和数字字面量能够被绑定. 只要你的数据在查询中能被表示为数字或者带引号的字符串, 就能够被绑定. 其余全部状况你不能使用 PDO 预处理语句: 既不是标识符也不是逗号分隔列表, 或者是引用的文字字符串的一部分, 或者其余任意查询部分都不能使用预准备语句绑定
最多见的用例解决方案能够在[本章的响应部分查看]()

预处理, 屡次执行

有时候你可使用预处理屡次执行准备好的查询, 比一次又一次执行相同的查询快一点, 由于它只解析查询一次. 若是能够执行另外一个PHP实例中的预处理语句, 这个功能就是很是有用的, 可是事实并不是如此. 只会在同一个实例中重复相同的查询, 这在常规的PHP脚本中不多使用到, 并限制了此功能用于重复插入和更新.

$data = [
    1 => 1000,
    2 => 200,
    3 => 200,
];
$stmt = $pdo->prepare('UPDATE users SET bonus = bonus + ? where id = ?');
foreach ($data as $id => $bonus) {
    $stmt->execute([$bonus, $id]);
}

注意这个功能有点被高估了. 不只须要讨论, 并且性能提高也不是很大 - 查询解析有时候是
很快的. 并且只有在关闭仿真模式的时候才能带来性能提高.

运行SELECT INSERT UPDATE DELETE语句

这些查询没有什么特别之处, 对PDO来讲他们都是同样的. 运行哪一个查询并不重要.
如上所示, 须要准备带有占位符的预处理查询, 传入变量并执行. DELETESELECT 的处理过程是基本相同的. 仅有的不一样点是( DML查询不会返回任何数据), 你可使用链式方法, 调用 execute()prepare().

$sql = "UPDATE users SET name = ? where id = ?";
$pdo->prepare($sql)->execute([$name, $id]);

然而, 你像得到影响行数, 代码将和无聊的三行代码相同:

$stmt = $pdo->prepare("DELETE FROM goods where category = ?");
$stmt->execute([$cat]);
$deleted = $stmt->rowCount();

更多的例子能够在respective article.找到.

从statement获取数据 foreach

咱们已经见过这个函数了, 如今让咱们仔细看看. 它从数据库获取单行数据, 在结果集中移动内部指针, 所以, 对函数的后续调用将逐个返回全部行. 这个方法和 mysql_fetch_array() 大体相同但在工做模式稍微有点不一样: 代替不少不一样函数( mysql_fetch_assoc() mysql_fetch_row), 这个只有一个方法, 可是它的行为能够经过一个参数改变. 在 PDO 中有不少的获取模式, 稍后咱们详细讨论, 这里有一些简单的实例:

  • PDO::FETCH_NUM 返回索引数组
  • PDO::FETCH_ASSOC 返回关联数组
  • PDO::FETCH_BOTH 以上二者都包含
  • PDO::FETCH_OBJ 返回对象
  • PDO::FETCH_LAZY 容许三个(索引数组, 关联数组, 对象)方法没有内存开销.

从上面能够看出, 这个必须在两种状况下使用:

  1. 当只须要一行时, 只获取一行

    $row = $stmt->fetch(PDO::FETCH_ASSOC);

    将以关联数组的方式从语句中获取一行

  2. 当咱们须要在使用以前处理返回数据. 在这种状况下, 必须经过while循环运行, 如上所示.

另外一种有用的模式是 PDO::FETCH_CLASS 能够建立一个特定类的对象

$news = $pdo->query("select * from news")->fetchAll(PDO::FETCH_CLASS, 'News');

将生成一个News类对象的数组, 而且经过返回值设置类属性. 注意这个模式下:

  • 属性会在构造方法以前设置
  • 全部未定义的属性都会调用 __set魔术方法
  • 若是没有 __set方法, 将会建立新属性
  • 私有属性也会被设置, 这有点意外可是很是方便
相关文章
相关标签/搜索