How to build a “dashboard” for your application in CakePHP

As an addition to my recent post, I wanted to share a technique, which should allow you to pretty easily build a “dashboard” for your app.
The dashboard would basically grab some information from a few different models and display it all on one page.

First, let’s go over some assumptions…

  1. We are not going to use requestAction(), because it’s the “worst” (and absolutely the last-resort approach). It has it’s purposes, but we want to avoid it as much as possible. (I’m not going into detail as to why, because that has been covered many times in many many places, so you can just fire-up your trusty search engine to learn more).
  2. The models from which we’ll gather the information are absolutely not related to one another, if they are related… this is not the best approach. Remember, we don’t want to reload any models (objects), which CakePHP has already loaded for us.

So, keeping both of those things in mind, here’s what we’ve got:

Post model, with a method getTop();
News model, with a method getRecent();
Employee model, with a method getTopPerformers();
Product model, with a method getTopSellers();

(You are welcome to come up with your own methods or use your imagination as to what they might return).
Even though it’s hard to believe that we have no relationship between any of the above models, we’ll assume that to be true for the sake of this example…

We are going to build a Dashboard Controller, with a single action (for now), called index() it’s only purpose is to grab the info from all of the above models and “set” it for the view.

Here’s what it might look like:

<?php
    class DashboardController extends AppController {
          
          var $name = 'Dashboard';
          var $uses = array();
          
          function index () {
               $this->set('topPosts', ClassRegistry::init('Post')->getTop());
               $this->set('recentNews', ClassRegistry::init('News')->getRecent());
               $this->set('topEmployees', ClassRegistry::init('Employee')->getTopPerformers());
               $this->set('topSellingProducts', ClassRegistry::init('Product')->getTopSellers());
          }
    }
?>

And that’s really it. Now we have all of our Models’ info available for the view, and we can display it any way we’d like.

Just a few things to point out:

  • We are using ClassRegistry::init('ModelName') to load the relevant models, only once we need them. The init() method will automagically instantiate the model object.
  • We are avoiding the use of the $uses array(), although it could have been used just as well, but this way the models are loaded only when required, and if you have additional actions that might need some other set of models, there is no need to “overburden” your app by loading something extra, before it’s actually needed.
  • If you’d like to make this new dashboard into your actual homepage of the site, please see my other post on how to setup the appropriate route.
About these ads

