Drop In PHP Caching Function

Updated about 7 mths, 23 days ago (July 27, 2023). Know a better answer? Let me know!

Drop in PHP Caching Function

Cache complex content using PHP without greatly modifying the existing code structure.

Drop in caching function I wrote – just call ned_cache() at whatever point (ideally before any time consuming processing has happened) in the file you want to be cached. Very useful for adding caching to something else without modifying it or actually designing it to be cached. Also enables normal browser caching. Seems to work on absurdly complex stuff, so should work on simple things…

/*******************************************************************************
*
*   Caching functions
*
*   This function will check for a cache file for this page, and if one does not
*   exist, will create it.
*
*   @param age int
*   The maximum age of the cache file in seconds. Defaults to 3600 seconds (1
*   hour). Set to a negative value to delete the cache file.
*
*   @author Ned Martin nedmartin.org
*
*******************************************************************************/
function ned_cache($age = 3600)
{
    // don't cache POST or any other type of requests
    if($_SERVER['REQUEST_METHOD'] == 'GET')
    {
        $cache = '/location/to/store/cache/'
            .md5($_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']).'.cache';

        // delete the cached file if we are sent a negative age. Provides a
        // method to delete cached content at will.
        if($age < 0)
        {
            unlink($cache);
        }

        // if the cache file exists and is still "fresh"
        if(file_exists($cache) && time() - ($fmod = filemtime($cache)) < $age)
        {
            // check if modified - if not modified, this will simply return a
            // 304 Not Modified header to the browser and exit. No further data
            // transferred or processing time wasted.
            ned_conditional_get($fmod);

            // add an Age header so we can tell how old the cached content is
            // and read the cached content from disk and return to browser. No
            // further processing time wasted.
            header('Age: '.(time() - $fmod));
            readfile($cache);
            exit();
        }

        // if the cache file did not exist, or was "stale", then the page
        // continues to load as per normal and we create a cache file for next
        // time.

        // depending on server environment, either using ob_start or
        // register_shutdown_function may work
        //register_shutdown_function('ned_cache_ob', $cache);
        ob_start('ned_cache_ob');
    }
}
function ned_cache_ob($data)
{
    // create a unique name for the cache file
    $cache = '/location/to/store/cache/'
        .md5($_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']).'.cache';

    // save data to cache file
    if(strlen($data) > 0)
    {
        file_put_contents($cache, $data);
    }

    // check if modified - if not modified, this will simply return a
    // 304 Not Modified header to the browser and exit. No further data
    // transferred or processing time wasted.
    ned_conditional_get(filemtime($cache));

    // return data to the browser for display
    return $data;
}



/**
* Return 304 not modified headers when applicable
*
* @param lastModified int modified time (in seconds since Unix epoch, UTC)
*
*/
function ned_conditional_get($lastModified)
{
        // one hour
        $expires = gmdate('D, d M Y H:i:s \G\M\T', $lastModified + 3600);
        $lastModified = gmdate('D, d M Y H:i:s \G\M\T', $lastModified);
        header('Pragma:');
        header('Cache-Control: must-revalidate');
        header('Expires: '.$expires);
        header('Last-Modified: '.$lastModified);
        header('ETag: "'.md5($lastModified).'"');

        // check if the last modified date sent by the client is the the same as
        // the last modified date of the requested file. If so, return 304
        // header and exit.

        // depending on server php implementation, may need to use $_SERVER or
        // apache_request_headers()
        //$h = apache_request_headers();
        //if(isset($h['If-Modified-Since']))
        if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']))
        {
            if($_SERVER['HTTP_IF_MODIFIED_SINCE'] == $lastModified)
            //if($h['If-Modified-Since'] == $lastModified)
            {
                header('HTTP/1.1 304 Not Modified');
                exit();
            }
        }
        // check if the Etag sent by the client is the same as the Etag of the
        // requested file. If so, return 304 header and exit.
        //if(isset($h['If-None-Match']))
        if(isset($_SERVER['HTTP_IF_NONE_MATCH']))
        {
            //if(str_replace('"', '', stripslashes($h['If-None-Match']))
            //  == md5($lastModified))
            if(str_replace('"', '', stripslashes($_SERVER['HTTP_IF_NONE_MATCH']))
                == md5($lastModified))
            {
                header("HTTP/1.1 304 Not Modified");
                exit();
            }
        }
}
/*******************************************************************************
*
*   End Caching functions
*
*******************************************************************************/

See Control Caching Of Dynamic Web Pages for more information on the caching part of this, ned_conditional_get

More Information

For more information on:

 

Updated about 7 mths, 23 days ago (July 27, 2023). Know a better answer? Let me know!

Related categories [coloured].

User submitted comments:

J T S, about 1 mth, 2 days ago
Thursday February 15, 2024 3:26 AM

Thanks Ned! This is exactly what I was looking for. Core cache features that I can implement with drop-in code on specific pages without having to mess with the overall structure of the app or with the server. If you were in the room, I would give you a big hug.

Comment on this article (no HTML, max 1200 characters):