Limit the models used in find operations
Often when issuing a find, findAll or other find*-variety there's way too much data returned which puts unneccessary overhead to your database server or it might pose a security risk when some crucial fields like passwords are available to the view designers just because the users-table was included in your findall...
For example you've got a User model which hasMany Article, hasOne ExtendedProfile.
If you want to get the User with id 1 and his ExtendedProfile in one step, for example using
You'll also get all the associated Articles which you might not be interested at that stage and which also might put unnecessary strain to your mysql server. So you might only want to fetch the data from User and ExtendedProfile.
There's the famous unbindAll() function from CrazyLegs and Oth at http://othy.wordpress.com/tag/uncategorized/ which lets you ignore certain relations for the next query.
But this one is tedious to use because the whole relationship needs to be provided in the argument each time.
So I've modified that function to accept just a list of Model names as parameters which will be included in the find. All not-mentioned models are ignored (at least at recursion <= 1).
Important: You need to call useModel() before every find* function (if needed) because after the find has executed, the relationships are automatically restored to the default.
Example usage from a controller: Get only the data from the User and the ExtendedProfile, but ignore the Articles this user currently has.
Instead of an array of relation names a single relation name can be passed as string to the function.
The name used as argument(s) to useModel() is the name of the relation defined in your hasOne/hasMany/belongsTo/HABTM configuration. This usually corresponds to the model name but it can be different, for example if there is more than one relation between two models.
If you need to do a find with recursion greater than 1 and want to restrict the usage of the relationships in the other models you can use a useModel() call for each Model used.
For example:
Place this in your /app/app_model.php to make it available everywhere
If you want to get the User with id 1 and his ExtendedProfile in one step, for example using
PHP Snippet:
Download code
<?php
$this->User->read(null, '1');
?>
You'll also get all the associated Articles which you might not be interested at that stage and which also might put unnecessary strain to your mysql server. So you might only want to fetch the data from User and ExtendedProfile.
There's the famous unbindAll() function from CrazyLegs and Oth at http://othy.wordpress.com/tag/uncategorized/ which lets you ignore certain relations for the next query.
But this one is tedious to use because the whole relationship needs to be provided in the argument each time.
So I've modified that function to accept just a list of Model names as parameters which will be included in the find. All not-mentioned models are ignored (at least at recursion <= 1).
Important: You need to call useModel() before every find* function (if needed) because after the find has executed, the relationships are automatically restored to the default.
Example usage from a controller: Get only the data from the User and the ExtendedProfile, but ignore the Articles this user currently has.
PHP Snippet:
Download code
<?php
$this->User->useModel( array('ExtendedProfile') );
$this->User->read(null, '1'); // Find the entry with id=1
?>
Instead of an array of relation names a single relation name can be passed as string to the function.
The name used as argument(s) to useModel() is the name of the relation defined in your hasOne/hasMany/belongsTo/HABTM configuration. This usually corresponds to the model name but it can be different, for example if there is more than one relation between two models.
If you need to do a find with recursion greater than 1 and want to restrict the usage of the relationships in the other models you can use a useModel() call for each Model used.
For example:
PHP Snippet:
Download code
<?php
$this->User->useModel( array("ExtendedProfile", "Article") );
$this->User->Article->useModel(); // To stop recursion there if Article has further relations for recursion > 1
?>
Place this in your /app/app_model.php to make it available everywhere
Model Class:
Download code
<?php
function useModel($params = array())
{
if( !is_array($params) )
$params = array($params);
$classname = get_class($this); // for debug output
foreach($this->__associations as $ass)
{
if(!empty($this->{$ass}))
{
// This model has an association '$ass' defined (like 'hasMany', ...)
$this->__backAssociation[$ass] = $this->{$ass};
foreach($this->{$ass} as $model => $detail)
{
if(!in_array($model,$params))
{
//debug("Ignoring association $classname <i>$ass</i> $model... ");
$this->__backAssociation = array_merge($this->__backAssociation, $this->{$ass});
unset($this->{$ass}[$model]);
}
}
}
}
return true;
}
?>
Comments
Comment
1 Even Better
First, I question the necessity of the array_merge line. It doesn't seem useful, and nothing has broken since I commented it out.
Second, I occasionally find myself forgetting to wrap multiple association names in an array() call. Therefore, I changed the first few lines to:
function useModel() {
$params = func_get_args();
if (is_array($params[0])) $params = $params[0];
...
Comment
2 I needed a restoreModel function
The function I wrote is there, it works for me, hope it can be usefull ..
// usefull for optimizing queries
function restoreModel($params = array())
{
if( !is_array($params) )
$params = array($params);
if(empty($params))
return;
$classname = get_class($this); // for debug output
foreach($this->__backAssociation as $name => $ass)
{
if(!empty($ass) && in_array($name, $this->__associations) && isset($this->{$name}) )
{
foreach($ass as $model => $detail)
{
if(in_array($model,$params) && !in_array($model, $this->{$name}))
{
$this->{$name}[$model] = $detail;
unset($this->__backAssociation[$name][$model]);
if(isset($this->__backAssociation[$model]))
unset($this->__backAssociation[$model]);
}
}
}
}
return true;
}
?>