How does an ORM manage to accomplish features like chained accessors and how deep are they typically expected to work?
Nobody seems to have answered this. I can quickly describe how Doctrine does this in PHP.
In Doctrine, none of the fields which you see on an object model are actually defined for that class. So in your example, $car->owners, there is no actual field called 'owners' defined in $car's class.
Instead, the ORM uses magic methods like __get and __set. So when you use an expression like $car->color, internally PHP calls Doctrine_Record#__get('color').
At this point the ORM is free to satisfy this in anyway necessary. There are a lot of possible designs here. It can store these values in an array called $_values, for example, and then return $this->_values['color']. Doctrine in particular tracks not only the values for each record, but also its status relative to the persistence in the database.
One example of this that is not intuitive is with Doctrine's relations. When you get a reference to $car, it has a relationship to the People table that is called 'owners'. So the data for $car->owners is actually stored in a separate table from the data for $car itself. So the ORM has two choices:
- Each time you load a $user, the ORM automatically joins all related tables and populates that information into the object. Now when you do $car->owners, that data is already there. This method is slow, however, because objects may have many relationships, and those relationships may have relationships themselves. So you'd be adding a lot of joins and not necessarily even using that information.
- Each time you load a $user, the ORM notices which fields are loaded from the User table and it populates them, but any fields which are loaded from related tables are not loaded. Instead, some metadata is attached to those fields to mark them as being 'not loaded, but available'. Now when you write the expression $car->owners, the ORM sees that the 'owners' relationship has not been loaded, and it issues a separate query to get that information, add it into the object, and then return that data. This all happens transparently without you needing to realize it.
Of course, Doctrine uses #2, since #1 becomes unwieldy for any real production site with moderate complexity. But it also has side-effects. If you are using several relations on $car, then Doctrine will load each one separately, as you access it. So you end up running 5-6 queries when maybe only 1 was required.
Doctrine allows you to optimize this situation by using Doctrine Query Language. You tell DQL that you want to load a car object, but also join it to its owners, manufacturer, titles, liens, etc. and it will load all of that data into objects.
Whew! Long response. Basically, though, you've gotten at the heart of "What is the purpose of an ORM?" and "Why should we use one?" The ORM allows us to continue thinking in object mode at most times, but the abstraction is not perfect and the leaks in the abstraction tend to come out as performance penalties.