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…
function index() {
$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 [cci lang="php"]Configure::listObject(‘model’)[/cc] 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… [cci lang="php"]in_array($variable, $this->models)[/cc] — if it is we do the usual song and dance of loading the model up with… [cci lang="php"]$this->loadModel($variable);[/cc] 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.
[cc lang="diff]
diff –git a/cake/libs/controller/component.php b/cake/libs/controller/component.php
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;
}
[/cc]