Frames Modems Help Home Page Chipsets Search No Frames
Diary Entries See also Site Info & Diary.
28 March 2004 Documenting the php PEAR Cache_Lite Class...
 

The sob story: all I wanted to do was write a little routine to let me see a graph of the last 100 days hits on my sites. That’s not much to ask, is it? Should only take a day or so. The MySQL routines for this are a bit slow, but, using caching, it might be possible to put it up as a public feature. As soon as I went to look for the documentation on the Date and Time class (there wasn’t any) I knew that this was going to be a grind.

This page seeks to document (below) one of the un-documented Classes within PEAR--the “PHP Extension and Application Repository”, one of the hidden extras of PHP--namely the Cache_Lite Class. [4 Apr update: caching has been introduced to the Search pages. Early results are encouraging; one page was reduced from 3.36 to 0.11 secs to produce.]

Info on PEAR, including installation, can be found on my site as part of the Date class documents. I do not have the time to do a full job here, but will attempt to at least list all the functions of the Class and to give any specifics as I find & use them.

 

Cache_Lite describes itself as a “Fast, light and safe Cache Class”. It gives itself 3 main goals:

  • speed
  • simplicity
  • security

There is a brief tutorial translated from the original French, and a DevShed article (broken when I tried it). WebReference has a good general article on caching, which includes specifics on Cache_Lite. There is also a page of PEAR API Documentation which uses phpDocumentor to list all Cache_Lite Classes & functions. In the course of researching all this I also came across an excellent--though rather long--article on optimising PHP.

Because of all the above, this will be documentation-Lite, using my own site(s) as a touchstone (as a side comment, modem-help.com can also be accessed via modem-help.uklinux.net (not any more); modem-help.freeserve.co.uk leads to the same page, and modem-help.co.uk (used to) encourage you to go to the .com site; this proliferation of urls is due to the historic development of these sites - more info is on the compression page).

One of the display goals for modem-help.com is to get each php page script to complete in less than one-tenth of a second. The biggest successes in this effort came through focussing on SQL statements (the site uses MySQL 3.23.58-log). Some statements stubbornly refused to be optimised any further yet were very slow. These die-hards were ideal candidates for a Cache routine, and implementing such a routine became one of my mid-term goals. [Another side-comment: I was surprised to find that a Limit clause within the SQL did not seem to speed it up. Indeed, returning all 30,000 lines seemed to take less time than returning the last 10. Strange.]

Cache_Lite offers 3 kinds of caching:

  • strings/other (Cache_Lite() via Lite.php) (non-strings are serialised)
  • functions/methods (Cache_Lite::Cache_Lite_Function() via Function.php)
  • output (Cache_Lite::Cache_Lite_Output() via Output.php) (using output buffering)

...and each one can be done on a global or a group basis, on disk or in memory (this latter is described as “beta quality”). Caching requires a bit of pre-thought before you begin to use it. In my case, the routine collects an array of 100 days-worth of hits. If in the cache already only today’s hits need collecting, whilst at midnight the cache needs refreshing with a new array. Eventually, I will probably use Function.php, but for now will use Lite.php. Written as pseudo-code it looks like this:

