01. PDO 连接数据库
PDO 是 PHP 访问数据库的标准扩展,支持多种数据库切换驱动不用改代码。new PDO 创建连接,DSN 指定数据库类型和地址。PDO 默认会抛异常,配合 try-catch 捕获错误。连接后 query 直接执行 SQL,exec 执行无返回的语句。
php
<?php
$dsn = "mysql:host=localhost;dbname=mydb;charset=utf8mb4";
$user = "root";
$pass = "123456";
try {
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
echo "连接成功\n";
} catch (PDOException $e) {
die("连接失败: " . $e->getMessage());
}
// 查询
$stmt = $pdo->query("SELECT * FROM users");
$users = $stmt->fetchAll();
print_r($users);PDO::ATTR_DEFAULT_FETCH_MODE 设成 FETCH_ASSOC 就不用每次都写 fetch 模式了。
02. 预处理语句防注入
预处理语句是防范 SQL 注入的核心武器。用 prepare 准备带占位符的 SQL,然后 bindValue/bindParam 绑定参数,最后 execute 执行。占位符有两种:? 命名占位和 :name 命名占位(推荐后者,语义更清晰)。永远不要拼接用户输入到 SQL 里!
php
<?php
$pdo = new PDO("mysql:host=localhost;dbname=mydb", "root", "123456");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 命名占位符(推荐)
$stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (:name, :email)");
$stmt->execute([
":name" => "小明",
":email" => "[email protected]",
]);
// 查询
$stmt = $pdo->prepare("SELECT * FROM users WHERE age > :age");
$stmt->execute([":age" => 18]);
$users = $stmt->fetchAll();
foreach ($users as $user) {
echo $user["name"] . "\n";
}
// 单行查询
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute([":id" => 1]);
$user = $stmt->fetch();
$count = $stmt->rowCount(); // 受影响行数拼接 SQL 字符串是 PHP 代码最常见的安全漏洞:$pdo->query("SELECT * FROM users WHERE name = '$name'") 非常危险!
03. 事务处理
事务确保一组 SQL 要么全成功要么全失败。PDO 用 beginTransaction 开启,commit 提交,rollBack 回滚。事务期间必须用同一个 PDO 连接。转账是最经典的事务场景。inTransaction 检查是否正在事务中。
php
<?php
try {
$pdo->beginTransaction();
// 扣钱
$stmt = $pdo->prepare("UPDATE accounts SET balance = balance - :amount WHERE id = :from");
$stmt->execute([":amount" => 100, ":from" => 1]);
// 加钱
$stmt = $pdo->prepare("UPDATE accounts SET balance = balance + :amount WHERE id = :to");
$stmt->execute([":amount" => 100, ":to" => 2]);
$pdo->commit();
echo "转账成功\n";
} catch (Exception $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
echo "转账失败: " . $e->getMessage() . "\n";
}事务中不要执行耗时的非数据库操作,比如发邮件、调远程 API,会长时间持有锁。
04. 数据层封装
在实际项目中不会直接用裸 PDO,通常会封装一个简单的查询类或使用 ORM。通过继承或组合封装增删改查通用操作,避免 SQL 散落在各处。如果要切换数据库或添加缓存、日志等横切逻辑,有封装层就好处理得多。
php
<?php
class BaseModel {
protected PDO $pdo;
protected string $table;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function find(int $id): ?array {
$stmt = $this->pdo->prepare("SELECT * FROM {$this->table} WHERE id = :id");
$stmt->execute([":id" => $id]);
return $stmt->fetch() ?: null;
}
public function all(): array {
$stmt = $this->pdo->query("SELECT * FROM {$this->table}");
return $stmt->fetchAll();
}
public function create(array $data): int {
$columns = implode(", ", array_keys($data));
$placeholders = ":" . implode(", :", array_keys($data));
$stmt = $this->pdo->prepare("INSERT INTO {$this->table} ({$columns}) VALUES ({$placeholders})");
$stmt->execute($data);
return (int) $this->pdo->lastInsertId();
}
}
class User extends BaseModel {
protected string $table = "users";
}
$user = new User($pdo);
$user->create(["name" => "小红", "email" => "[email protected]"]);实际项目推荐用 Laravel Eloquent 或 Doctrine ORM,不需要自己手写这套。
05. 常见安全问题
数据库操作的安全漏洞排名第一就是 SQL 注入。黄金法则:所有用户输入都用预处理参数绑定。第二是错误信息泄露,生产环境绝不能把数据库错误直接展示给用户,用 try-catch 记日志展示友好提示。数据库权限也应最小化,Web 应用用专门的受限账户而不是 root。
php
<?php
// 生产环境配置
ini_set("display_errors", "0"); // 不显示错误给用户
error_reporting(E_ALL);
// 安全的查询写法
function searchUsers(PDO $pdo, string $keyword): array {
$stmt = $pdo->prepare("SELECT * FROM users WHERE name LIKE :keyword");
$stmt->execute([":keyword" => "%{$keyword}%"]);
return $stmt->fetchAll();
}
// 生产环境错误处理
try {
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
} catch (PDOException $e) {
error_log($e->getMessage()); // 记录日志
die("服务暂时不可用,请稍后再试"); // 友好提示
}用最小权限原则:Web 应用只给 SELECT/INSERT/UPDATE/DELETE 权限,不要给 DROP/ALTER。
知识测验
第 1/4 题正确 0
PDO 中准备语句的命名占位符格式?
下一节
下一节 Laravel 入门