Loading up models is one of the most resource intensive operations CakePHP does — checking the schema, initializing behaviors, binding associations — and it’s only made worse by developers who insist that they “absolutely need these six models for every single request” or “these have to be in AppController, I use them everywhere!”
Some camps argue that you should explicitly load your models just before actually using them while others argue that the overhead is negligible and to not worry about it. I’m inclined to lean towards the former but I really hate seeing things like this…
$this->loadModel('Post');
$posts = $this->Post->find('all');
}
function view($id = '') {
$this->loadModel('Post');
$post = $this->Post->read(null, $id);
}
Like I mentioned earlier, I appreciate what this is accomplishing, I just hate the repetition and the forced, manual nature of it. Doesn’t jive with what I think CakePHP is all about. Luckily, there’s a PHP magic method that will let us forget about all these issues, __get() — here’s the function from my AppController.
* Catches requests for class members that are unset or not visible to
* the callee. We check to see if they're asking for a known model and,
* if they are, will load it up for them.
* @param string $variable
* @return mixed
* @access public
*/
public function __get($variable) {
if (!isset($this->models)) {
// We don't want to get this array for every request, store it
$this->models = Configure::listObjects('model');
}
// Is it a model we're trying to access?
if (in_array($variable, $this->models)) {
// It is! Lets load it up and return the model...
$this->loadModel($variable);
return $this->$variable;
}
}
The __get() method is called everytime you try to access a non-existant variable. In this method we’re setting
which returns an array of all models that CakePHP knows about, to our $this->models variable. We store it there so that we don’t have to keep getting the same array.
We then check if the $variable you’re asking for happens to be a model we know about by using…
– if it is we do the usual song and dance of loading the model up with…
and last but not least, we return the newly loaded model for use.
Now you can use any model in any controller without pre-loading them or cluttering your code up with initialization methods. Just write your code as though you had already loaded the model. It’s important to note that future requests for the same model will skip the __get() method because we’ve already loaded it up for you.
Enjoy!
Update…
Many thanks to ADmad and Ceeram for catching a snafu with the above code and some of the newer versions of CakePHP. It would seem that a pass by reference in one of the core files causes the whole thing to goto hell — unfortunate really.
As luck would have it though ADmad tossed together a patch to adjust the file in question and solve the issue. While this does break the cardinal rule of “Never, ever change the core” I’ll leave the moral decisions up to you.
index 4df0b12..84d1a1b 100644
--- a/cake/libs/controller/component.php
+++ b/cake/libs/controller/component.php
@@ -244,12 +244,16 @@ class Component extends Object {
}
} else {
if ($componentCn === 'SessionComponent') {
- $object->{$component} =& new $componentCn($base);
+ $this->_loaded[$component] = new $componentCn($base);
} else {
- $object->{$component} =& new $componentCn();
+ $this->_loaded[$component] = new $componentCn();
+ }
+ if (PHP5) {
+ $object->{$component} = $this->_loaded[$component];
+ } else {
+ $object->{$component} =& $this->_loaded[$component];
}
$object->{$component}->enabled = true;
- $this->_loaded[$component] =& $object->{$component};
if (!empty($config)) {
$this->__settings[$component] = $config;
}
Author: deadalnix
March 30th, 2010
at 5:26 pm
Holly crap !
That’s so simple and in the same way so effective ! Adopted
Author: Richard@Home
March 31st, 2010
at 2:09 am
That’s a really slick solution. Wonder why It’s not in core?
Also worth mentioning, you could add a $uses = array(); to your AppController so no models are loaded by default.
Author: ob
March 31st, 2010
at 4:37 am
this is a perfect addition to my application, thank you
Author: cargan
March 31st, 2010
at 7:29 am
Looks very promising. Would be nice to see benchmark stats of usual loading and this advanced ‘lazy’ models loading
Author: Joe
March 31st, 2010
at 8:09 am
@cargan
The real benefit comes from the fact that you’re loading only what you require, there is nothing “faster” about doing it this way, just a lot more convenient and less likely to load up models you don’t need.
Author: Joe
March 31st, 2010
at 12:24 pm
@Richard@Home
It’s probably not in the core because CakePHP supports PHP4 and PHP5 while __get() only works on PHP5
Author: Jose Diaz-Gonzalez
April 1st, 2010
at 11:13 am
How about plugin Auto-loading? Could this same trick work for Models in some way as well?
Author: Joe
April 1st, 2010
at 1:52 pm
You can, it’s just a bit trickier and more involved. Basically you have to tell the application that you want to add your plugin’s models directories as searchable for model loading. I’m not sure if this will do that perfectly so you may have to tweak this. Put this in before the __get() method.
foreach (App::path('plugins') as $path) {
$folder = new Folder($path);
// Loop over each plugin folder we found and add the models dir
foreach (array_shift($folder->read()) as $plugin) {
$paths[] = $path . $plugin . DS . 'models' .DS;
}
}
// Add any plugin model directories we found to App
App::build(array('models' => $paths));
Author: Tweets that mention Dynamic model inclusion Joe Beeson -- Topsy.com
April 2nd, 2010
at 8:20 am
[...] This post was mentioned on Twitter by cakephper, Joe Beeson. Joe Beeson said: Dynamic model inclusion for #CakePHP – http://tinyurl.com/y8cat47 [...]
Author: CakePHP : signets remarquables du 30/03/2010 au 13/04/2010 | Cherry on the...
April 13th, 2010
at 10:01 am
[...] Dynamic model inclusion [...]