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.
m4s0n501
  • http://www.kienpham.com Kien Pham

    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.

  • http://teknoid.wordpress.com teknoid

    @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.

  • http://www.gabardinestudios.com Darren

    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?

  • http://teknoid.wordpress.com teknoid

    @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 ;)

  • http://realm3.com Brian

    Nice trick, thanks for sharing.

  • http://teknoid.wordpress.com teknoid

    @Brian

    You’re welcome ;)

  • http://www.pagebakers.nl Eelco Wiersma

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

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

    cheers

  • http://teknoid.wordpress.com teknoid

    @Eelco Wiersma

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

  • holooli

    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?

  • http://teknoid.wordpress.com teknoid

    @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.

  • holooli

    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)?

  • http://teknoid.wordpress.com teknoid

    @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.

  • Martin

    @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.

  • rafaelbandeira3

    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.

  • http://richardathome.wordpress.com/ Richard@Home

    “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 :)

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

  • dooltaz

    applause!

  • http://teknoid.wordpress.com teknoid

    @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 ;)

  • http://www.wiltonsoftware.com Brett Wilton

    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 ?

  • http://teknoid.wordpress.com teknoid

    @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.

  • http://webdevelopment2.com/ Baz L

    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.

  • http://teknoid.wordpress.com teknoid

    @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 ;)

  • Pingback: CakePHP Digest Volume #4 :: PseudoCoder.com()

  • jason m

    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?

  • http://teknoid.wordpress.com teknoid

    @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 ;)

  • http://www.rrrtc.com Andrew

    “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

  • http://teknoid.wordpress.com teknoid

    @Andrew

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

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

    etc..

  • http://james.revillini.com james revillini

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

  • http://teknoid.wordpress.com teknoid

    @james revillini

    No problem. Glad it helped ;)

  • http://aditi.com Mohd Amjed

    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 ??

  • http://teknoid.wordpress.com teknoid

    @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).

  • Kym

    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

  • http://teknoid.wordpress.com teknoid

    @Kym

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

    $someData = $News->someMethod();

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

  • Kym

    Hey, works flawless, thanks :)

  • http://teknoid.wordpress.com teknoid

    @Kym

    No problem ;)

  • Dan

    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?

  • http://teknoid.wordpress.com teknoid

    @Dan

    No, notice the empty $uses array.

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

    • Dan

      Thanks. I am new to cake. Still trying to get a grasp how everything comes together.

  • http://pinoytech.org Thorpe Obazee

    Thanks for the post. This ClassRegistry thing is useful.

  • http://teknoid.wordpress.com teknoid

    @Thorpe Obazee

    Nice good to hear.

  • Gunnar Oledal

    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.

  • http://teknoid.wordpress.com teknoid

    @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())

  • korcan

    I tried to add some of the above code to the docs and got denied, here is where I was pointed as some of the statements made were not 100% accurate :)

    http://groups.google.com/group/cake-php/msg/794c451038c0c798?pli=1

    App::import() only includes the file. So you would new to create a new
    instance every time. This is not recommended
    ClassRegistry::init() loads the file, adds the instance to the a
    object map and returns the instance. This is an easy and convenient
    way to access models.
    Controller::loadModel(); Uses ClassRegistry::init() adds the model to
    a property of the controller and also allows persistModel to be
    enabled.
    While you “can” do any of these things, you should ask yourself why
    you are creating dependencies on models that are not natural to the
    controller. If you “have” to do use any of these, then I would do so
    in reverse order of the way i described them. IE, Controller::loadModel
    () then CR::init() and actually I never use App::import() for models.

  • teknoid

    @korcan

    Is that a question or a statement or a rant? :)

    I guess to answer why CR::init() was used here, the reasons are pretty simple. We only use each model once and I wanted to show off method chaining, which is only available by proceeding as above.

    Hope this helps.
    Cheers.

  • http://www.paradigm-development.net/ Arnold Almeida

    yepo.

    ClassRegistry::init() is the way to go over $uses.

    From what i understand.

    Say we have
    Controller
    ClassRegistry::init(‘User’)->foo();
    ClassRegistry::init(‘User’)->bar();

    View
    ClassRegistry::init(‘User’)->foo();
    ClassRegistry::init(‘User’)->bar();

    While it may “look” inefficient the User object (along with its assoc) are cached and only initialized once.

  • teknoid

    @Arnold Almeida

    I would not recommend this approach from the view. Because you are essentially breaking MVC and making your app an… MV.

  • n4than

    Hi, but If I want to call some function stored in a controller? For example

    $Temp2 = ClassRegistry::init(‘User’);
    $data2 = $Temp2->view(1);

    This isn’t work cause the function view is inside the users_controller.
    So if I want from my dashboards_controller call the function view from users_controller what I have to do ?
    Thanks for your good post.

  • teknoid

    @n4than

    That’s why one of the most important rules of MVC is to keep your logic in the Models.

    You have an alternative in requestAction(), but that makes me question your app design.

  • http://mobedio.com chustar

    This is a pretty cool idea. I don’t think I’m going to be going around stripping uses() out of my application but it’ll be good to keep in mind going forward. Thanks. And good job being available to readers.

  • teknoid

    @chustar

    Thanks.

  • bhuvana

    thanks for sharing.. its useful..

  • Kerry Kim Russo

    Novice here. I’m currently doing a project where I only need a single ‘dashboard’-like page where all the interactions will be done through javascript/AJAX. While the above method worked to access the models, I found that it didn’t allow me to use properties like virtualFields or displayField. Is that correct or am I doing something wrong?

    Thanks!

  • Pingback: How to build a “dashboard” for your application in CakePHP | nuts and bolts of cakephp | SHOCM()

  • Pingback: CakePHP Dashboard « 静水深流 || Still waters run deep()