Article
Cache it! Solve PHP Performance Problems
How do I use PEAR::Cache_Lite for server-side caching?
The previous solution explored the ideas behind output buffering using the PHP ob_* functions. Although we mentioned at the time, that approach probably isn't the best way to meet to dual goals of keeping your code maintainable and having a reliable caching mechanism. It's time to see how we can put a caching system into action in a manner that will be reliable and easy to maintain.
Solution
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. In this case, we'll use a little help from PEAR::Cache_Lite (version 1.7.2 is used in the examples here). Cache_Lite provides a solid yet easy-to-use library for caching, and handles 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.
Cache_Lite has four main classes. 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 three subclasses. Cache_Lite_Function can be used to call a function or class method and cache the result, which 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 "How do I cache just the parts of a page that change infrequently?". The Cache_Lite_File class bases cache expiry on the timestamp of a master file, with any cache file being deemed to have expired if it is older than the timestamp.
Let's work through an example that shows how you might use Cache_Lite to create a simple caching solution. When we're instantiating any child classes of Cache_Lite, we must first provide an array of options that determine the behavior of Cache_Lite itself. We'll look at these options in detail in a moment. Note that the cacheDir directory we specify must be one to which the script has read and write access:
cachelite.php (excerpt)
<?php
require_once 'Cache/Lite/Output.php';
$options = array(
'cacheDir' => './cache/',
'writeControl' => 'true',
'readControl' => 'true',
'fileNameProtection' => false,
'readControlType' => 'md5'
);
$cache = new Cache_Lite_Output($options);
For each chunk of content 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. The group 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). The start method will check to see if a valid cache file is available and, if so, it will begin outputting the cache contents. If a cache file is not available, start will return false and begin caching the following output.
Once the output for this chunk has finished, we use the end method to stop buffering and store the content as a file:
cachelite.php (excerpt)
$cache->setLifeTime(604800);
if (!$cache->start('header', 'Static')) {
?>
<!DOCTYPE html public "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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>
<p>The header time is now: <?php echo date('H:i:s'); ?></p>
<?php
$cache->end();
}
To cache the body and footer, we follow the same procedure we used for the header. Note that, again, we specify a five-second lifetime when caching the body:
cachelite.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')) {
?>
<p>The footer time is now: <?php echo date('H:i:s'); ?></p>
</body>
</html>
<?php
$cache->end();
}
?>
On viewing the page, Cache_Lite creates cache files in the cache directory. Because we've set the fileNameProtection option to false, Cache_Lite creates the files with these names:
- ./cache/cache_Static_header
- ./cache/cache_Dynamic_body
- ./cache/cache_Static_footer
You can read about the fileNameProtection option--and many more--in "What configuration options does Cache_Lite support?". 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.
What configuration options does Cache_Lite support?
When instantiating Cache_Lite (or any of its subclasses, such as Cache_Lite_Output), you can use any of a number of approaches to controlling its behavior. These options should be placed in an array and passed to the constructor as shown below (and in the previous section):
$options = array(
'cacheDir' => './cache/',
'writeControl' => true,
'readControl' => true,
'fileNameProtection' => false,
'readControlType' => 'md5'
);
$cache = new Cache_Lite_Output($options);
Solution
The options available in the current version of Cache_Lite (1.7.2) are:
cacheDir
This is the directory in which the cache files will be placed. It defaults to /tmp/.
caching
This option switches on and 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 option will be important. The default value is true (caching enabled).
lifeTime
This option represents the default lifetime (in seconds) of cache files. It can be changed using the setLifeTime method. The default value is 3600 (one hour), and if it's set to null, the cache files will never expire.
fileNameProtection
With this option activated, Cache_Lite uses an MD5 encryption hash to generate the filename for the cache file. This option protects you from error when you try to use IDs or group names containing characters that aren't valid for filenames; fileNameProtection must be turned on when you use Cache_Lite_Function. The default is true (enabled).
fileLocking
This option is used to switch the file locking mechanisms on and off. The default is true (enabled).
writeControl
This option 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 facility would allow your code to attempt to rewrite a cache file that was created incorrectly, but it comes at a cost in terms of performance. The default value is true (enabled).
readControl
This option checks any cache files that are being read to ensure they're not corrupt. 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 corrupt. 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 to guarantee that your visitors aren't seeing scrambled pages. The default value is true (enabled).
readControlType
This option lets you specify the type of read control mechanism you want 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 option 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 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 facility is that the complete set of cache files can be stored in a single file, reducing the number of disk read/write operations by reconstructing the cache files straight into an array to which your code has access. The memoryCaching option may be worth further investigation if you run a large site. The default value is false (disabled).
onlyMemoryCaching
If this option is enabled, only the memory caching mechanism will be used. The default value is false (disabled).
memoryCachingLimit
This option 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 option 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.
automaticSerialization
If enabled, this option will automatically serialize all data types. While this approach will slow down the caching system, it is useful for caching nonscalar data types such as objects and arrays. For higher performance, you might consider serializing nonscalar data types yourself. The default value is false (disabled).
automaticCleaningFactor
This option will automatically clean old cache entries--on average, one in x cache writes, where x is the value set for this option. Therefore, setting this value to 0 will indicate no automatic cleaning, and a value of 1will cause cache clearing on every cache write. A value of 20 to 200 is the recommended starting point if you wish to enable this facility; it causes cache cleaning to happen, on average, 0.5% to 5% of the time. The default value is 0 (disabled).
hashedDirectoryLevel
When set to a nonzero value, this option will enable a hashed directory structure. A hashed directory structure will improve the performance of sites that have thousands of cache files. If you choose to use hashed directories, start by setting this value to 1, and increasing it as you test for performance improvements. The default value is 0 (disabled).
errorHandlingAPIBreak
This option was added to enable backwards compatibility with code that uses the old API. When the old API was run in CACHE_LITE_ERROR_RETURN mode (see the pearErrorMode option earlier in this list), some functions would return a Boolean value to indicate success, rather than returning a PEAR_Error object. By setting this value to true, the PEAR_Error object will be returned instead. The default value is false (disable).