幕后制作:ORM如何“思考”?

时间:2009-07-21 14:18:11

标签: php ruby-on-rails orm activerecord doctrine

我对Rails ActiveRecord,Doctrine for PHP(以及类似的ORM)背后的一些设计感兴趣。

  • ORM如何设法完成链式访问器等功能以及它们通常需要的工作深度?
  • ORM如何在内部构建查询?
  • ORM如何管理查询,同时维持所有预期的任意性质?

显然这是一个学术问题,但欢迎各种答案!

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

4 个答案:

答案 0 :(得分: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 /等。或类文件。

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

关于你的上一个问题,

  

ORM如何管理查询   维持所有人的任意性质   这是预期的吗?

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

答案 1 :(得分:2)

  

ORM如何设法完成链接访问器等功能以及它们通常需要的工作深度?

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

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

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

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

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

  1. 每次加载$ user时,ORM都会自动加入所有相关表并将该信息填充到对象中。现在,当您执行$ car->所有者时,该数据已经存在。然而,这种方法很慢,因为对象可能有很多关系,而这些关系本身可能有关系。所以你要添加很多连接,甚至不一定要使用这些信息。
  2. 每次加载$ user时,ORM都会注意从User表加载哪些字段并填充它们,但不会加载从相关表加载的任何字段。相反,某些元数据附加到这些字段以将其标记为“未加载但可用”。现在,当您编写表达式$ car-> owner时,ORM会看到“所有者”关系尚未加载,并发出单独的查询以获取该信息,将其添加到对象中,然后返回该数据。这一切都是透明的,无需你意识到这一点。
  3. 当然,Doctrine使用#2,因为#1对任何具有中等复杂性的真实生产站点都变得笨重。但它也有副作用。如果你在$ car上使用了几个关系,那么当你访问它时,Doctrine会分别加载每个关系。所以当你可能只需要1个查询时,你最终会运行5-6个查询。

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

    呼!反应很快。但基本上,你已经明白了“ORM的目的是什么?”的核心。和“我们为什么要用一个?” ORM允许我们在大多数时间继续思考对象模式,但抽象并不完美,抽象中的泄漏往往会出现性能损失。

答案 2 :(得分:1)

我创建了一个关于构建可能对您感兴趣的PHP DataMapper主题的演示文稿。它是在Oklahoma City Coworking Collaborative的视频中录制的,当时我在那里为PHP用户组提出了这个视频:

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

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

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

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

答案 3 :(得分:0)

链式访问器并不是什么大问题:来自setter方法的return $this。繁荣,完成,可以在任意级别上工作。