tags:

views:

33

answers:

3

Hi there,

I have a database with products organized by categories and subcategories. The thing is that I generate a menu by querying the categories table. Because categories are almost the same(they can change/add once a month or something), I don't think I have to query the database for each access of the web-page. Do you have a better idea for me?

Many thanks!

ps: I'm using the CakePHP framework, but your answer/idea doesn't have to relate to it. I just find about memcache

+1  A: 

You could write a script to query the database for available categories and write the output to a .php file, /inc/categories.php, which is used by your website.

This script is than ran by a cron job once a month or as many times as you please

Mark
If you did this you could run the update script whenever someone alters the categories.
Twelve47
This isn't a very Cake-y solution.
Daniel Wright
+1  A: 

Mark's answer is correct, and I like it for its simplicity. Just because it's not Cakey doesn't mean you should use it. I've caused myself more grief and time trying to fit something into a "cakey" format than I care to admit.

In rapid development, sometimes hacks are necessary.

That said, if you want to go the more Cake like route, I suggest you check out query caching. A good article about it is at http://www.endyourif.com/caching-queries-in-cakephp/

Travis Leleu
+1  A: 

I think in this instance I would use a combination of action caching and view caching. I won't go into the vagaries of different caching engines, just the basics to solve your problem.

The first thing you need to do is turn caching on. In app/config/core.php, you'll need to make a couple changes. First, comment out the Cache.disable line:

//Configure::write('Cache.disable', true);

Second, uncomment the Cache.check line:

Configure::write('Cache.check', true);

Finally, you'll need to make CacheHelper available to all views, so add it to the $helpers member definition in app/app_controller.php:

class AppController extends Controller
{
    var $helpers = array('Cache');
}

Next, we'll set up the view element that contains your category menu (doing the view stuff before the controller stuff may seem a little backwards, but bear with me -- it'll make sense). Putting the menu in a view element makes caching it a bit simpler than trying to selectively designate parts of a larger view or layout as uncached with <cake:no-cache></cake:no-cache> tags.

Here's a sample view element, rendering the category menu in a simple unordered list (<ul>):

<?php
    $menuItems = $this->requestAction(array('controller'=>'categories','action'=>'menu'));

    echo $html->nestedList( $menuItems, 'ul' );
?>

You may have been told by one or more people in the past that requestAction is evil, as it initiates a whole new dispatch. This is true. You should avoid requestAction like the plague, except when you're doing this type of view-caching, in which case it's the best way to go.

We'll store this new view element as app/views/elements/category_menu.ctp. The requestAction invokes the menu action in CategoriesController. Here's what CategoriesController looks like, then I'll go through it bit by bit:

class CategoriesController extends AppController
{
    var $cacheAction = array(
        'menu/' => '1 day'
    );

    function menu()
    {
        return $this->Category->find('list', array(
            'fields' => array('Category.id','Category.label'),
            'order'  => array('Category.order')
        ));
    }
}

What this does:

  • The member definition for $cacheAction tells Cake that it should cache the results of the menu action for 1 day.
  • The menu method returns a one-dimensional array of categories, using Category.id as the keys, and Category.label as the values.

So, let's go over what we've got so far. We have a category_menu element, which requests the CategoriesController::menu action. When this request is received, Cake will check to see whether the result of that action has been cached, and hasn't expired. If the cached result is still valid, Cake will return it without invoking the menu method we defined. Otherwise, Cake will invoke the method, and will cache the result. The view element then renders the result as a nestedList.

All that remains, then, is to use the view element in other views. Here's a simple sample app/views/layouts/default.ctp:

<html>
[ snip... ]
<body>
<div class="category-menu">
    <?php echo $this->element('category_menu', array('cache'=>'1 day')); ?>
</div>
<div class="content">
     <p>Lorem ipsum sit dolor amet.</p>
</div>
</body
</html>

We use the familiar View::element to render the category_menu view element we created earlier, but we also tell Cake that we want it to render the view element once, then cache this rendered version, and hold onto it for a month. Any subsequent requests for that element, until a month elapses or we clear the cache, will deliver the pre-rendered view element. It won't even interpret app/views/elements/category_menu.ctp, much less invoke requestAction, or hit the database.

To ensure changes you make to Category model records are reflected immesiately, you can invoke the global function clearCache() in the create/update actions. One caveat about clearCache(): it's a bit of a shotgun approach: inelegant but effective. clearCache() wipes out the entire cache, rather than selectively clearing only the necessary views. It is a simple approach, however, and given you're talking about updating the categories monthly, having to regenerate the cache once a month is probably worth the diminished hassle of a more selective approach.

Very long answer! HTH

Daniel Wright