On The Fly Model Chains With CakePHP

Posted by Matt on Fri, Apr 17 2009

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.

Posted in CakePHP

21 Comments

Mariano Iglesias said on Apr 17, 2009
"Once you go Containable you never go back"... Damn, that makes me feel dirty... Sexy dirty...

Oh Matt... you always do it to me ;)
mattweg said on Apr 17, 2009
Matt,
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.
Jonah Turnquist said on Apr 17, 2009
The Yii Framework does this already -_-

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)
Matt said on Apr 17, 2009
I think __get and __set both work in PHP4. I needed __isset to make this work without changing Containable. __isset is PHP 5.1 or above.
Mariano Iglesias said on Apr 17, 2009
@Jonah: And Yii seems like yet another pretend framework trying to pose as a real framework
Miles J said on Apr 17, 2009
@Jonah - CakePHP 1.3 or 2.0 (dont remember which one) will be pure PHP 5, dont worry.
primeminister said on Apr 17, 2009
He Matt,
Really nice approach! Is this what they are calling 'lazy loading' of models?
Matt said on Apr 17, 2009
This is different then Rafael Bandeira's Lazy Loader. I'm not sure which code the term "lazy loading" fits better.
Rafael Bandeira said on Apr 19, 2009
lazy load pattern is much more covered here. That's the real lazy loading. Kudos for you man, you rocked it hard.

What LazyLoader does is like a fake lazy loading mixed with a fake proxy... so yeap, I'm pretty much full of shit.

regards
SayB said on Apr 17, 2009
O this is cool ... and just an idea that you could use some additional attributes within the models where you could define the associated models you'll always need and load them no matter what. Sounds pretty simple to do, though I'll be trying this within the weekend. Great concept by the way, Kudos!
Dooltaz said on Apr 17, 2009
This is a neat concept and it gives me a few ideas on optimization.
Felix Geisendörfer said on Apr 18, 2009
Wow! I thought about this before and always suspected it would cause horrible side affects on more complex applications.

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 ; )
Tim Koschuetzki said on Apr 19, 2009
Works great Matt on http://flashfun247.com with an average speed increase of 100ms. :] Well done!
ADmad said on Apr 19, 2009
There's some saved code on cake bin which does something similar http://bin.cakephp.org/saved/41000 Someone can compare :)
Matt said on Apr 20, 2009
Also this one: http://bin.cakephp.org/saved/39855, which is very similar, but dated earlier. I like the way both handle stopping the automatic model creation without destroying the definitions. This would fix the problem Tim mentioned below.
Tim Koschuetzki said on Apr 19, 2009
Okay found one problem with it: Automatic counter caches do not increase, but that can be easily fixed.
Frobozz said on Apr 25, 2009
I found error after implementing of this code: HABTM tags don't work.

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.
Matt said on Apr 26, 2009
Thanks. I'm going to mess with the original code to fix some of the other bugs mentioned above. I'll take a look at his one too.
Masom said on Apr 27, 2009
Same problem here.

Project HABTM Tags, when editing/saving project, tags are lost. The model never get associated. :/
Jose Diaz-Gonzalez said on Apr 29, 2009
Your code has been destroyed and replaced with an unknown page on github. Looks like your gist was deleted.
Matt said on Apr 29, 2009
I've been re-organizing my GitHub code. It is now at http://github.com/mcurry/lazy_loader/tree/master

Add new comment