Fork me on GitHub

Pagination Caching With CakePHP

Pagination Caching With CakePHP

A site i was working with was using pagination quite extensively with lots of records and associations. This was producing unnecessary high load on the database and wait times on the production site. Caching had to be done. Unlike normal returned data from finds etc. Paginated data can not be cached as easily, as the paginate method needs to be called to generate the pagination numbers etc. So what was done was to do a custom pagination query with cache built in. So in your app_model.php file add in:

function paginate ($conditions, $fields, $order, $limit, $page = 1, $recursive = null, $extra = array()) {
		$args = func_get_args();
		$uniqueCacheId = '';
		foreach ($args as $arg) {
			$uniqueCacheId .= serialize($arg);
		}
		if (!empty($extra['contain'])) {
			$contain = $extra['contain'];	
		}
		$uniqueCacheId = md5($uniqueCacheId);
		$pagination = Cache::read('pagination-'.$this->alias.'-'.$uniqueCacheId, 'paginate_cache');
		if (empty($pagination)) {
			$pagination = $this->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive', 'group', 'contain'));
			Cache::write('pagination-'.$this->alias.'-'.$uniqueCacheId, $pagination, 'paginate_cache');
		}
		return $pagination;
	}
	
	function paginateCount ($conditions = null, $recursive = 0, $extra = array()) {
		$args = func_get_args();
		$uniqueCacheId = '';
		foreach ($args as $arg) {
			$uniqueCacheId .= serialize($arg);
		}
		$uniqueCacheId = md5($uniqueCacheId);
		if (!empty($extra['contain'])) {
			$contain = $extra['contain'];	
		}
		
		$paginationcount = Cache::read('paginationcount-'.$this->alias.'-'.$uniqueCacheId, 'paginate_cache');
		if (empty($paginationcount)) {
			$paginationcount = $this->find('count', compact('conditions', 'contain', 'recursive'));
			Cache::write('paginationcount-'.$this->alias.'-'.$uniqueCacheId, $paginationcount, 'paginate_cache');
		}
		return $paginationcount;
	}

This will then take over from any paginate calls and generate a cached version of the dataset for the paginated items and the pagination controls, unique to each page and query set. I am in the process of trying to convert this over to a behaviour, will post up if i get a chance to complete it.

Of course you need to specify the caching rule in core.php, something like:

Cache::config('paginate_cache', array(
	    'engine'		=> 'File',
	    'path'		=> CACHE .'sql'. DS,
	    'serialize'	=> true,
			'duration' => '+1 hour',
	));

Posted by voidet

Categorised under CakePHP
Bookmark the permalink or leave a trackback.

