Overview

After reading JP's article, "Building your website with cached dynamic modules", I decided I would rework many of my database-driven pages, incorporating his solution. However, I realized that some functions were missing so I used his foundation and adapted it with some additions of my own. Also, I realized that there were some syntactical issues and fundamental usability that needed reworking.
I'll briefly list the specific functionalities that I added with explanation.

Spaces And Quoting

I needed the abililty to use spaces in the values of the arguments being passed to the module. I also wanted the developer to be able to use any quoting convention. Single or double quotes are valid around the value, but not both.
    <my-style name=test>
    <my-style name="test">
    <my-style name='test'>
    <my-style name='test value'>

Manual Cache File Naming

I used a template to generate cache files on the fly, and this was based on dates and times. The cache's filename is generated on the value's being passed, but some filesystems do not allow colons that would be found in a time format. I realized I needed to allow the developer to name the cache file manually if desired.
<my-style name="time now" time="12:32:00" cache_name="onTheAir">
Originally, this tag would have produced a cache file with a name such as, 'style_name=time now_time=12:32:00'. Now, this module would produce a cache file with the simpler (valid) name: 'style_onTheAir'

'On The Hour' Cache Updating

JP's system allowed the developer to set the expires delay for a cache file. For example, if the expires delay was set to 3600 seconds, the cache file would be regenerated subsequent to the next hit after 3600 seconds have elapsed since the creation of the cache file.
My site needed to work a little differently. I needed the ability to automatically have the module refresh at specific intervals, such as, on the hour, or on the quarter-hour, or on the half-hour, etc. So I went ahead and added some functions to perform these evaluations and return whether the module was due to be updated or not.
<my-style name=test refresh='HALFHOUR'>
(this cache file updates itself every half-hour)
The available arguments for the refresh variable are:
MINUTE, QUARTERHOUR, HALFHOUR, HOUR, HALFDAY, DAY, MONTH

Cache File Locking

I'm performing some complex SQL selects to generate some of these cached modules. Some of them are taking longer than I would like, but priorities demand that I concentrate on tasks other than database performance. I simply added some cron tasks to automate the refreshing of modules in coordination with their tag REFRESH tag. The cron tasks simply request the page from the web server.
However, I was concerned with multiple users hitting the server initiating redundant SQL executions, which would unnecessarily increase the load on the database.
I used a simple locking solution. If the script determines that a cache file needs to be updated, it creates a lock file that indicates the cache file is already being generated. Subsequent requests for that same cache file notice that the lock file exists and wait until that lock file is removed by the initial script and then grabs the cache file when it finishes.
Hopefully, with these enhancements, this solution becomes even more powerful while making it easier to implement for the developer.
You should be able to cut and paste it into your own solution.

<?php

//*********************************************************//
// function.parse_my_tags.php3
// Created By: "JP" <jprins@dds.nl>
// Modified By: "PHPBuilder Staff" <webmaster@phpbuilder.com>
// Last Modified Date: 11/21/1999
//
//*********************************************************//
// Special thanks to "JP" <jprins@dds.nl> for providing this
// concept and a solid source foundation for this solution.
// http://www.phpbuilder.com/columns/jprins20000201.php3
//*********************************************************//
// Reserved Tags:  REFRESH, EXPIRES, CACHE_NAME
//
//    1)    REFRESH - 
//            This tag will automatically check to see if this interval
//            has passed and refresh the cache appropriately.
//            I.E. QUARTERHOUR will automatically update the cache if 
//                    the cache is older that the latest quarter hour
//            arguments allowed:    MINUTE, QUARTERHOUR, HALFHOUR,
//                                HOUR, HALFDAY, DAY, MONTH
//    2)    EXPIRES - 
//            arguments allowed: Any interval in seconds
//
//        Please note the differences between REFRESH and EXPIRES.
//            EXPIRES - updates the content at the file's time of creation 
//                plus the interval
//            REFRESH - updates the content on the interval hour.  
//                HALFHOUR is 11:00 and 11:30.  The first refresh will occur 
//                at the first occurance that this interval is met after the 
//                cache as been created.
//                I.E.    a) Last cache creation @ 11:14
//                        b) Interval set at HALFHOUR
//                        c) Next refresh occurs at 11:30 (or the first 
//                            request afterwards)
//
//        refresh and expires may be used simultaneously if needed.
//
//    3)    CACHE_NAME - this argument can be used to name the cache file
//        in lieu of the default method of concatenating the arguments
//        in the tag.  This is useful when a tag contains characters
//        that are unusable for filenaming like slashes or colins as found
//        in date strings.
//
// Usage:
//        <my-style name='test value' expires="3600" cache_name="myStyleCache">
//
//    Note that code has been modified to use quoting (both, double and 
//    single quotes) and also modified to allow spaces to be used in values
//*********************************************************//

