本文目录导读:

我们来详细解析一下 PHP 项目中的 行数据网关 和 记录集 这两个概念,它们都是数据源架构模式中的重要组成部分,尤其是在传统的 PHP 项目中(如使用 MySQLi 或 PDO 扩展,或者基于 ActiveRecord 模式之前的框架中)非常常见。
核心概念理解
行数据网关
- 定义:一个对象,它充当一个单独的行记录与数据库之间的“网关”或“接口”。
- 核心思想:将数据库查询、插入、更新、删除操作(CRUD)从业务逻辑中分离出来,封装到一个专门的对象中,这个对象的每个实例通常对应数据库表中的一行数据。
- 特点:
- 领域模型简单:通常没有复杂的业务逻辑,主要是数据的 getter/setter 和简单的数据校验。
- 强耦合于表结构:一个行数据网关类通常对应一个数据库表。
- 无状态:对象本身不维护连接,每次操作都需要传入数据库连接。
记录集
- 定义:它是一种数据结构,用来存储从数据库查询返回的一组或多行数据的集合。
- 常见实现:
- 在 PHP 的 mysqli 扩展中,是
mysqli_result对象。 - 在 PDO 中,是
PDOStatement对象(执行查询后,通过fetchAll()等方法可以获取数据)。
- 在 PHP 的 mysqli 扩展中,是
- 特点:
- 包含元数据:除了数据行,还包含字段名、字段类型等描述信息。
- 可遍历:通常可以通过循环(如
while($row = ...)) 逐行获取数据。 - 不可变性/一次性使用:在某些实现中,记录集在获取完所有数据或关闭后,无法再次使用(除非重新查询)。
- 数据访问方式:通常通过游标(cursor)进行访问,可以顺序读取或随机读取(取决于实现)。
两者之间的关系与区别
这是理解这两个概念的关键点。
| 特性 | 行数据网关 | 记录集 |
|---|---|---|
| 粒度 | 一行数据(一个对象实例代表一行)。 | 多行数据(一个对象/结构代表整个结果集)。 |
| 封装层次 | 更上层,封装了 SQL 操作(增删改查)和字段映射。 | 更底层,是数据库查询结果的直接、原始的包装。 |
| 角色 | 主动的,可以执行 save()、update()、delete() 等方法。 |
被动的,主要作为数据容器,通常不提供直接的修改数据库方法。 |
| 面向对象 | 高,是真正的对象,每个字段有 getter/setter。 | 低,更像是一个数组的集合或迭代器。 |
| 典型应用 | 与 ActiveRecord 模式类似,但更关注于简单的数据映射和操作。 | 常用于简单查询,或者作为 ORM(对象关系映射)的底层数据来源。 |
一句话总结: 记录集是数据从数据库拿到内存后的原始样子(一堆数组)。行数据网关是把记录集中的一行(一个数组)包装成一个对象,这个对象知道如何保存自己、更新自己或删除自己。
PHP 代码示例
我们来通过一个简单的 users 表来演示。
记录集(使用 PDO)
记录集通常是直接使用 PDO 查询得到的结果。
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 查询所有用户,得到的就是一个记录集(PDOStatement 对象)
$stmt = $pdo->prepare('SELECT id, name, email, created_at FROM users WHERE active = :active');
$stmt->execute(['active' => 1]);
// 记录集 $stmt 包含了所有行数据
// 我们可以用 fetchAll() 将所有行放入一个数组(这是一个数组的记录集)
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
// $row 是一个关联数组:['id' => 1, 'name' => '张三', ...]
echo $row['name'] . ' - ' . $row['email'] . PHP_EOL;
}
// 也可以逐行获取,使用游标(cursor)
$stmt->execute(['active' => 1]);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo $row['name'] . ' - ' . $row['email'] . PHP_EOL;
}
?>
- 特点:数据是数组形式,没有对象方法,无法直接保存回去。
行数据网关
我们将每一行数据封装成一个 User 对象,这个对象拥有访问数据库的能力。
<?php
// 行数据网关类
class UserGateway {
private $pdo;
// 属性对应表字段
public $id;
public $name;
public $email;
public $active;
public $created_at;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
// 从数据库加载一行数据到当前对象
public function findById(int $id): ?self {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) {
return null;
}
// 将数组属性映射到对象属性
return $this->map($row);
}
// 将查询结果映射到对象
private function map(array $row): self {
$this->id = $row['id'];
$this->name = $row['name'];
$this->email = $row['email'];
$this->active = $row['active'];
$this->created_at = $row['created_at'];
return $this;
}
// 保存当前对象(插入或更新)
public function save(): bool {
if ($this->id) {
// 更新
$stmt = $this->pdo->prepare('UPDATE users SET name = :name, email = :email, active = :active WHERE id = :id');
return $stmt->execute([
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'active' => $this->active
]);
} else {
// 插入
$stmt = $this->pdo->prepare('INSERT INTO users (name, email, active, created_at) VALUES (:name, :email, :active, NOW())');
$result = $stmt->execute([
'name' => $this->name,
'email' => $this->email,
'active' => $this->active
]);
if ($result) {
$this->id = $this->pdo->lastInsertId();
}
return $result;
}
}
// 删除当前对象
public function delete(): bool {
if (!$this->id) return false;
$stmt = $this->pdo->prepare('DELETE FROM users WHERE id = :id');
return $stmt->execute(['id' => $this->id]);
}
// 静态方法:获取所有用户(返回对象数组)—— 这里通常需要配合集合对象或工厂
public static function findAllActive(PDO $pdo): array {
$stmt = $pdo->prepare('SELECT * FROM users WHERE active = :active');
$stmt->execute(['active' => 1]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$gateways = [];
foreach ($rows as $row) {
$gateway = new self($pdo);
$gateways[] = $gateway->map($row);
}
return $gateways;
}
}
// 使用行数据网关
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', 'password');
// 1. 创建一个新用户
$user = new UserGateway($pdo);
$user->name = '李四';
$user->email = 'lisi@example.com';
$user->active = 1;
$user->save();
echo "新用户ID: " . $user->id; // 输出 ID
// 2. 加载并更新一个用户
$user = new UserGateway($pdo);
$loadedUser = $user->findById(1);
if ($loadedUser) {
$loadedUser->name = '张三(已修改)';
$loadedUser->save();
}
// 3. 删除用户
$user = new UserGateway($pdo);
$loadedUser = $user->findById(2);
if ($loadedUser) {
$loadedUser->delete();
}
// 4. 获取所有活跃用户(对象数组)
$activeUsers = UserGateway::findAllActive($pdo);
foreach ($activeUsers as $user) {
echo $user->name . ' - ' . $user->email . PHP_EOL;
}
?>
- 特点:每个
UserGateway对象代表数据库中的一行,可以独立进行save()、delete()等操作,它封装了 SQL 语句,业务逻辑(如修改用户)只需要操作对象属性,然后调用save()即可。
行数据网关与记录集的优缺点
| 模式 | 优点 | 缺点 |
|---|---|---|
| 记录集 | 简单直接:代码量少,易于理解和编写。 性能较好:没有额外的对象创建开销(特别是批量查询时)。 灵活:可以轻松地使用原生 SQL 进行复杂查询。 |
散落 SQL:SQL 语句分散在业务逻辑中,难以维护和复用。 非面向对象:数据是数组/原始类型,缺少行为(方法),业务逻辑与数据分离。 代码冗余:同样的查询逻辑(如“按ID获取用户”)会在多个地方重复。 |
| 行数据网关 | 更好的封装性:SQL 语句被隐藏,业务逻辑更干净。 代码复用:CRUD 操作集中在网关类中,修改表结构时只需修改一处。 业务逻辑可扩展:可以在网关中添加校验规则、计算属性等。 测试更友好:可以轻松 mock 网关类。 |
对象开销:每行数据都要实例化一个对象,内存和性能开销略大。 实现复杂:需要额外编写映射代码。 查询灵活性降低:对于复杂查询(如 JOIN),需要在网关中编写专门的查询方法,或返回记录集。 |
总结与选择建议
-
何时使用记录集?
- 简单脚本/小项目:例如一个简单的博客、API 端点,不需要复杂的业务逻辑。
- 批量数据导出/报表:只需要读取数据,不需要修改。
- 性能敏感的场景:需要快速处理大量只读数据,避免对象创建开销。
-
何时使用行数据网关?
- 中型到大型项目:业务逻辑复杂,需要清晰的代码结构和可维护性。
- 数据需要修改/保存:需要对单行数据进行频繁的创建、更新、删除操作。
- 作为 ORM 的轻量级替代:当不想引入复杂的 ORM 框架(如 Doctrine、Eloquent)时,行数据网关是一个很好的中层解决方案。
-
现实中的组合使用:
- 一个 PHP 项目中往往同时使用两者。
UserGateway::findAllActive()方法内部会使用 PDO 获取记录集,然后遍历记录集,将每一行数据实例化为一个UserGateway对象,这样,你既获得了记录集查询的灵活性,又得到了对象的好处。
// 组合使用:记录集用于查询,行数据网关用于操作 $stmt = $pdo->query('SELECT * FROM users'); $rows = $stmt->fetchAll(); // 这是记录集 $users = []; foreach ($rows as $row) { $user = new UserGateway($pdo); // 创建行数据网关对象 $user->map($row); // 填充数据 $users[] = $user; }
希望这个详细的解释能帮助你清晰地理解 PHP 中的行数据网关与记录集。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播