5

我对 Rails ActiveRecord、Doctrine for PHP(和类似的 ORM)背后的一些设计很感兴趣。

  • ORM 如何实现链式访问器之类的功能以及它们通常工作的深度?
  • ORM 如何在内部构造查询?
  • ORM 如何管理查询,同时保持对它的期望的任意性?

显然这是一个学术问题,但欢迎所有性质的答案!

(我选择的语言是OO PHP5.3!)

4

4 回答 4

3

链式方法调用与 ORM 问题是正交的,它们在 OOP 中随处可见。可链式方法只返回对当前对象的引用,允许调用返回值。在 PHP 中

class A {
    public function b() {
        ...
        return $this;
    }

    public function c($param) {
        ...
        return $this;
    }       
}


$foo = new A();
$foo->b()->c('one');
// chaining is equivilant to
// $foo = $foo->b();
// $foo = $foo->c();

至于如何构造查询,有两种方法。在像 ORM 这样的 ActiveRecord 中,有一些代码可以检查数据库的元数据。大多数数据库都有某种 SQL 或类似 SQL 的命令来查看此元数据。(MySQL 的DESCRIBE TABLE、Oracle 的USER_TAB_COLUMNS表等)

一些 ORM 让您使用中性语言(例如 YAML)来描述您的数据库表。其他人可能会从您创建对象模型的方式中推断出数据库结构(我想说 Django 会这样做,但我已经有一段时间没有看过它了)。最后是一种混合方法,使用前两种技术中的任何一种,但提供了一个单独的工具来自动生成 YAML/etc。或类文件。

一个表的名称和数据类型是已知的,很容易实用地编写一个返回所有行或满足特定条件的特定行集的 SQL 查询。

至于你的最后一个问题,

ORM 如何管理查询,同时保持对它的期望的任意性?

我认为答案是“不太好”。一旦超越了单表、单对象的比喻,每个 ORM 都有不同的方法,即关于如何使用 SQL 查询来建模对象的理念。但在抽象中,它就像添加基于 ORM 假设构造查询的新方法一样简单(即 Zend_Db_Table 的“findManyToManyRowset”方法)

于 2009-07-21T16:30:33.407 回答
2

ORM 如何实现链式访问器之类的功能以及它们通常工作的深度?

似乎没有人回答这个问题。我可以快速描述 Doctrine 如何在 PHP 中做到这一点。

在 Doctrine 中,您在对象模型上看到的所有字段实际上都不是为该类定义的。因此,在您的示例 $car->owners 中,$car 的类中没有定义名为“所有者”的实际字段。

相反,ORM 使用__get 和 __set 之类的魔术方法。所以当你使用像 $car->color 这样的表达式时,PHP 内部会调用 Doctrine_Record#__get('color')。

此时,ORM 可以以任何必要的方式自由地满足这一点。这里有很多可能的设计。例如,它可以将这些值存储在一个名为 $_values 的数组中,然后返回 $this->_values['color']。特别是 Doctrine 不仅跟踪每条记录的值,而且还跟踪其相对于数据库中持久性的状态。

一个不直观的例子是 Doctrine 的关系。当您获得对 $car 的引用时,它与称为“所有者”的 People 表有关系。因此,$car->owners 的数据实际上存储在与 $car 本身的数据不同的表中。所以 ORM 有两种选择:

  1. 每次加载 $user 时,ORM 都会自动连接所有相关表并将该信息填充到对象中。现在,当您执行 $car->owners 时,该数据已经存在。然而,这种方法很慢,因为对象可能有很多关系,而这些关系本身可能也有关系。所以你会添加很多连接,甚至不一定使用这些信息。
  2. 每次加载 $user 时,ORM 都会注意到从 User 表加载了哪些字段并填充它们,但不会加载从相关表加载的任何字段。相反,一些元数据附加到这些字段以将它们标记为“未加载,但可用”。现在,当您编写表达式 $car->owners 时,ORM 看到“所有者”关系尚未加载,它发出单独的查询以获取该信息,将其添加到对象中,然后返回该数据。这一切都是透明地发生的,您无需意识到这一点。

当然,Doctrine 使用 #2,因为对于任何具有中等复杂性的实际生产站点来说,#1 变得笨拙。但它也有副作用。如果您在 $car 上使用多个关系,则 Doctrine 将在您访问它时分别加载每个关系。因此,当可能只需要 1 个时,您最终会运行 5-6 个查询。

Doctrine 允许您通过使用 Doctrine 查询语言来优化这种情况。您告诉 DQL 您要加载汽车对象,但还要将其加入其所有者、制造商、所有权、留置权等,它会将所有这些数据加载到对象中。

哇!长响应。不过,基本上,您已经掌握了“ORM 的目的是什么?”的核心。和“我们为什么要使用一个?” ORM 允许我们在大多数情况下继续以对象模式思考,但抽象并不完美,抽象中的泄漏往往会导致性能损失。

于 2010-03-07T16:42:45.937 回答
1

我创建了一个关于构建 PHP DataMapper 主题的演示文稿,您可能会感兴趣。当我在那里为 PHP 用户组展示它时,它被记录在俄克拉荷马城联合办公协作组织的视频中:

视频: http ://blip.tv/file/2249586/

演示幻灯片: http ://www.slideshare.net/vlucas/building-data-mapper-php5-presentation

该演示文稿基本上是phpDataMapper的早期概念,尽管此后发生了很多变化。

希望它们能帮助您更好地理解 ORM 的内部工作原理。

于 2009-08-20T21:24:59.730 回答
0

链式访问器并不是什么大问题:你return $this来自 setter 方法。繁荣,完成,在你喜欢的多个层次上工作。

于 2009-07-21T14:24:33.890 回答