// Setup
    
$str_cache_file "";

function 
parse_it ($str) {

    global 
$arr_loaded;
    global 
$str_cache_file;
    
$int_default_cache_time 3600;
    if (
eregi "<[Mm][Yy]-([A-Za-z0-9]*) ([^>]*)"$str$regs)) {
        
$str_tag $regs[1];
        if (!
$arr_loaded[$str_tag]) {
//        include("./res/$str_tag/$str_tag.php"); //For Unix
            
include("res\$str_tag\$str_tag.php"); // For Win
            
$arr_loaded[$str_tag] = 1
    }

        
$str_func =  "handle_$str_tag";
        
$arr_list explode " "strtolower ($regs[2]));
//    $cache_dir =  "cache/$str_tag"; // For Unix
        
$cache_dir =  "cache\$str_tag"// For Win
        
$str_cache_file $cache_dir;
        
$flag_quote 0;
        for (
$i 0$i count ($arr_list); $i++) {
            
// *Here we are handling the tag's value..
            // *Checking for single and double quoting.
            //    A New Tag
            
if ($flag_quote == 0) {
                if (
$argname strtok($arr_list[$i],  "=")) {
                    
$val strtok ("=");
                    if (
$val[0] ==  "'") {
                        
$char_quotetype "'";
                        
$flag_quote 1;
                        
$arglist[$argname] = substr($val1);
                    } elseif (
$val[0] ==  "\"") {
                        
$char_quotetype "\"";
                        
$flag_quote 1;
                        
$arglist[$argname] = substr($val1);
                    } else {
                        
$flag_quote 0;
                        
$arglist[$argname] = $val;
                    }
                    if (
substr($arglist[$argname], -1) == $char_quotetype) {
                        
$flag_quote 0;
                        
$arglist[$argname] = substr($arglist[$argname], 0, -1);
                    }
                }
            } elseif (
$flag_quote == 1) {
                if (
substr($arr_list[$i], -1) == $char_quotetype) {
                    
$arglist[$argname] .= " " substr($arr_list[$i], 0, -1);
                    
$flag_quote 0;
                } else {
                    
$arglist[$argname] .= " " $arr_list[$i];
                }
            }
            
// Generate the cache file's name
            // We exclude any non-specific arguments for use in
            // naming the cache file
            
if (($argname != "expires") &&
                (
$argname != "refresh") &&
                (
$argname != "cache_name")) { 
                
$str_cache_file .=  "_" $argname .  "=" $arglist[$argname]; 
            }
        }
        
// if the developer set a cache_name, use it instead
        // of concatenating the module arguments for a cache
        // filename
        
if (isset($arglist["cache_name"])) {
            
$str_cache_file $cache_dir "_" $arglist["cache_name"];
        }

        
// Check to see if this tag is already being updated
        // If so, wait until it is completed and then continue
        
clearstatcache();
        
// Here we perform some error correction.  If a lock file
        // is left over from a previous request, this ensures that 
        // it is deleted.
        
if (file_exists($str_cache_file '.lock')) {
            
register_shutdown_function("remove_cache_lock");
        }
        while (
file_exists($str_cache_file '.lock')) {
            
sleep(2);
            
clearstatcache();
            continue;
        }
        
clearstatcache();

        
// Setup the variables
        
$flag_read_cache 0
        
$flag_write_cache 0;
        
$flag_expires_read_cache 0;
        
$flag_expires_write_cache 0;
        
$flag_refresh_write_cache 0;
        
$flag_refresh_read_cache 0;

        
// Check if the developer is using the 'expires' tag
        
if    (!(isset($arglist["expires"]) && ($arglist["expires"] < 10))) {
            
$flag_expires_write_cache 1;
            if (
file_exists ($str_cache_file)) { 
                if (!isset (
$arglist["expires"])) { 
                    if ((
filemtime ($str_cache_file) + $int_default_cache_time) > date "U")) { 
                        
$flag_expires_read_cache 1
                        
$flag_expires_write_cache 0
                    }     
                } else { 
                    if ((
filemtime ($str_cache_file) + $arglist["expires"]) > date "U")) { 
                        
$flag_expires_read_cache 1
                        
$flag_expires_write_cache 0
                    } 
                } 
            } 
        }
        
// Check if the developer is using the 'refresh' tag
        
