How do I implement a simple server side caching system?
Now that we have a grasp of the ideas behind output buffering, it's time to see how we can put this process into action in a manner that will be easy to maintain. To do this, we'll use a little help from PEAR::Cache_Lite (version 1.1 was used in the examples here).
As I mentioned, in the interests of keeping your code maintainable and having a reliable caching mechanism, it's a good idea to delegate the responsibility of caching logic to classes you trust. Cache_Lite provides a solid but easy to use library for caching, handling issues such as file locking, creating, checking for, and deleting cache files, controlling the output buffer, and directly caching the results from function and class method calls. More to the point, Cache_Lite should be relatively easy to apply to an existing application, requiring only minor code modifications.
There are three main classes in Cache_Lite. First is the base class, Cache_Lite, which deals purely with creating and fetching cache files, but makes no use of output buffering. This class can be used alone for caching operations in which you have no need for output buffering, such as storing the contents of a template you've parsed with PHP. The examples here will not use Cache_Lite directly, but will instead focus on the two subclasses. Cache_Lite_Function can be used to call a function or class method and cache the result; this might prove useful for storing a MySQL query result set, for example. The Cache_Lite_Output class uses PHP's output control functions to catch the output generated by your script, and store it in cache files; it allows you to perform tasks such as those we completed in the previous solution.
Here's an example of how you might use Cache_Lite to accomplish the task we completed in the last solution. When instantiating any of Cache_Lite's classes, we must first provide an array of options that determine the behavior of Cache_Lite. We'll look at these in detail in a moment. Note that the cacheDir directory specified must be one to which the script has read and write access.
Example 5.7. 4.php (excerpt)
<?php
// Include the PEAR::Cache_Lite Output class
require_once 'Cache/Lite/Output.php';
// Define options for Cache_Lite
$options = array(
'cacheDir' => './cache/',
'writeControl' => 'true',
'readControl' => 'true',
'readControlType' => 'md5'
);
// Instantiate Cache_Lite_Output
$cache = new Cache_Lite_Output($options);
For each chunk that we want to cache, we need to set a lifetime (in seconds) for which the cache should live before it's refreshed. Next, we use the start method, available only in the Cache_Lite_Output class, to turn on output buffering. The two arguments passed to the start method are an identifying value for this particular cache file, and a cache group. This is an identifier that allows a collection of cache files to be acted upon; it's possible to delete all cache files in a given group, for example (more on this in a moment). Once the output for this chunk has finished, we use the end method to stop buffering and store the content as a file.
Example 5.8. 4.php (excerpt)
// Set lifetime for this "chunk"
$cache->setLifeTime(604800);
// Start the cache with an id and group for this chunk
if (!$cache->start('header', 'Static')) {
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title> PEAR::Cache_Lite example </title>
<meta http-equiv="Content-Type"
content="text/html; charset=iso-8859-1" />
</head>
<body>
<h2>PEAR::Cache_Lite example</h2>
The header time is now: <?php echo date('H:i:s'); ?><br />
<?php
// Stop and write the cache
$cache->end();
}
Caching the body and footer follows the same procedure as the header. Note that we again specify a five second lifetime when caching the body:
Example 5.9. 4.php (excerpt)
$cache->setLifeTime(5);
if (!$cache->start('body', 'Dynamic')) {
echo 'The body time is now: ' . date('H:i:s') . '<br />';
$cache->end();
}
$cache->setLifeTime(604800);
if (!$cache->start('footer', 'Static')) {
?>
The footer time is now: <?php echo date('H:i:s'); ?><br />
</body>
</html>
<?php
$cache->end();
}
?>
On viewing the page, Cache_Lite creates in the cache directory files with these names:
./cache/cache_Static_header
./cache/cache_Dynamic_body
./cache/cache_Static_footer
When the same page is requested later, the code above will use the cached file if it is valid and has not expired.
Protect your Cache Files
Make sure that the directory in which you place the cache files is not publicly available, or you may be offering your site's visitors access to more than you realize.
Cache_Lite Options
When instantiating Cache_Lite (or any of its subclasses, such as Cache_Lite_Output), there are a number of ways to control its behavior. These should be placed in an array and passed to the constructor as in the previous example:
Example 5.10. 4.php (excerpt)
// Define options for Cache_Lite
$options = array(
'cacheDir' => './cache/',
'writeControl' => TRUE,
'readControl' => TRUE,
'readControlType' => 'md5'
);
// Instantiate Cache_Lite_Output
$cache = new Cache_Lite_Output($options);
In the current version (1.1) the available options are:
cacheDir - This is the directory in which the cache files will be placed. This defaults to the current script execution directory.
caching - This option switches on or off the caching behavior of Cache_Lite. If you have numerous Cache_Lite calls in your code and want to disable the cache for debugging, for example, this will be important. The default value is TRUE (caching enabled).
lifetime - This represents the default lifetime (in seconds) of cache files. It can be changed using the setLifeTime method. The default value is 3600 (one hour).
fileNameProtection - With this option activated, Cache_Lite uses an MD5 encryption hash to generate the filename for the cache file. This protects you from error when you try to use IDs or group names containing characters that aren't valid for filenames; it must be turned on when you use Cache_Lite_Function. The default is TRUE (enabled).
fileLocking - This is used to switch the file locking mechanisms on or off. The default is TRUE (enabled).
writeControl - This checks that a cache file has been written correctly immediately after it has been created, and throws a PEAR::Error if it finds a problem. Obviously, this would allow your code to attempt to rewrite a cache file that was created incorrectly, but comes at a cost in terms of performance. The default is TRUE (enabled).
readControl - This checks cache files that are being read for corruption. Cache_Lite is able to place inside the file a value, such as the string length of the file, which can be used to confirm that the cache file isn't corrupted. There are three alternative mechanisms for checking that a file is valid, and they're specified using the readControlType option. These mechanisms come at the cost of performance, but should help guarantee your visitors aren't seeing scrambled pages. The default value is TRUE (enabled).
readControlType - This specifies the type of read control mechanism to use. The available mechanisms are a cyclic redundancy check ('crc32', the default value) using PHP's crc32 function, an MD5 hash using PHP's md5 function ('md5'), or a simple and fast string length check ('strlen'). Note that this mechanism is not intended to provide security from people tampering with your cache files; it's just a way to spot corrupt files.
pearErrorMode - This tells Cache_Lite how it should return PEAR errors to the calling script. The default is CACHE_LITE_ERROR_RETURN, which means Cache_Lite will return a /#c#?PEAR::Error object.
memoryCaching - With memory caching enabled, every time a file is written to the cache, it is stored in an array in Cache_Lite. The saveMemoryCachingState and getMemoryCachingState methods can be used to store and access the memory cache data between requests. The advantage of this is that the complete set of cache files can be stored in a single file, reducing the number of disk read/writes by reconstructing the cache files straight into an array to which your code has access. We'll be sticking to the normal Cache_Lite mechanism here, but memoryCaching may be worth further investigation if you run a large site. The default value is TRUE (disabled).
onlyMemoryCaching - If this is enabled, only the memory caching mechanism will be used. The default value is TRUE (disabled).
memoryCachingLimit - This places a limit on the number of cache files that will be stored in the memory caching array. The more cache files you have, the more memory will be used up by memory caching, so it may be a good idea to enforce a limit that prevents your server from having to work too hard. Of course, this places no restriction on the size of each cache file, so just one or two massive files may cause a problem. The default value is 1000.
Purging the Cache
Cache_Lite's in-built lifetime mechanism for cache files provides a good foundation for keeping your cache files up to date, but there will be some circumstances in which you need the files to be updated immediately. For such cases, the methods remove and clean come in handy. The remove method is designed to delete a specific cache file; it takes the cache ID and group name of the file. To delete the page body cache file we created above, we'd use:
$cache->remove('body', 'Dynamic');
Using the clean method, we can delete all the files in our cache directory simply by calling the method with no arguments; alternatively, we can specify a group of cache files to delete. If we wanted to delete both the header and footer created above, we could do so like this:
$cache->clean('Static');
The remove and clean methods should obviously be called in response to events within an application. For example, if you have a discussion forum application, you probably want to remove the relevant cache files when a visitor posts a new message. Although it may seem like this solution entails a lot of code modifications, with some care it can be applied to your application in a global manner. If you have a central script that's included in every page a visitor views, you can simply watch for incoming events (e.g. a variable like $_GET['newPost']) and have some code respond by deleting the required cache files. This keeps the cache file removal mechanism central and easier to maintain. You might also consider using the php.ini setting auto_prepend_file to include this code in every PHP script.
Caching Function Calls
In Chapter 2, XML, we looked at accessing remote Web services with SOAP and XML-RPC. Because Web services are accessed over a network, it's often a very good idea to cache results so that they can be fetched locally, rather than repeating the same slow request multiple times. A simple approach might be to use PHP sessions, as we considered in that chapter, but as this solution operates on a per visitor basis, the opening requests for each visitor will still be slow. This is where Cache_Lite can come in very handy.
PEAR uses Cache_Lite
The PEAR Web installer (see Appendix D, Working with PEAR) takes advantage of Cache_Lite by caching the XML-RPC requests it makes to the PEAR Web server.
In the section called "How do I consume SOAP Web services with PHP?", we built a client for a SOAP Web service based on its WSDL file; the service provided weather information for airports around the world. Here's the code that fetched the data from the remote server:
$countries = $stationInfo->listCountries();
and
$country = $stationInfo->searchByCountry($_GET['country']);
In both cases, these calls correspond to a request for data that's made over the network. Using Cache_Lite_Function, we could cache the results so the data returned from the service could be reused; this would avoid unnecessary network calls and significantly improve performance. Note that we're focusing on only the relevant code here. At the top, we include Cache_Lite_Function:
Example 5.11. 5.php (excerpt)
// Include PEAR::Cache_Lite_Function
require_once 'Cache/Lite/Function.php';
Further down, we instantiate the Cache_Lite_Function class with some options:
Example 5.12. 5.php (excerpt)
// Define options for Cache_Lite_Function
// NOTE: fileNameProtection = TRUE!
$options = array(
'cacheDir' => './cache/',
'fileNameProtection' => TRUE,
'writeControl' => TRUE,
'readControl' => TRUE,
'readControlType' => 'strlen',
'defaultGroup' => 'SOAP'
);
// Instantiate Cache_Lite_Function
$cache = new Cache_Lite_Function($options);
It's important that the fileNameProtection option is set to TRUE (this is in fact the default value, but in this case I've set it manually to emphasize the point). If it were set to FALSE, the filename will be invalid, so the data will not be cached.
Here's how we make the calls to our SOAP client class:
Example 5.13. 5.php (excerpt)
$countries = $cache->call('stationInfo->listCountries');
And:
Example 5.14. 5.php (excerpt)
$country = $cache->call('stationInfo->searchByCountry',
$_GET['country']);
If the request is being made for the first time, Cache_Lite_Function stores the results as serialized arrays in cache files (not that you need to worry about this), and this file is used for future requests until it expires. The setLifeTime method can again be used to specify how long the cache files should survive before they're refreshed; right now, the default value of 3,600 seconds (one hour) is being used.
In general, Cache_Lite provides a solid, easy-to-implement library for solving caching issues. As we move to the "next level" of caching, for sites with particularly high traffic, it's worth examining PEAR::Cache, Cache_Lite's big brother. PEAR::Cache is a complete caching framework that offers greater flexibility than Cache_Lite, and ties in with database abstraction libraries such as PEAR:B. It also offers advanced features such as caching to shared memory, as an alternative to the file system, or, with help from the Msession PHP extension, storing cache data in load balanced sessions, which is particularly useful for load balanced Web servers. Further PEAR::Cache reading material is recommended for at the end of this chapter. Cache_Lite, however, offers more than enough functionality to meet the requirements of the majority of sites.
Source:
http://www.sitepoint.com/artic...-caching/3