Community Documentation

v3 Knowledgebase

Implementing Cache

In phpfox 2.0.4 the feed module stressed the database to a point sometimes unbearable, in 2.0.5 it is barely noticeable. The solution was to cache feeds. We have learned plenty by seeing how Facebook does certain things, sometimes we would write a message to someone and it would show the notification but going to the messages section showed no new messages, until 5 minutes later. Sometimes it was the opposite, the whole "Top News" vs "Most Recent" is a caching trick.

In phpfox many items are cached by default, but if you are creating your own content or you just want to improve performance you can set up a cache control for your items.

Caching needs to be implemented in the functions that fetch items from the database or that update/insert/delete items from the database (and by "database" I mean the table that you are working on)

In this tutorial we will implement caching for the main Polls section as an example, the purpose is to show how to implement caching in general and not to encourage to edit core files.

A quick look at the controller shows us that the function used to get the polls is getPolls in the Poll service. There is a hook right at the beginning of that function but if we want to return something this wont work.
For the cache to be effective it must store everything that is fetched from the database and reliably return it the next time it is requested.
One way to do that is to name the cache file after the conditions that would give us the result set from the database, for example if the query is something like:
FROM a_table
WHERE a_field = 'a_value'

the name for the cache file could be "<module>_a_value", so we can easily look it up. The name is not very important as long as its unique.

In this function we see that the first query ($iCnt) depends on the conditions ($aCond), so we could save that query by caching it based on $aConds.
That would be ok, but in reality this count is very fast, and it wouldn't be much of an improvement.
So we keep looking and we see that the second query is more interesting ($aPolls), it depends on a constant and who is viewing the item, then on a couple of modules to be enabled and then is limited by page and size, much more interesting!
To do this we can build an array of checks, serialize it and md5 it. This will give us a consistent and trimmed name format for our polls.
But still, all we need to do is build the name for the cache properly so we can reliably cache it.
All that would look like this:
/* Start of caching for polls, put all the checks in one array */
$aCache = array(
'aCond' => (!is_array($aCond)) ? array($aCond) : $aCond,
'iUser' => $iUser,
'iPage' => $iPage,
'iPageSize' => $iPageSize,
'privacy' => (defined('PHPFOX_IS_USER_PROFILE') && Phpfox::getUserId() && Phpfox::isModule('privacy')) ? 0,
'friend' => (defined('PHPFOX_IS_USER_PROFILE') && Phpfox::getUserId() && Phpfox::isModule('friend')) ? 0,
/* Make a name for the cache */
$sCacheId $this->cache()->set('poll_front_page_' md5(serialize($aCache))); 

Notice how we summed all the checks to get $aPolls into this one array. Then we standardize the name.

We have only came up with a name for the cache, we still need to check the cache and save the results from the database into it.
At this moment we can check if we have cached the polls or not, of course we have not saved anything into cache so we won't get a result but its ok because once we save into cache we will start getting it from cache right away.
so right after we set the name we check it like this:
if (($aPolls $this->cache()->skipClose(true)->get($sCacheId)))

In one line we ask our caching layer if we have cached this query and ask it to not remove the caching name (we may need it later), and if we find it cached just return it.
The get() function accepts a second parameter to specify when is a cached object expired, for example
/* Only get cached objects saved 60 minutes or less ago*/
if (($aPolls $this->cache()->skipClose(true)->get($sCacheId, (60)))) 

Now if we go to the end of the function there we find the original return:
return array($iCnt$aPolls2); 

right before it we can save the polls fetched and processed into our cache file:
$this->cache()->save($sCacheId, array($iCnt$aPolls2)); 

And just with that you have implemented caching.
If you check (with debug mode level 3) you will notice how you are saving 3 queries now but also you are saving the processing of those result sets.
One caveat with this approach is that the answers are shuffled before being returned, so after caching the shuffling of answers wont happen, it will shuffle them once and return that thereafter. Fixing it is beyond the scope of this tutorial but it can be easily done.

The second part of caching is when to delete it, we need to delete this cache every time a Poll is added or deleted, and every time a poll is updated (if it changes the answers, the title or description).
If you set an expire time above you could discard this part as they will eventually get the updated polls, removing the cache will ensure they get the updated polls as soon as possible, this also means that the queries to database will happen as soon as possible so it is up to you to delete the cache or not. The less frequently a cache is deleted the more improvement you will see.

Deleting cache is very simple, as we can delete cache files by prefix and since our cache names all start with "poll_front_page_" that makes it easy. This is the code to delete all the cache objects we may create from the above code:

Simple as that.

So we know that all data modification functions are in the process services, so we open the process service in the poll module (/module/poll/include/service/process.class.php) and look at the end of the add function:
(($sPlugin Phpfox_Plugin::get('poll.service_process_add_end')) ? eval($sPlugin) : false);
return array(

We can use this hook to insert the cache removing code: poll.service_process_add_end

in this same file we have the function updateAnswer, unfortunately this one does not have a hook, but in here you can also add the code to remove the cache:

That concludes today's tutorial, I hope it was clear enough and it helped you understand the way caching works and its importance