Making dynamic PHP pages cacheable

Mar 24 2005

RD2 launched our new site yesterday. One of my interests was in building RESTful URI for the website. The site is very minimal and only has sections for about, work and contact. Inside of work we break it down by client and then by section number. You end up with URI like work/entrust/1. The work portfolio section is built out with a PHP file. This creates the problem of a non-cacheable page.

Initially I set the page up to build with a simple include.


<?php
include('includes/portfolio/' . $company_list[$current_company]['copy_dir'] . '/' . $current_section . ".php");
?>

I decided to replace that with my own caching_include function. The first thing I noticed is that I’m using include to read the text files. I don’t need PHP to do a full include and parse of the file each time. I switched to using fpassthru instead of include.

The next thing I do is generate an ETag or entity tag for the page. I use the filepath and the file modification time to generate the ETag. Since I’m using the file’s modification time my ETag will change whenever the file does. This will make sure the browser doesn’t keep displaying a cached version even after I’ve made modifications.

I recommend using the Live HTTP Headers extension with Firefox for testing this out. You may also need to look into using output buffering if your headers are not being sent.

Here is the new caching_include function I put together to more intelligent handly building out my templates.


function caching_include($file) {


$headers = apache_request_headers();


// Get the modification timestamp
list(,,,,,,,,,$lastModified) = stat($file);
// Build our entity tag
$eTag = "ci-".dechex(crc32($file.$lastModified));

if ((strpos($headers['If-None-Match'], "$eTag")) &&
(gmstrftime("%a, %d %b %Y %T %Z", $lastModified) == $headers['If-Modified-Since'])) {


// They already have an up to date copy so tell them
header('HTTP/1.1 304 Not Modified');
header('Cache-Control: private');
header("Pragma: ");
header('Expires: ');
header('Content-Type: ');
header('ETag: "'.$eTag.'"');
exit;


} else {


// We have to send them the whole page
$fh = fopen($file,'rb');
header('Cache-Control: private');
header('Pragma: ');
header('Expires: ');
header('Last-Modified: '.gmstrftime("%a, %d %b %Y %T %Z",$lastModified));
header('ETag: "'.$eTag.'"');
fpassthru($fh);


}


}

8 Comments to “Making dynamic PHP pages cacheable”

  1. Dude! There’s a much saner way to get the modified time of the main file (works even if you *include* the function):

    header(“Last-Modified: ” . date(“D, d M Y H:i:s”, getlastmod()));

    It pays to Read The Fucking Manual.

    Cheers,
    -Hope-

    By HopeSeekr of xMule on May 10th, 2005 at 3:43 pm
  2. I didn’t realize date() was GMT, I thought it took timezone into account. So yep, that would work too.

    I wonder if there is a performance benefit to choosing one over the other.

    By matt on May 10th, 2005 at 3:57 pm
  3. header(“Last-Modified: “.gmdate(“D, d M Y H:i:s”,getlastmod()).” GMT”);

    By pijiazi on July 20th, 2005 at 3:32 pm
  4. Thanks a lot. This is the only solution that worked for me trying to make the browser cache css files that were delivered by php.
    The simple solution posted in the comments does have no effect for these files. Etags rule!

    By Andreas Stephan on December 12th, 2007 at 3:44 pm
  5. Thanks. But Actually i want to know that can we use etag example as given above for dynamic site. I mean to say that those files in which i m using php codes for website. If this possible then plz send me an examle.

    By Suman Kumar Sinha on February 27th, 2008 at 12:40 am
  6. Thanks a lot for this script Matt, it should be very useful.

    By Sean on April 3rd, 2008 at 7:21 am
  7. header(“Last-Modified: ” . date(“r”, getlastmod()));

    By Peter on January 13th, 2009 at 11:00 pm
  8. Thanks. This article helped. But one thing I found is a more portable way to get the If-None-Match request header is through $_SERVER['HTTP_IF_NONE_MATCH'].

    By Keith on January 15th, 2009 at 5:42 am

Leave a Reply