42 Responses to How to build a “dashboard” for your application in CakePHP

  1. Kien Pham says:

    Thanks for your post. I usually use the $uses array() to load my Model in combination with the contain(). I guess ClassRegistry is a better approach.

  2. teknoid says:

    @Kien Pham

    No problem.
    It can be a better approach, depending on the situation. Also, while it is very important to use ‘contain’ whenever possible/required, Containable does not limit the objects that are loaded by your app, only the results of your find operations. So, while the two can be used in conjunction, they serve, in a sense, a different purpose.

  3. Darren says:

    First – thanks for this tip! Love the efficiency of it.

    Question: Is there any fundamental difference between $uses and loading models in an action with App::import?

  4. teknoid says:

    @Darren

    The main difference is that when you have models listed in $uses there is a very good chance that you are loading them (objects) on each request, while maybe only using one or two at time of the actual action (of course, that really depends on your app).
    Is that going to affect the performance of your app? In theory (again this is very generic assumption), $uses makes things a bit more bloated, the reality will most likely prove that you’ve got plenty of other bottlenecks before something like this becomes an issue.

    The most important thing is not to mis-use $uses by loading models like var $uses = array(‘Post’, ‘Comment’, ‘User’); when they are already related via associations.

    Now you’ve just re-loaded the models that are already available (loaded) via a chain like $this->Post->Comment->User… that’s the main mistake that people usually make with $uses.

    Well, hope that clarifies some things and… I hope you didn’t mean App::import() to load the models… ClassRegistry::init() is a lot better suited for that as seen in the example ;)

  5. Brian says:

    Nice trick, thanks for sharing.

  6. teknoid says:

    @Brian

    You’re welcome ;)

  7. Nice, I was using the same approach but with $uses.

    I’ll consider using this instead in the future :)

    cheers

  8. teknoid says:

    @Eelco Wiersma

    Nice, glad to hear it gave you some ideas :)

  9. holooli says:

    Thanks great tip, I always use requestAction :S

    BTW, if I need *the exact* action of another controller, do I have to repeat all the logic and model requests, or just use requestAction?

  10. teknoid says:

    @holooli

    What exactly does the action do?
    In majority (vast majority) of cases, such action is better offloaded to the model, and then accessed as such either from a controller’s action or another controller via model chain, or using the method described above.

  11. holooli says:

    I was thinking about it and yap you are right, I also did some googles and found your useful post:
    http://teknoid.wordpress.com/2008/08/20/dynamic-menus-without-requestaction-in-cakephp-12/

    This is great, so when do we use requsetAction (example please)?

  12. teknoid says:

    @holooli

    Never :)
    The best example I can think of is when you need to request some data from an element, which cannot talk directly to the model, so it needs requestAction() for that.
    Are there ways to get around it? Probably yes (well that really depends on your specific needs), but given that we have a bunch of techniques outlined here and many other places you should be able to come up with alternative methods.

  13. Martin says:

    @holooli

    One very good example of when requestAction is the only option (AFAIK) is when you are pulling data from plugins and your dashboard can’t know in advance what data is supposed to be presented.

    I shutter at the thought of the main application keeping a static lookup table of which models each installed plugin wants to present as a dashboard item… even more than at using requestAction. :)

    It might be possible to hack something together by always creating a model called “Pluginname” as with the controllers. But that is not something I have seen any official efforts towards yet.

  14. rafaelbandeira3 says:

    Actually, models are never reloaded if ClassRegistry is used: Models are used like singletone instances, and that’s the real deal about ClassRegistry. So using:
    ClassRegistry::init(‘Post’)->find(‘allThatIWant’);
    ClassRegistry::init(‘Post’)->find(‘allThatINeed’);
    won’t load/instantiate the Post model twice, the same for:
    ClassRegistry::init(‘Post’)->find(‘myNeeds’);
    ClassRegistry::init(‘PostComments’)->find(‘myOthersNeeds’); // PostComments is related to Post
    here, you are not loading/instantiating PostComments again.

    Anyway, I think that using $uses is much more readable, as it will show exactly what is going to be used within that class right in it’s definition.

  15. Richard@Home says:

    “Anyway, I think that using $uses is much more readable, as it will show exactly what is going to be used within that class right in it’s definition.”

    But what if your $uses contains classes that are only used in one action? This approach seems much more elegant as you can see which models are being used in each action.

    Thanks for the tip teknoid :)

  16. Pingback: CakePHP : signets remarquables du 11/12/2008 au 17/12/2008 | Cherry on the...

  17. dooltaz says:

    applause!

  18. teknoid says:

    @Martin

    Sounds like a good use for requestAction()

    @rafaelbandeira3

    Readable… probably true, more “application intensive” is also true… either way it’s an option, use the one that works best for you ;)

    @Richard@Home

    No problemo ;)

    @dooltaz

    Thanks ;)

  19. Brett Wilton says:

    Thanks for the example seems very nice.
    I was wondering how would you handle the requirement of lets say getTop() being requested by most controller actions ?
    I have not done any testing on this but does a cached element which calls requestAction() perform that badly ?

  20. teknoid says:

    @Brett Wilton

    The cached element is definitely going to perform “poorly” only on when it requests info using requestAction(). In the case of cached elements, I would say it is not a bad idea to do so. However, if your element’s content is truly dynamic, i.e. it constantly changes based on the heavy activity in the DB caching of the element itself may not be applicable.

    To deal with such an element you can use App Controller’s beforeFilter() method to try a similar approach as above.
    If you need to ensure that it’s executed only on a vast majority of actions and not a few others, you can do something like: if (!in_array($this->action, array(‘some’, ‘of_my’, ‘actiions’)) { //execute the code }. So it will get the data for your element, but only when the actions do not match the “restricted” ones in the above array.

  21. Baz L says:

    Ok, I haven’t been “Caking” much new stuff in the past months, so excuse this question, but did I miss something? ClassRegistry::init()?? What happened to App::Import?

    Just curious.

  22. teknoid says:

    @Baz L

    App::import() still works just fine, but for loading models it is recommended to use ClassRegistry::init().
    One benefit is that it will instantiate the model for you… So using it in the manner described above actually makes the code a little more concise.
    And you can always refer to the API to see the inner workings of the init() method ;)

  23. Pingback: CakePHP Digest Volume #4 :: PseudoCoder.com

  24. jason m says:

    Great post, thanks. After seeing your post I used the classRegistry::init in my model in an afterSave() method to save data in another model that had no db relations to link them together. When I first learned about requestAction, I kind of went crazy and really overused it and had to go back and move the request action code to the model to speed things up later. My question is: in terms of performance is it ok to use this classRegistry init in many different places (model, view, controller, or elsewhere), or should it be used sparingly?

  25. teknoid says:

    @jason m

    Glad the post was of some help.
    You are definitely improving your app by avoiding requestAction(). ClassRegistriy::init() is likely the best performing out-of-the-box option.
    It should not be used for related models, since any associated models are loaded already.
    And definitely not to be used in the views, otherwise you get MV instead of MVC ;)

  26. Andrew says:

    “if they are related… this is not the best approach.”

    I would like to set up a dashboard, my models are related. Could you help me find the “best” approach? Thanks

  27. teknoid says:

    @Andrew

    If the models are related you should leverage their relationships, i.e.:

    $this->User->Post->getTop();
    $this->User->Post->Comment->getRecent();

    etc..

  28. Thanks for sharing! It’s extactly what I need to do.

  29. teknoid says:

    @james revillini

    No problem. Glad it helped ;)

  30. Mohd Amjed says:

    hey, thankx for such an informative post and the trailing thread ..
    I have a quick question, i want to add articles feature,wanna put some dynamic content on the home page, is there any way to integrate wordpress or some rick text editor which can do this in cake ??

  31. teknoid says:

    @Mohd Amjed

    There is always is a way to accomplish that, unfortunately it cannot be summed up in few easy sentences. (Gotta slay a few dragons, before you get to the princess).

  32. Kym says:

    Hey, really nice topic, helped a lot..

    What if my Dashboard access the same model more than once? ie:

    function index() {
    $this->set(‘lastestNews’, ClassRegistry::init(‘News’)->getLastestNews());
    $this->set(‘spotlightNews’, ClassRegistry::init(‘News’)->getSpotlightNews());
    $this->set(‘coverages’, ClassRegistry::init(‘News’)->getCoverages());
    $this->set(‘interviews’, ClassRegistry::init(‘News’)->getInterviews());
    }

    I have more things to set for the dashboard but those 4 come from the same model, is that a problem to use ClassRegistry::init more than once? Should I do something else?

    Thanks in advance

  33. teknoid says:

    @Kym

    $News = ClassRegistry::init(‘News’);

    $someData = $News->someMethod();

    This way you re-use the instance of the same object.

  34. Kym says:

    Hey, works flawless, thanks :)

  35. teknoid says:

    @Kym

    No problem ;)

  36. Dan says:

    In this example, what would be in your dashboard table? If you have a DashboardController, doesn’t it mean you need to have a table?

  37. teknoid says:

    @Dan

    No, notice the empty $uses array.

    As explained, the only purpose of this controller is to gather data from some other models.

  38. Thanks for the post. This ClassRegistry thing is useful.

  39. teknoid says:

    @Thorpe Obazee

    Nice good to hear.

  40. Gunnar Oledal says:

    What if you want to sort the result among the different result sets (by date of course)? What’s the best practice? Example:

    Recent (sorted by date):
    John submitted a new image gallerie. (gallerie model)
    The article “cool stuff” has been submitted. (article model)
    Anna submitted a new image gallerie. (gallerie model)
    Basjmannen is back in town. (news model)

    Like the startpage on facebook.

  41. teknoid says:

    @Gunnar Oledal

    You’d need to combine the results from all models.
    It’s a little tricky, because for now cake returns results as arrays.

    Look at Set class for some useful methods (i.e. Set::combine() and Set::merge())

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 25 other followers

%d bloggers like this: