ASPit - Totally ASP JSit - Totally JavaScript
Search PHPit

Use this textbox to search for articles on PHPit. Seperate keywords with a space.

Advertisements

How to build a simple caching system, with PHP

(Page 2 out of 2)

Bringing it all together

Let's create our caching system. First of all, make sure you create a directory on your server, and make it fully write-able (CHMOD 777 if on a Linux server). If the directory isn't write-able, our caching system can't write its cached files.

Before we make our caching system too complicated, let's make sure it does the basic thing: gets the content, and saves it properly:

// Define cache directory - change to your own directory
define ('cache_dir', '/home/phpit/cache/');

// How long is data in cache valid - in hours
define ('cache_time', 24);

function cache_page ($content) {
        // Get current URL
        $url = get_url();

        // Create a unique filename out of our URL
        $filename = md5($url) . '.html';

        // Create data string
        $data = mktime() . '|' . $content;

        // Write file
        $f = fopen(cache_dir . $filename, 'w');
        fwrite($f, $data);
        fclose($f);

        return $content;
}

function get_url () {
        if (isset($_SERVER['REQUEST_URI'])) {
                $url = $_SERVER['REQUEST_URI'];
        } else {
                $url = $_SERVER['SCRIPT_NAME'];
                $url .= (!empty($_SERVER['QUERY_STRING'])) ? '?' . $_SERVER['QUERY_STRING'] : '';
        }

        return $url;
}

// Enable output buffering, with our caching callback
ob_start ('cache_page');

?>

One thing you might notice in the above code is the URL part. To create a unique cache of each page, our system must get the current URL, with query strings, and make a MD5 hash out of it. The URL must include query strings, because index.php?page=one could very well be a complete different page from index.php?page=two.

The next thing we must add is the display part. The above code only saves data to the cache; the caching system also has to display cached data. See the code below.

// Define cache directory - change to your own directory
define ('cache_dir', '/home/phpit/cache/');

// How long is data in cache valid - in hours
define ('cache_time', 24);

function cache_page ($content) {
        // Get current URL
        $url = get_url();

        // Create a unique filename out of our URL
        $filename = md5($url) . '.html';

        // Create data string
        $data = mktime() . '|' . $content;

        // Write file
        $f = fopen(cache_dir . $filename, 'w');
        fwrite($f, $data);
        fclose($f);

        return $content;
}

function get_url () {
        if (!isset($_SERVER['REQUEST_URI'])) {
                $url = $_SERVER['REQUEST_URI'];
        } else {
                $url = $_SERVER['SCRIPT_NAME'];
                $url .= (!empty($_SERVER['QUERY_STRING'])) ? '?' . $_SERVER['QUERY_STRING'] : '';
        }

        return $url;
}

function display_cache () {
        // Get current URL
        $url = get_url();

        // Get filename of the current URL
        $filename = md5($url) . '.html';
        $file = cache_dir . $filename;

        // Cache exists?
        if (!file_exists($file)) {
                // No cache, exit function
                return false;
        }

        // Get data
        $f = fopen($file, 'r');
        $data = fread($f, filesize($file));
        fclose($f);

        // Split timestamp and content
        $data = explode('|', $data, 2);

        // Is data valid?
        if (count($data) != 2 OR !is_numeric($data['0'])) {
                return false;
        }

        // Check if cache isn't too old
        // If current time - cachetime is bigger than cached data
        // Then not valid
        if (mktime()-(cache_time*60*60) > $data['0']) {
                return false;
        }

        // Cache is valid, display it, and end page
        echo $data['1'];
        die();
}

// Display cache (if any)
display_cache();

// Enable output buffering, with our caching callback
ob_start ('cache_page');

?>

That's all, and the above code is our complete caching system, and it does everything we want: time validation, automatic content caching, etc. Copy the code above in a new file called 'cache.php', and include the cache.php in all your PHP pages, above any other code. Those pages will then automatically be cached.

Conclusion

In this tutorial I have shown you how to create a simple, but easy-to-use, caching system. The system we created can be used for many websites, but you do have to be careful about one thing: don't cache pages that are custom for each user. For example, if you include your user's name or something else personal on a page, and it gets cached, everyone else would see the user's name. Not a good thing. Our caching system can only be used for non-personal pages.

If you're looking to create a more advanced caching system, have a look at DevShed's caching series: part one, part two, part three, part four and part five.

Update 15 Jan 2006

It is possible to cache private pages (see the comments below), and there are several ways of doing it. If you only have two versions of a page, namely 'open' and 'private', then it's possible to cache both versions seperately. But if your page is completely unique to a certain user, then it's not possible to cache.

« Previous: Output Callback & Time



9 Responses to “How to build a simple caching system, with PHP”

  1. Ramy Says:

    Dear Sir/Madam,

    this is really good but I am having problem accesing to the next page.
    Thank you.

    Regards,

    Ramy Tran.

  2. Duncan Crombie Says:

    I haven’t actually run the script, but can see some problems with this script:

    - use time() instead of mktime() to get the current timestamp
    - the logic in get_url() seems to be reversed: if (!isset($_SERVER[’REQUEST_URI’])) $url = $_SERVER[’REQUEST_URI’];
    - why load the entire cache file into memory just to check the date? I would use filemtime instead…

    ;)

  3. Duncan Crombie Says:

    Forgot the most imporant:

    - NEVER chmod to 777!!!

  4. Dennis Pallett Says:

    I fixed the get_url() logic, it was a small mistake that comes from my testing.

    Does it really matter if you use time() instead of mktime()? Both return the same thing anyway.

    I like your idea of using filemtime instead of using my method. It’s probably a bit faster, and easier.

    I understand that chmod’ing to 777 is a bad thing, but at least it works in all cases. Better of course would be something like 776 or 664.

    Thanks for your comments!

  5. Laszlo Baranyai Says:

    File lock could improve this cache system. What will happen if concurrent visitors are reading the page? The first will truncate the cache file to zero length and starts writing; the second could receive partial content or corrupt the cache data by writing simultaneously. I recently ran into a long discussion about this situation on a mailing list. :)
    Thanks for the article.
    Best regards, Laszlo.

  6. Hutch Says:

    The cache system can be modified to work with personal/secure pages as such:

    For regular non user/secure pages:
    Change the cached pages to .php not .html.
    Change the data for the cache file to $data = ‘’;

    When getting the cached pages use mktime (as previously suggested) and simply include the file ie:
    // Cache too old
    if (mktime()-(cache_time*60*60) > filemtime($file)){
    return false;
    }
    include($file);
    die();

    NOTE: I haven’t done this as my site is trivial but I believe it should work
    For personalized pages I suggest a shorter cache time so define a second variable secure_cache_time. At the top of any pages that are secure create a flag to identify as such define(’secure’,'1′) to the “cache engine”.
    For the filename use a combination of the url and a unique user identifier (username, userid, ip) or some combination.
    For the data $data = ‘’;
    IMPORTANT: where correct_user is the logic you would regularly check for user credentials. Finally for added security setup an htaccess file on the directory or put the user cached pages outside the root directory all together.

    Hacking in a method to define a per page cache_time would be a good idea since we generally know how often certain pages update and I also added a refresh_cache function. At the moment it can’t determine which files it needs updating so it just removes all of them (more of a refresh_all function).

  7. Dennis Pallett Says:

    Guess it ain’t working. Let me know what you want to post at dennis [AT] phpit [DOT] net, and I’ll add it to the article it self.

  8. Johanna Nordin Says:

    Santana Juelz What The Game’s Been Missing

    PHPit - Totally PHP &r…

  9. keitai Says:

    hi there,

    Trying to follow and implement your caching functions, but

    One question how about $content how is this one set??

Leave a Reply

About the author
Dennis Pallett is the main contributor to PHPit. He owns several websites, including ASPit and Chill2Music. He is currently still studying.
Article Index
  1. Output Callback & Time
  2. Bringing it all together
Bookmark Article
Download Article
PDF
Download this article as a PDF file