在 PHP OOP 中处理数据库访问的正确方法

Right way to handle database access in PHP OOP

我在 PHP 的数据库访问方面需要一些帮助。我正在尝试以面向对象的方式做事,但我不确定我的方向是否正确。

假设我有一个 class 人,例如:

class Person {
    private $id;
    private $firstname;
    private $lastname;
    // maybe some more member variables

    function __construct($id = NULL) {
        if(isset($id)) {
            $this->id = $id;
            $this->retrieve();
        }
    }

    // getters, setters and other member functions

    private function retrieve() {
        global $db;

        // Retrieve user from database
        $stmt = $db->prepare("SELECT firstname, lastname FROM users WHERE id = :id");
        $stmt->bindParam(":id", $this->id, PDO::PARAM_INT);
        $stmt->execute();
        $result = $stmt->fetch();

        $this->firstname = $result['firstname'];
        $this->lastname = $result['lastname'];
    }

    function insert() {
        global $db;

        // Insert object into database, or update if exists
        $stmt = $db->prepare("REPLACE INTO users (id, firstname, lastname) VALUES (:id, :firstname, :lastname)");
        $stmt->bindParam(":id", $this->id, PDO::PARAM_INT);
        $stmt->bindParam(":firstname", $this->firstname, PDO::PARAM_STR);
        $stmt->bindParam(":lastname", $this->lastname, PDO::PARAM_STR);
        $stmt->execute();
    }
}

请注意,这只是我为描述我的问题而编写的示例,而不是我在应用程序中使用的实际代码。

现在我的第一个问题是:这是处理数据库交互的正确方法吗?我认为这是一个好方法,因为您可以实例化一个对象,对其进行操作,然后再 insert/update 它。

换句话说:在内部(就像在我的示例中)或外部处理数据库交互更好吗? ] 它,在代码中说 instantiates/uses class?

我的第二个问题是关于更新一大堆可能已修改或未修改的行。假设 class Person 有一个成员变量 $pets[],它是一个包含该人拥有的所有宠物的数组。宠物在数据库中单独存储table,像这样:

+---------+-------------+---------+
|  Field  |    Type     |   Key   |
+---------+-------------+---------+
| pet_id  | int(11)     | PRI     |
| user_id | int(11)     | MUL     |
| name    | varchar(25) |         |
+---------+-------------+---------+

假设我修改了 Person 对象中的一些宠物。也许我添加或删除了一些宠物,也许我只更新了一些宠物的名字。

更新整个人(包括他们的宠物)的最佳方法是什么?假设一个人有 50 只宠物,即使只有一只宠物发生变化,我是否也只更新它们?

我希望这已经够清楚了 ;)

编辑:

更重要的是,我如何同时处理deletions/insertions?我目前的方法是,在 "edit page" 上,我检索某个人(包括他们的宠物)和 display/print 他们以供用户编辑的形式。然后用户可以编辑宠物、添加新宠物或删除一些宠物。当用户单击 "apply" 按钮时,表单将被 POST 回 PHP 脚本。

我能想到的将这些更改更新到数据库中的唯一方法是删除数据库中当前列出的所有宠物,然后插入新的一组宠物。但是这有一些问题:首先,宠物 table 中的所有行在每次编辑时都会被​​删除并重新插入,其次,自动增量 ID 每次都会因此而发生巨大的飞跃。

我觉得我做错了什么。是不是不可能让用户 remove/add 养宠物并同时修改现有宠物(我应该分别处理这些操作)吗?

在回答问题 1 时:

我建议在 class 中使用 sql,并根据您的示例通过参数输入条件。因为它们会(应该)都与表示 Person 对象的 table 数据相关。

第二次:

如果宠物在一秒钟内 table,我建议您将其作为一个单独的 class,使用不同的 sql 查询。您的示例中的变量 $pets[] 可以保存 pet_ids 并且您可以在 Pet class 中有单独的 sql 以根据需要进行任何更改、添加或删除。

您要完成的是一项名为 对象关系映射 的任务(将对象映射到关系数据库中的 table,反之亦然)。整本书都写在这方面,但我会尽量给出一个简短的概述。

Now my first question is: is this the correct way to handle database interaction? I thought this would be a good way because you can instantiate an object, manipulate it, then insert/update it again.

这是一种有效的方法。然而, 一般来说,你应该尽量坚持Separation of Concerns。在我看来,建模一个 域实体 (就像一个人,在你的情况下)和从数据库存储这个对象 in/loading 是两个(可以说是三个)不同的问题,应该是在单独的 classes 中实现(同样,个人意见!)。它使 classes 的单元测试变得非常困难,并增加了很多复杂性。

关于 ORM,随着时间的推移出现了几种设计模式。最突出的是:

  • Active Record 基本上是您已经在问题中提出的方法;它将数据和数据访问逻辑紧密耦合在一个对象中。在我看来这不是最好的方法,因为它违反了关注点分离,但可能最容易实现。

  • Gateways or Mappers:尝试创建一个单独的 class 来访问您的 Persons table(类似于 PersonGateway。这样,你的 Person class 只包含数据和各种行为,而你的 PersonGateway 各种 insert/update/delete 方法。A 另一方面,mapper 可能是一个 class,它将通用数据库结果对象(例如 PDO 查询返回的行)转换为 Person 对象(在本例中,Person class不需要知道这样一个映射器的存在 class).

What is the best way to update the whole Person, including their pets in that case? Lets say one Person has 50 pets, do I just update them all even if only one of them has changed?

同样,有几种可能性。在任何情况下,您都应该将我们的宠物映射为单独的 class。

  • 将宠物分开 table 并为此 class 实现您自己的数据访问逻辑(例如使用上述模式之一)。然后,您可以随意单独更新、插入或删除它们。您可以在父对象(人)中跟踪 added/deleted 宠物,然后在持久化父对象时级联更新操作。

  • Embed Persons table 中的 pets 集合。不过,根据此集合的平均大小以及您可能希望查询它们的方式,这可能不是一个好主意。

如果您的项目变得复杂,您可能还想看看为您解决这些问题的 ORM 框架(例如 Doctrine ORM)。