Even though Google's memcache documentation is quite good and the examples are very straight forward and aren't all that much code, I still didn't want to repeat the same code over and over so I came up with something reusable. Read on to check it out.
On a side note — as it stands now, I'm getting around a 70% hit rate on the cached data, which is quite good I think.
These are the steps I wanted to execute for every request that could be put into Memcache:
- Check a global Memcache disable flag (useful for debugging)
- Check if the logged in user is an Admin user and skip Memcache (to avoid poisoning the cache with admin requests)
- Check if content is in Memcache
- Return content if found
- Generate/query content, store in Memcache and return the content if not found
All simple steps but totally unnecessary to repeat everywhere that Memcache was being used. So with that said, lets look at the code.
First I defined a consts.php file that held a couple of global constants that I could use to completely disable Memcache use and control the default expiry time of items in the cache...
consts.php
/* whether data should be stored in MemCache */
const AG_USE_MEMCACHE = true;
/* default amount of seconds to keep data in memcache (30 days) */
const AG_MEMCACHE_EXPIRE = 2592000;
Then I put the utility code into MemcacheUtil.php...
MemcacheUtil.php
<?php
use google\appengine\api\users\UserService;
/**
* Utility for working with memcache.
*/
class MemcacheUtil
{
/**
* Fetches content from memcache if it exists, otherwise calls the $contentFunc
* to generate content to store in memcache and return.
*/
public static function serveFromMemcache($key, $contentFunc,
$expire = AG_MEMCACHE_EXPIRE) {
$content = false;
$isAdmin = false;
$user = UserService::getCurrentUser();
if (isset($user) && UserService::isCurrentUserAdmin()) {
$isAdmin = true;
}
if (AG_USE_MEMCACHE && !$isAdmin) {
$memcache = new \Memcache;
$content = $memcache->get($key);
}
if ($content === false) {
if (is_callable($contentFunc)) {
$content = $contentFunc();
}
else {
throw new \Exception('Content function not callable.');
}
if (AG_USE_MEMCACHE && !$isAdmin) {
$memcache->set($key, $content, 0, $expire);
}
}
return $content;
}
/**
* Removes an entry from the memcache.
*/
public static function remove($key) {
if (AG_USE_MEMCACHE) {
$memcache = new \Memcache;
$memcache->delete($key);
}
}
}
?>
The code is fairly straight forward and executes the steps that I outlined above, so I won't be going through it line by line. Instead lets see how it is used.
Anywhere that I wanted to either serve data from Memcache or generate it and store it in Memcache if it wasn't available, I could do something like this...
PHP
$data = MemcacheUtil::serveFromMemcache('datakey', function() {
return 'somedata';
}
);
The 'datakey' is just an example key string that is used to look up or store data in Memcache. In most cases this should be based on some ID e.g. the page ID being generated. I actually ended up adding some other functions to the MemcacheUtil class that would generate these data keys for me in a consistent manner (not shown in the code above).
The anonymous function in the example is what is called to generate data if the utility code doesn't find a value in Memcache. In the example above it always returns the same string, a string - 'somedata' but in reality the body of this function can be much more complicated e.g. looking up something from the DataStore then populating a Smarty/Twig template and generating the HTML for a specific page.
The content generation function can also make use of closures, so it's possible to do something like this...
PHP
$someOtherString = 'derp';
$data = MemcacheUtil::serveFromMemcache('datakey', function() use ($someOtherVar) {
return $someOtherVar . ' somedata';
}
);
The above is a trivial example, in my case I make use of closures to pass the Symphony Request object to my content generation function.
It is also possible to provide a custom data expiry time if the default is not sufficient, just pass an integer value after the content generation function.
The remove() method on the MemcacheUtil class is just a convenience method to remove data from the cache. It does a basic global disable check and removes data if Memcache is not disabled.
-i