Skip to content

Instantly share code, notes, and snippets.

@runion
Created October 1, 2012 16:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save runion/3812913 to your computer and use it in GitHub Desktop.
Save runion/3812913 to your computer and use it in GitHub Desktop.
class.caching.php - simple class for caching PHP files
<?php
class caching {
public $useCache = true;
public $hours_cache_remains_valid = 8;
private $curDirectory;
private $cacheFolder; // DOCUMENT_ROOT + this + $cacheFileName == cached_filename
private $serverFilename;
public $cacheFileName; // this is just the filename -- used to test if over 255 characters
public $cached_filename; // this includes the full path
public $saveCache; // boolean
private $ignore_directories = array(
"/administrator"
, "/translate"
, "/test"
, "/errors"
);
public function __construct() {
$this->serverFilename = $_SERVER['SCRIPT_FILENAME'];
if(!empty($this->serverFilename)) {
$this->curDirectory = dirname($this->serverFilename);
$this->cacheFolder = "/.cache".$this->curDirectory;
}
}
public function doCache() {
if(
$this->is_cpc_user()
|| sizeof(array_filter($this->ignore_directories, array($this, "ignore_directories_filter")))
|| sizeof($_POST) > 0
|| isset($_GET['bustcache'])
) {
$this->useCache = false;
} else {
if($this->valid_cache($this->get_cached_filename())) {
$this->saveCache = false;
} else {
$this->saveCache = true;
}
}
}
private function ignore_directories_filter($ignore_directory) {
# This function is used by array_filter directly above -- the $ignore_directory variable is one element
# passed by array_filter from the $this->ignore_directories array.
# We want to match on a substring so /test/subfolder will match and $this->useCache will be set to false,
# just the same as if we were in /test -- otherwise we could just do in_array
return strpos($this->curDirectory, $_SERVER['DOCUMENT_ROOT'].$ignore_directory) !== false;
}
public function valid_cache($cacheFile) {
if(!file_exists($cacheFile)) { return false; }
if(strlen($this->cacheFileName) > 250) { return false; }
$hours_cache_remains_valid = 8;
$max_cache_age = 60 * 60 * $hours_cache_remains_valid;
$cache_mtime = filemtime($cacheFile);
#echo $cacheFile."<br>";
#echo $scriptFile."<br>";
$script_mtime = filemtime($this->serverFilename);
# cache is valid if the file is less than $hours_cache_remains_valid old, and if the cache script was modified more recently than the generating script.
if($this->is_cache_expired($cache_mtime) || $this->is_cached_file_older_than_script($cache_mtime, $script_mtime)) {
unlink($cacheFile);
return false;
}
return true;
}
public function is_cache_expired($cache_mtime) {
$max_cache_age = 60 * 60 * $this->hours_cache_remains_valid;
return (time() - $max_cache_age > $cache_mtime);
}
public function is_cached_file_older_than_script($cache_mtime, $script_mtime) {
return ($cache_mtime < $script_mtime);
}
private function is_cpc_user() {
/* Determine if customer is a CPC customer. We show different contact info for paid search customers for tracking purposes. We could split off separate caches for this but it's easier to just generate the page... */
if (isset($_COOKIE["__utmz"])) {
$AnalyticsCookie = array();
foreach((array)preg_split('~([.|])(?=ut)~', $_COOKIE["__utmz"]) as $pair) {
if(strpos($pair, "=")) {
list($key, $value) = explode('=', $pair);
$AnalyticsCookie[$key] = $value;
}
}
if(isset($AnalyticsCookie['utmgclid']) && strlen($AnalyticsCookie['utmgclid']) > 0) {
return true;
}
}
if(
isset($_GET['gclid'])
|| isset($_GET['utm_nooverride'])
|| isset($_GET['utm_source'])
|| isset($_GET['utm_campaign'])
|| isset($_GET['utm_medium'])
) {
return true;
}
return false;
}
public function get_cached_filename() {
if(isset($this->cached_filename)) { return $this->cached_filename; }
GLOBAL $language; # we support multiple languages, a whole story in itself.
$this->create_cache_folder(); # make sure the folder exists first
$script_filename = $_SERVER['SCRIPT_FILENAME'];
$script_parts = explode("/", $script_filename);
$script = array_pop($script_parts);
$qs = "";
ksort($_GET); # alphabetize get by keys
if(sizeof($_GET) > 0) { $qs = "?"; }
$qsArr = array();
foreach($_GET as $key=>$value) {
array_push($qsArr, $key."=".$value);
}
$qsString = implode("+", $qsArr); # ampersands are a no-go but apparently equals signs in file names are fine
$qs .= $qsString;
$cacheFileNameTemp = str_replace(array("/", ",", "\\"), "", $script . "-" . $language . "-" . $hostTemp . "-" . $qs); # chomp / and ,
$this->cacheFileName = str_replace(" ", "%20", $cacheFileNameTemp); # spaces to %20 -- as it should be
$cacheFileNameWithPath = $this->cacheFolder . "/" . $this->cacheFileName;
$this->cached_filename = $_SERVER['DOCUMENT_ROOT'].$cacheFileNameWithPath;
return $this->cached_filename;
}
private function create_cache_folder() {
if(!file_exists($_SERVER['DOCUMENT_ROOT'].$this->cacheFolder)) {
#echo "creating: ".$_SERVER['DOCUMENT_ROOT'].$this->cacheFolder."<br>";
mkdir($_SERVER['DOCUMENT_ROOT'].$this->cacheFolder, 0777, true);
}
}
}
?>
@runion
Copy link
Author