if (isset($arglist["refresh"])) {
            
$flag_refresh_write_cache 1;
            if (
file_exists ($str_cache_file)) {
                
$flag_refresh_read_cache 1;
                
$flag_refresh_write_cache 0;
                switch (
strtoupper($arglist["refresh"])) {
                    case 
'MINUTE':
                        if (!
checkRefreshMinute(mktime(), filemtime($str_cache_file))) {
                            
$flag_refresh_write_cache 1;
                            
$flag_refresh_read_cache 0;
                        }
                        break;
                    case 
'QUARTERHOUR':
                        if (!
checkRefreshQuarterHour(mktime(), filemtime($str_cache_file))) {
                            
$flag_refresh_write_cache 1;
                            
$flag_refresh_read_cache 0;
                        }
                        break;
                    case 
'HALFHOUR':
                        if (!
checkRefreshHalfHour(mktime(), filemtime($str_cache_file))) {
                            
$flag_refresh_write_cache 1;
                            
$flag_refresh_read_cache 0;
                        }
                        break;
                    case 
'HOUR':
                        if (!
checkRefreshHour(mktime(), filemtime($str_cache_file))) {
                            
$flag_refresh_write_cache 1;
                            
$flag_refresh_read_cache 0;
                        }
                        break;
                    case 
'HALFDAY':
                        if (!
checkExpiredHalfDay(mktime(), filemtime($str_cache_file))) {
                            
$flag_refresh_write_cache 1;
                            
$flag_refresh_read_cache 0;
                        }
                        break;
                    case 
'DAY':
                        if (!
checkRefreshDay(mktime(), filemtime($str_cache_file))) {
                            
$flag_refresh_write_cache 1;
                            
$flag_refresh_read_cache 0;
                        }
                        break;
                    case 
'MONTH':
                        if (!
checkRefreshMonth(mktime(), filemtime($str_cache_file))) {
                            
$flag_refresh_write_cache 1;
                            
$flag_refresh_read_cache 0;
                        }
                        break;
                    default:
                        echo 
"Invalid REFRESH attribute<BR>\n";
                        break;
                }
            } else {
                
$flag_refresh_write_cache 1;
                
$flag_refresh_read_cache 0;
            }
        }
        
// Check for either cache update tags for reading and writing the cache
        
if (($flag_expires_write_cache || $flag_refresh_write_cache) &&
            !(
file_exists($str_cache_file '.lock'))) {
            
$flag_write_cache 1;
            
touch($str_cache_file '.lock');
            
register_shutdown_function("remove_cache_lock");
        }
        if ((
$flag_expires_read_cache || $flag_refresh_read_cache) && 
            !(
$flag_write_cache)) {
            
$flag_read_cache 1;
        }

        
$start =  "\n<!-- $str_tag starts here //-->\n";
        if (
$flag_read_cache || (!strlen ($buf .= @$str_func ($arglist)))) {
            if (
$f fopen ($str_cache_file,  "r")) { 
                while (
$str fgets ($f4096)) { 
                    
$buf .= $str
                } 
                
fclose ($f); 
            } else 
$buf .=  "<!-- $str_tag: error - cache is empty //-->\n"
        }
        
$end .=  "\n<!-- $str_tag ends here //-->\n"

        if (
$flag_write_cache && ($f fopen ($str_cache_file,  "w"))) { 
            
$buf " // -->\n" $buf;
            if (
$arglist["expires"] != "") { 
                
$buf " expires-{" $arglist["expires"] . '} ' $buf; }
            if (
$arglist["refresh"] != "") { 
                
$buf " refresh-{" $arglist["refresh"].'} ' $buf; }
            
            
$buf "<!-- CACHE: " $buf;
            
fputs ($f$buf); 
            
fclose ($f);
            
remove_cache_lock(); 
        } 
        return 
$start $buf $end
    } else { 
        return 
$str
    } 
}

function 
parse_my_tags ($file) {
    
$buf ""
    if (
$f fopen ($file,  "r")) {
    while (
$str fgets ($f4096)) {
            if (
$str != "") {
                
$str str_replace("\"""\\\""$str);
                eval ( 
"\$str = \"$str\";" );
            }
            
$buf .= parse_it($str);
        }
        
fclose ($f);
    }
    return 
$buf;
}

//*********************************/
// Some other functions
function remove_cache_lock() {
    global 
$str_cache_file;
    
clearstatcache();
    if (
file_exists($str_cache_file '.lock')) {
        @
unlink($str_cache_file '.lock');
    }
}

//*********************************/
// CACHE REFRESH CHECKING FUNCTIONS
// Each function checks the next interval higher for verification

function checkRefreshYear($systime$filetime) {
    
$sysYear date var gDomain="www.qsstats.com"; var gDcsId="dcsee0dng10000sdxo14hwex8_4x4k"; var gFpc="WT_FPC"; var gConvert=true; var gFpcDom = "phpbuilder.com"; if ((typeof(gConvert) != "undefined") && gConvert && (document.cookie.indexOf(gFpc + "=") == -1) && (document.cookie.indexOf("WTLOPTOUT=")==-1)) { document.write("<\/SCR"+"IPT>"); } function dcsAdditionalParameters() {}