Doctrine, hydrate_record, & lazy loading
So, through my adventures with Doctrine I’ve encountered quite a few bugs that a normal (or power) user would come across. The problem I am going to mention today is one that can severely hurt a site’s loading time and cause query backup to the database.
It begins with code like this:
$q = Doctrine_Query::create()->
from("profile p")->
select("p.name, p.zipcode")->
whereIn("p.id", array(1,2,3,4,5))->
execute(array(), Doctrine::HYDRATE_RECORD);
Simple code, pull the list of names and zip codes from the database where the ID’s coincide with the given array.
<?php foreach($q AS $person): ?> <?php echo $person->name ?> - <?php echo $person->zipcode ?> <br /> <?php endforeach; ?>
The result would be something such as
Bob - 63122 Tom - 63122 Frank - 63025 Jim - 63123 Bill - 61236
Now here is the problem: Let’s assume that Jim doesn’t have his zip code in the database. Instead of “62123″ the result of “$person->zipcode” would be null.
Bob - 63122 Tom - 63122 Frank - 63025 Jim - Bill - 61236
Unfortunately, the null response kicks Doctrine’s lazy field loading feature into drive. Lazy field loading is Doctrines ability to load data from the database not specified in a select() tag. Read more about it here. So, in short, the null response tricks Doctrine into thinking that the field wasn’t populated by the hydrator, then creates a query to grab the entire row.
Now, imagine that being looped five times if none of the users above had the zip codes filled in. That’s one query for each person. Imagine it being scaled higher? We have a potential problem here.
There are two solutions. Theeasy way and then modifying Doctrine itself.
The first solution is to use HYDRATE_ARRAY or HYDRATE_SCALAR. These methods can not perform lazy field loading due to the fact that the hydrator binds the results to an array which is not double checked when outputted (such as the case for objects).
The next solution (and the method I chose) was to modify Doctrine’s coding itself, as there were cases in which objects were necessary. I basically choose to turn lazy-loading off by default, and then added a switch to enable and disable the feature in the core code of Doctrine. The affected files were: /Doctrine.php, doctrine/Configurable.php, and doctrine/Record.php.
Like I said, by default lazy loading is turned off, so if you need it (such as in the case of a plugin), you can turn it on with:
//on
Doctrine_Manager::getInstance()->
setAttribute('lazy_field_loading', true);
//off
Doctrine_Manager::getInstance()->
setAttribute('lazy_field_loading', false);
The modified files are here:
Doctrine Lazy Loading 1.1.2
That’s pretty much a wrap for that. If anyone wants specifics on what I changed, feel free to ask.
