On The Fly Model Chains With CakePHP
This code has been completely re-worked. The latest is at http://github.com/mcurry/lazy_loader/tree/master. This post has more information about it.
Built For Speed
In yesterdays post I talked about how CakePHP builds a model chain of all the related models. The smart readers (which is really all of you guys. you're the best) probably realized that there is a lot of overhead in bulding the chain, especially for apps with lots of interlinked models. Wouldn't it be awesome if Cake only created the pieces of the chain as needed? Or what if some really awesome member of the community took the time to build such functionality and then shared it with everyone. You know, something like:
httpc://github.com/mcurry/lazy_loader/raw/5ea7dc8d0dd70ec945b33abaf8f67db940ee9764/app_model.php
What The Hell Is Going On Here?
Actually not that much. First we need to catch the constructor and remove all the associations. Wait. What? You crazy! Why would you do that? By removing all the associations before the parent constructor is called Cake thinks there are no related models and won't waste a bunch of time building the chain, when chances are you'll never use half of them. Don't worry, all the associations are first backed up in the __definedAssociations attribute.
What's the deal with the __loadAssociations attribute?
Some of the Cake core models don't play so well with this trick, so this attribute lists models that should have their chains loaded the old fashion way.
Great, So Now I Have A Model With No Chain. Thanks, Moron.
But wait. There's more. See the __get and __isset methods? Those are part of PHP5's overloading ability (Did I mention this was PHP 5.1 or better?). Whenever your code tries to reference a model in the chain that doesn't exist the __get method gets called, which in turn calls __connect to check if it's a valid association. If it is Cake's bind method is called and the model is added to the chain.
Your Code Sucks. It Doesn't Work With $recursive Set
Actually I didn't even check this. I just assumed it won't work. Really there isn't much reason to use $recursive in Cake 1.2. You know the saying: Once you go Containable you never go back. Did I mention this code works awesomely with Containable?
I Tried Your Code And It Broke My Shit
Yea, I expect that to happen. There are probably some incompatibilities with this. I think there is an issue with ACL and probably with other behaviors. It would be cool if you all could try it out and report back.
How Much Faster Is This?
Ahhh, no we get to The Question. Here we go:
Basic controller, no models:
Time per request - 145.636 ms
Calls to ClassRegistry::init - 0
Basic controller, one model, no chain:
Time per request - 142.817 ms
Calls to ClassRegistry::init - 1
Basic controller, one model, nine chained models:
Time per request - 244.533 ms
Calls to ClassRegistry::init - 19
Basic controller, one model, nine dislinked chained models:
Time per request - 162.905 ms
Calls to ClassRegistry::init - 1
Going from one unchained model to one model with a long chain added about 100ms (71% increase). Adding this code brought the number back pretty close to what it was before with just the one unchained model.
How Do I Use It
Pretty simple. Just drop this code into your /app/app_model.php.
One More Thing
I'm slipping this in at the end, because I figure anyone whose stuck around this long would be interested. I'm looking for CakePHP people, who like to read, and want to help me with a project. The bold text should give a pretty good hint as to what the project is. Send me an email at matt -at- pseudocoder.com if you're interested.

21 Comments
Oh Matt... you always do it to me ;)
I have some new code that has really complex relationships and a ton of data. Once we get the test cases done, I'll run some benchmarks. Might be a week or two though.
Great concept. You have come up with a way to use only the relationships you need at the time.
cake seems like old technology to me now, without any php 5 features (I believe these __get,__set methods are php 5, that's why it doesn't have it)
Really nice approach! Is this what they are calling 'lazy loading' of models?
What LazyLoader does is like a fake lazy loading mixed with a fake proxy... so yeap, I'm pretty much full of shit.
regards
Turns out I'm wrong. Both debuggable.com and another client project I just tested continued to work without any tweaking after applying the above patch. Speed increase however varied.
debuggable.com (~20 models) didn't benefit from this method at all. I guess we programmed it well ; ).
Another client project (~50 models) saw a nice 200 ms drop (20%) on pretty much all pages.
Great work matt. Btw. send me that reading material of yours ; )
Example.
I have Item and Group entities (item HABTM groups). Now I do something like this on Item editing page:
$form->input('Group', array('div' => false, 'label' => false, 'type' => 'select', 'options' => $groups, 'multiple' => 'multiple'));
It gives me
select id="GroupGroup" multiple="multiple" name="data[Group][Group][]"
in html.
After implementation of your code the same code gives different result in html.
Project HABTM Tags, when editing/saving project, tags are lost. The model never get associated. :/
Add new comment