20 Comments

  1. Thanks Bigtime!!! I’ve been searching a solution to cache paginated queries! This seems to work great!

    February 25, 2010 @ 1:56 pm
  2. anagram

    it doesn’t work on my app … it’s broken when a pagination use a containable

    any workaround for this issue? Thx

    March 10, 2010 @ 1:39 am
  3. VoiDeT

    What exactly is broken?
    I’ve used this on my apps and the pagination caches fine with contains, as you can see it is included in the finds. What you might have to do is included the extra array into the unique cache id, but i’d be surprised if you’re getting conflicting caches.

    Anagram please explain what is going on.

    March 10, 2010 @ 9:28 am
  4. yusuf

    i m also have used this to cache the pagination (thanks for sollution :) ), but i am get a problem with $paginator (prev/next),

    March 30, 2010 @ 7:47 pm
  5. daniel

    thanks a lot for the code.
    just one question: how can I temporarily disable the pagination-caching?
    I got some views in the backend where I need fresh data.

    April 10, 2010 @ 2:07 am
    • VoiDeT

      Hey Daniel,

      I would simply use the extras parameter or add another parameter that would accept a cache disabled test, then i would just use that to turn the cache off or on in the method.

      Let me know if you need help to achieve this and i can post a modified version.

      April 10, 2010 @ 11:00 am
  6. daniel

    Hi,

    thanks for the answer. That’s exactly what I did yesterday and it works perfectly.
    My problem was that I first tried to put the parameter in the paginate-call and not in the controllers paginate variable.

    April 10, 2010 @ 6:08 pm
  7. Hi,

    thanks for the answer. That’s exactly what I did yesterday and it works perfectly.
    My problem was that I first tried to put the parameter in the paginate-call and not in the controllers paginate variable.

    May 20, 2010 @ 12:10 am
  8. Floris

    To make sure the custom paginate function also works with Contain:

    On line 13; remove ‘recursive’.

    When using Containable, you most likely defined $recursive = -1; in your AppModel. The function compact on line 13 uses this variable, wich would mean, recursive is passed with the find function on line 13 and there fore, you explicitly ask for no recursion.

    May 25, 2010 @ 11:28 pm
  9. i m also have used this to cache the pagination (thanks for sollution :) ), but i am get a problem with $paginator (prev/next),

    June 23, 2010 @ 11:53 pm
  10. Great tutorial, just one question… How do I reset the cache using an afterSave method?

    August 24, 2010 @ 5:53 pm
    • VoiDeT

      You can use Cache::delete(‘paginate_cache_unique_id’); in your afterSave() method to clear out the cache for the pagination. Just change the key to whatever you used to generate your pagination cache with. Or if you want to flush all cache for pagination then you can use the Cache::clear(expiration_check, config_name) method :)

      August 24, 2010 @ 9:12 pm
  11. We often use the ‘joins’ parameter in our find options. If you want those to be used in the code above you need to add to each function:


    if(!empty($extra['joins'])){
    $joins = $extra['joins'];
    }

    Just after the section checking that $extra['contain'] is not empty and then add the ‘joins’ to the find like so:


    $paginationcount = $this->find('count', compact('conditions', 'contain', 'recursive', 'joins'));

    and:


    $pagination = $this->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive', 'group', 'contain', 'joins'));

    Great article, keep it up.

    October 9, 2010 @ 12:46 am
  12. Сould you please post a code for afterSave() method to clear out pagination cache?
    We should use Cache::delete(‘paginate_cache_unique_id’);
    But I can’t find out how to get the value for paginate_cache_id. Cos it’s dynamic. Any ideas on solving this?
    And one more question on temporarily disabling the pagination-caching.

    I would simply use the extras parameter or add another parameter that would accept a cache disabled test, then i would just use that to turn the cache off or on in the method.

    Let me know if you need help to achieve this and i can post a modified version.
    Could you post it please? Can’t find out how to add this extra paramater from controller.
    $this->paginate('Post.Comment', array('Post.id'=>$id)); Where should I call for “no caching mode”?

    November 3, 2010 @ 5:59 am
  13. You, sir, are an absolute pro!!
    This is one of those simple things that ‘just works’, but helps so much with performance.

    Did you ever succeed in turning this into a behaviour?

    February 4, 2011 @ 8:15 pm
  14. VoiDeT

    Hey Ollie!
    Thanks for the comments and great idea. I will look into moving this into a behaviour. But AppModel is pretty easy as it stands! I will update this page when I do some enhancements, also keep an eye on my GitHub page.

    February 4, 2011 @ 9:21 pm
  15. Hi there,
    the code still not works with “containable” on my site. I have the 3 models: A,B,C. A belongs to B ,B belongs to C. It works great when paginate A with some B fields but when I need query some fields from C, it return nothing :( How to fix this?

    April 8, 2011 @ 7:43 pm
  16. Just for the record, it would be impossible to turn this into a behavior because the Controller class does a “method_exists” on the object (usually a Model) that is being paginated. Since behaviors do not open classes as in rails, you would never get this to occur.

    The proper way would likely be to override the AppController::paginate() method, or even to separate Pagination into it’s own component, and then being able to set model/behavior callbacks from there.

    May 21, 2011 @ 2:51 pm
  17. This causes issue with virtual field. Any idea to solve?

    February 20, 2012 @ 8:05 pm
  18. Compatibility Cake 2.0 : just add “public” before the functions ;)
    Thanks :)

    June 4, 2013 @ 10:40 pm

2 Trackbacks

  1. [...] Pagination Caching With CakePHP [...]

  2. By CakePHP & Caching Until a Future Post • Jotlab on March 23, 2010 at 1:36 pm

    [...] for all find, pagination and pagination count methods with CakePHP. You can find a post on this here. The cache was expiring after every hour, however we failed to realise something. Our CMS system [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

or