runion commented Oct 1, 2012

I set the PHP variable auto_prepend to a file (say, "auto_prepend.php" in the root) that loads this like so:


$c = new caching();
$c->doCache();
if($c->useCache && !$c->saveCache) {
    include($c->get_cached_filename());
    define('NOSHUTDOWNFUNC', true);
    echo PHP_EOL."";
    die();
}
if($c->saveCache) {
    define('SAVECACHE', $c->get_cached_filename());
}

I save the cache using a PHP shutdown function (next comment).

@runion
Copy link
Author

runion commented Oct 1, 2012


if(defined("SAVECACHE") && !defined("is_a_404_page")) {
    /*
     * this should be the last shutdown function run because we need to 
     * make sure the HTML has already been modified before we cache it 
     */
    ob_start('save_cache');
}

As referenced in the comment, I have other PHP shutdown functions that modify the HTML before it's sent out. The save_cache function, which saves the final HTML output, needs to run after the modifications are made.

And finally, the save_cache function:

function save_cache($buffer) {
    $fp = fopen(SAVECACHE, "w");
    fwrite($fp, $buffer);
    fclose($fp);
    return $buffer;
}

@runion
Copy link
Author

runion commented Oct 1, 2012

In my first comment there is a line

echo PHP_EOL.""

Inside the quotes is an html comment I put at the bottom of the page. That explains why it didn't show. This should show:


echo PHP_EOL . "get_cached_filename()))."--" . ">";

@runion
Copy link
Author

runion commented Oct 1, 2012

Third time's the charm:

echo PHP_EOL . "<" . "!-- cached on ".date('jS F Y H:i', filemtime($c->get_cached_filename()))."--" . ">";

@runion
Copy link
Author

runion commented Oct 1, 2012

darn it.

echo PHP_EOL . $html_comment_start . " cached on ".date('jS F Y H:i', filemtime($c->get_cached_filename())) . $html_comment_end;

@yazeed
Copy link

yazeed commented Apr 13, 2013

Could you please explain how to set this up without the prepended option for a single PHP script?

@EdgeCaseBerg
Copy link

On Line 53 I think you mean to say $this->hours_cache_remains_valid so that the property of the object is used for anyone who modifies that expecting it to change the caching time. Also you could then remove Line 52!

@yazeed I think you would want to do a 'topcache' and 'bottomcache' page include method described like in this article

And then within the top and bottom you'd do what @runion is doing in his first two comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment