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

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

  • ORM如何设法实现链式访问器等function以及它们通常预期有多深?
  • ORM如何在内部构建查询?
  • ORM如何管理查询,同时维持所有预期的任意性质?

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

(我选择的语言是OO PHP5.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”方法)

ORM如何设法实现链式访问器等function以及它们通常预期有多深?

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

在Doctrine中,您在对象模型上看到的所有字段实际上都不是为该类定义的。 所以在你的例子中,$ car-> owner,在$ car的类中没有定义名为’owner’的实际字段。

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

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

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

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

当然,Doctrine使用#2,因为对于任何具有中等复杂性的真实生产站点,#1变得笨重。 但它也有副作用。 如果你在$ car上使用了几个关系,那么当你访问它时,Doctrine会分别加载每个关系。 所以当你可能只需要1个查询时,你最终会运行5-6个查询。

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

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

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

video: http : //blip.tv/file/2249586/

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

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

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

链式访问器并不是什么大问题:你从setter方法return $this 。 繁荣,完成,在你喜欢的任何级别工作。