Friday, January 16, 2009

Four Ways To Retrieve Model Data

There are a number of ways (in addition to direct model access from a controller) to retrieve data from a model. The type of method you use depends on what you are attempting to do. Here is what I've discovered.

$uses array

As you recall, the standard way a controller defines its models is by using the variable $uses.

$uses = array('Post','Comment');

This works fine for most cases, but what happens if one action in your controller needs to use the UserNotes Model, but no other actions in the controller needs it. One way to solve this would be to simply throw it into the $uses array().

$uses = array('Post','Comment','UserNote');

Now in your controller action you can call $this->UserNote->find... to get the data you want to get.


ClassRegistry::init

Recently, after browsing through the regular CakePHP blogs, I came across an article about building a dashboard in CakePHP. This article introduced the available function called ClassRegistry::init(). I found it to be an interesting addition to my growing Cake PHP toolset. ClassRegistry::init() is the actual method that a controller uses to load its models (you can find the call in the cake controller class in the function loadModel).

When to use $uses and when to use ClassRegistry::init

There may be times when you do not want to use the $uses array. In the above example, UserNotes will be loaded for all of the actions (even the ones that never call it). This is a problem because the more models that that single action needs, the more strain it will put on all the other actions. The question of when to use ClassRegistry::init is: Are you willing to have all of the other actions suffer from the performance hit of loading unused models?

This is where ClassRegistry::init() comes in to play. ClassRegistry::init() will load a model directly, skipping the standard model caching, from within the action. This means only the action you are running at that moment will have UserNotes loaded. All of the other actions in that controller will not load that model.

function getUserComments() {
...
$userNote = ClassRegistry::init('UserNote');
$userNotes = $userNote->getUserNotes($user_id);
...
}

When should you use ClassRegistry::init?
  1. When you have an action that calls models that other actions do not need.
  2. When you need to retrieve unrelated model data.
  3. When you want to skip model Caching and access the model directly.
Model Relationships

Many times both of the previous methods can be avoided because of natural model linking. Remember, when you define a model, you also setup relationships. The controller/action can use those relationships to retrieve data, as seen in Mark Story's Blog post.

If you are requesting information from a joined table within the relational chain, calling models through their relationships would be the best methodology.

$uses = array('User');
...
$this->User->Notes->find('all');

Although this method is limited, it is powerful, and it prevents redundency. By default a model will load the models it is associated with. So when you want data from an associated table, don't bother defining it in the $uses array, because Cake will automagically load that model and make it available through your current model. The key to making this work effectively is good database design.

requestAction() a Last Resort


The tackiest way to retrieve model data is to use requestAction. The reason for this is because of how Cake works. As you can see from the diagram, the dispatcher determines the route and then loads a single controller along with its models. Using a request action will cause the dispatcher to run again (possibly re-running routes) and then loading your additional controller, along with its models. This can cause a performance hit without caching.

Inside your Users controller you have an action called show_notes(). show_notes needs to access the notes table and retrieve a list of notes.

// Users Controller
function show_notes() {
...
$notes = $this->requestAction('/notes/get_notes/'.$user_id);

}
// Notes Controller
function get_notes($user_id) {
return $this->findByUserId($user_id);
}

There are a number of problems with this type of design.
  1. Additional server load for every request, which may cause bottlenecks.
  2. It goes against the "fat model, skinny controller" concept
  3. You are redefining the controller to do what the models are supposed to do.
Summary

Put simply, there are plenty of options to retrieve model data. The method that you use truly depends on your specific problem that you are addressing.

No comments: