PHP项目行数据网关与记录集

wen PHP项目 2

本文目录导读:

PHP项目行数据网关与记录集

  1. 核心概念理解
  2. 两者之间的关系与区别
  3. PHP 代码示例
  4. 行数据网关与记录集的优缺点
  5. 总结与选择建议

我们来详细解析一下 PHP 项目中的 行数据网关记录集 这两个概念,它们都是数据源架构模式中的重要组成部分,尤其是在传统的 PHP 项目中(如使用 MySQLi 或 PDO 扩展,或者基于 ActiveRecord 模式之前的框架中)非常常见。

核心概念理解

行数据网关

  • 定义:一个对象,它充当一个单独的行记录与数据库之间的“网关”或“接口”。
  • 核心思想:将数据库查询、插入、更新、删除操作(CRUD)从业务逻辑中分离出来,封装到一个专门的对象中,这个对象的每个实例通常对应数据库表中的一行数据。
  • 特点
    • 领域模型简单:通常没有复杂的业务逻辑,主要是数据的 getter/setter 和简单的数据校验。
    • 强耦合于表结构:一个行数据网关类通常对应一个数据库表。
    • 无状态:对象本身不维护连接,每次操作都需要传入数据库连接。

记录集

  • 定义:它是一种数据结构,用来存储从数据库查询返回的一组或多行数据的集合。
  • 常见实现
    • 在 PHP 的 mysqli 扩展中,是 mysqli_result 对象。
    • PDO 中,是 PDOStatement 对象(执行查询后,通过 fetchAll() 等方法可以获取数据)。
  • 特点
    • 包含元数据:除了数据行,还包含字段名、字段类型等描述信息。
    • 可遍历:通常可以通过循环(如 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 中的行数据网关与记录集。

抱歉,评论功能暂时关闭!