<?php
  require_once 'Cache/Lite.php';
  require_once 'Date.php';

  $oldDay = $today = new Date();
  $oldDay->subtractSeconds( 100 * 86400 );   // 100 days

  $today_query_com = "(sql statement)";      // ~0.0100 sec Domain:  modem-help.com
  $today_query_uk  = "(sql statement)";      // ~0.1724 sec Domains: Freeserve + .co.uk
  $full_query_com  = "(sql statement)";      // ~0.4409 sec
  $full_query_uk   = "(sql statement)";      // ~0.4687 sec

  $id = 'hits';                              // timing begins (code not shown)
  $options = array(
    'automaticSerialization' => TRUE,
    'lifeTime' => 86400                      // one day max
  );
  $cache = new Cache_Lite( $options );

  if ( $hits = $hitsCache->get( $id ) {      // cache hit
    $cacheDay = new Date( $cache->lastModified() );
    $todayDOW = $today->getDayOfWeek();
    $cacheDOW = $cacheDay->getDayOfWeek();
  }

  if ( $hits && $todayDOW == $cacheDOW ) {   // cache hit + same day
                                             // only need to refresh today`s hits
    $r = mysql_query( $today_query_com );
    while( list(...) = mysql_fetch_row( $r ) ) $hits[date][domain] = $visit;
    $r  = mysql_query( $today_query_uk );
    while( list(...) = mysql_fetch_row( $r ) ) $hits[date][domain] = $visit;
  } else {                                   // cache miss or not same day
                                             // need to do full query + save in cache
    $r = mysql_query( $full_query_com );
    while( list(...) = mysql_fetch_row( $r ) ) $hits[date][domain] = $visit;
    $r  = mysql_query( $full_query_uk );
    while( list(...) = mysql_fetch_row( $r ) ) $hits[date][domain] = $visit;
    $cache->save( $hits );
  }                                          // timing ends
?>

Early timing tests are 0.18 secs / 0.89 secs (cache-hit / cache-miss). Emptying the TRUE (cache-hit) part of the routine timed at 0.00 secs: the entire cache routine takes less than one hundredth of a second. I call that impressive!

OK, on to the Cache-Lite class:

 

The PEAR Cache_Lite() class:

Overview: the idea is:

  1. Set up the $id (+ $group if used) & options array
  2. Declare a Cache_Lite object
  3. Test for the presence of the info in the cache, then:
    • if TRUE, use the returned info
    • if FALSE, create the info + save it to the cache

See above for an example.

  1. Lite.php
  2. Output.php
  3. Function.php
 

Lite.php:

Constructor:

  $cache = new Cache_Lite()             // uses all defaults as below
  $cache = new Cache_Lite( $options )

$options array:

  $options = array(
    'automaticSerialization' => FALSE,  // (boolean) TRUE = can use with non-string data (slower)
    'cacheDir'               => '/tmp/',// (string) where to put files; must exist already;
                                        // take care over access permissions; trailing slash required
    'caching'                => TRUE,   // (boolean) TRUE / FALSE; enable / disable caching
    'lifeTime'               => 3600,   // (int) cache lifetime in seconds
    'fileLocking'            => TRUE,   // (boolean) enable / disable fileLocking
    'fileNameProtection'     => TRUE,   // (boolean) TRUE = can use ANY cache id or group name
    'memoryCaching'          => FALSE,  // (boolean) disable/enable in-memory caching (no lifetime)
    'memoryCachingLimit'     => 1000,   // (int) max records to store in memory
    'onlyMemoryCaching'      => FALSE,  // (boolean) disable / enable ONLY in-memory caching
    'readControl'            => TRUE,   // (boolean) enable / disable read control
    'readControlType'        => 'crc32',// (string) 'md5', 'crc32', 'strlen' (slow->fast)
    'writeControl'           => TRUE,   // (boolean) enable / disable write control
    'pearErrorMode'          => 1       // (int) (on raiseError call) 1 = CACHE_LITE_ERROR_RETURN
                                        // (cf PEAR doc)              8 = CACHE_LITE_ERROR_DIE
  );

Cache_Lite::Functions:

  $result = $cache->clean()                                                   // remove ALL files
  $result = $cache->clean( $group )                                           // TRUE  = no problem
  $data   = $cache->get($id, $group='default', $doNotTestCacheValidity=FALSE) // FALSE = no cache
  (NULL)  = $cache->getMemoryCachingState($id, $group='default', $doNotTestCacheValidity=false)
  (NULL)  = $cache->raiseError($msg, $code)                                   // (int) $code
  $result = $cache->remove( $id, $group = 'default' )                         // TRUE  = no problem
  $result = $cache->lastModified()                                            // Unix timestamp
  $result = $cache->save( $data, $id = NULL, $group = 'default' )             // TRUE  = no problem
  (NULL)  = $cache->saveMemoryCachingState( $id, $group = 'default' )
  (NULL)  = $cache->setLifeTime( $newLifeTime )                               // in seconds
  (NULL)  = $cache->setToDebug()                                              // stop on error