A PHP Error was encountered

Severity: 8192

Message: Methods with the same name as their class will not be constructors in a future version of PHP; Cacheall_info has a deprecated constructor

Filename: libraries/cacheall_info.php

Line Number: 136

Happy shop


Saving bandwith and cpu, Caching output in PHP

After reading the excellent article of Dave Child "Caching output in PHP" and the comments on that page, I tried to rewrite the code with some of the suggested improvements.

I also looked at the suggestions from Omar AlBadri "Better Php Caching" and other ones found at php.net. And of course some of my own ideas.
Improvements

This gives the following program flow:

flow

  1. Route 1:Saves bandwith and cpu we just send 304 not modified header (fastest)
  2. Route 2:Next best we serve the already compressed valid cachefile saving bandwith and cpu (fast)
  3. Route 3:Next best we serve the already compressed expired cachefile saving bandwith and cpu (fast) not waiting for a lock
  4. Route 4:We run our program and serve compressed content and save the compressed cachefile (slow)
  5. Route 5:We run our program and serve compressed content and save the compressed cachefile (slow)

Note that routes 3 and 4 only happen when there are several visitors at the same time so in 99% of the cases route 1,2 or 5 will happen

Structure

begin_cache.php
yourPhpFile.php
end_cache.php

The first, "begin_cache.php" in this case, will run before any other PHP on your site.
The second, "end_cache.php" in this case, runs after normal scripts have run.
The two scripts effectively wrap around your current site.

How to do it:

Method 1

Simply use the require_once() function and add them manually to every script you run.
Advantage portable, but a lot of work

<?php
require_once ("path_to_begin_caching.php/begin_caching.php");
//your php code
require_once ("path_to_end_caching.php/begin_caching.php");
?>

Method 2

Relies on adding the following two lines of code (modified to reflect the correct path to the two PHP files needed) to your htaccess file.
Easy to turn off (just by commenting # out the relevant lines in the .htaccess file). But session variables are needed, so a lot of work
php_value auto_prepend_file /full/path/to/begin_caching.php
php_value auto_append_file /full/path/to/end_caching.php

If you use this method the following variables need to be session variables $key_bc, $fp_bc, $cachefile_bc, $ignore_page_bc, $now_bc, $cachetime_bc

Method 3

The preferred way easy to turn off (just by commenting # out the relevant line in the .htaccess file) and quick to do.
Add the following line to your htaccess file:
RewriteRule ^([a-z]+)$ loader.php?p=$1

Create a file loader.php with the following lines in it:
<?php
require_once("begin_cache.php");
require_once(
$_REQUEST["p"]);
require_once(
"end_cache.php");
?>

The code in begin_cache.php


<?php
/* begin_cache.php
 * programmer RvH date 09-11-2013
 */

if ($_SERVER['REQUEST_METHOD'] === 'GET')                                       // Only process get REQUESTS
{
    
$cachedir_bc  '/path/to/your/cache/';                                     // Directory to cache files in (keep outside web root)
    
$extension_bc '.htm';                                                     // DOT + Extension to give cached files (usually .cache, .htm, .txt)
    
$exclude_bc   '__';                                                       // Don't cache files with these markers  ( caches t_est_.php but not tes__t.php )
    
$key_bc       "s3crEt";
    
$cachetime_bc 86400;                                                      // cache is valid: time in seconds   60*60*24 = 86400 = 1 day

    
$page_bc      'http://' $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];

    
error_reporting(0);                                                         // expensive
    
ignore_user_abort(TRUE);                                                    // make sure the cachefile gets written to disk

    
$ignore_list_bc = array(                                                    // files not to be cached = ignore list
        
'counter.php',
        
'search.php',
    );

    if (
strpos($page_bc,$exclude_bc) !== false )                                // add files with markers to the ignore list
        
$ignore_list_bc[] = $page_bc;

    
$cachefile_bc $cachedir_bc md5($page_bc) . $extension_bc;               // Cache file to either load or create
    
$ignore_page_bc false;

    if( 
in_array($page_bc,$ignore_list_bc) )                                    // do we need to ignore this file
        
$ignore_page_bc true;                                                 // if a match was found set it to true

    
if ( isset($_SERVER['REQUEST_TIME']) )                                      // not always available, but if so faster dan time()
       
$now_bc $_SERVER['REQUEST_TIME'];
    else
        
$now_bc time();

// caching
    
if ($ignore_page_bc === false)                                              // not in ignore list than proceed
    
{
        if (
is_file($cachefile_bc))                                             // do we have one
        
{
            
$filesize_bc      filesize($cachefile_bc);
            
$last_modified_bc filemtime($cachefile_bc);
            
$etag_bc          md5_file($cachefile_bc);
            
clearstatcache();
            
header_remove ("Pragma");                                           // sometimes hosting sets Pragma no-cache
            
header_remove ("Expires");
            
header("Last-Modified: ".gmdate("D, d M Y H:i:s"$last_modified_bc)." GMT");
            
header("Etag: $etag_bc");
            
header ("Cache-Control: must-REVALIDATE");

// serve from browser cache just send headers
            
if (@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified_bc || trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag_bc )
            {
                
header("Warning: BROWSER cache size $filesize_bc");
                
header("HTTP/1.1 304 Not Modified");
                
ob_end_clean();
                exit;
            }

// serve from cache if still valid
            
if ($now_bc $cachetime_bc $last_modified_bc)
            {
                
$size_bc filesize($cachefile_bc);
                
header("Content-Encoding: gzip");
                
header("Expires: $cachetime_bc");
                
header("Warning: 200 VALID from cache filesize $filesize_bc");
                if (
readfile($cachefile_bc)){
                    
ob_end_clean();
                    exit();
                }
            }

// expired cache
            
$fp_bc fopen$cachefile_bc"ab");                               // Let's first try to lock (w or w+ would erase the file)
            
if (! flock($fp_bcLOCK_EX LOCK_NB))                             // lock but don't wait for it, if we can't get one
            
{                                                                   // can't lock, SERVE AN OLD CACHEFILE
                
ob_start();

                
header("Content-Encoding: gzip");
                
header("Content-Length: ".$filesize_bc);
                
header("Expires: $cachetime_bc");
                
header("Warning: 200 EXPIRED cache");
                
header("Etag: ".$etag_bc);

                if (
fpassthru($fp_bc))
                {
                    
fclose($fp_bc);
                    
ob_end_clean();
                    exit();
                }
            }
            else
                
flock($fp_bcLOCK_EX);                                         // lock it (wait) and create new cachefile
        
}// eof cachefile exists
        
else
        {
           
$fp_bc fopen$cachefile_bc"ab");                                // No cachefile at all, let's first try to lock ( w or w+ would erase the file)
           
flock($fp_bcLOCK_EX);                                              // lock and wait for it than create new cachefile
        
}
        
ob_start();

    }
// eof ignore page

}// eof GET

The code in end_cache.php


<?php
/* end_cache.php
 * programmer RvH date 09-11-2013
 */

if ( ! isset( $key_bc ) || $key_bc !== "s3crEt")                    // if someone forgets to put this outside webspace
{
    
ob_end_clean();
    @
fclose($fp_bc);
    @
unlink($cachefile_bc);
    die(
"No direct acces allowed");
}

if (
$_SERVER['REQUEST_METHOD'] === 'GET')                           // ignore post!
{
    if (
$ignore_page_bc === false)                                  // not in ignore list than proceed
    
{
        
ftruncate($fp_bc0);                                       // same as fopen with w

        
$contents_bc ob_get_contents();
        
$filesize_bc ob_get_length();
        
ob_end_clean();

        if( 
stripos($contents_bc,"<pre>") === false )               // not to be used with <pre> tags
            
$contents_bc sanitize_output($contents_bc);

        
$compressed_out_bc gzencode($contents_bc,9);

        
header("Content-Encoding: gzip");
        
header("Warning: 200 cache CREATED $filesize_bc bytes time = ".date("Y-m-dTH:i:s",$now_bc));
        
header("Cache-Control: must-revalidate");
        
header('Last-Modified: '.gmdate('D, d M Y H:i:s \G\M\T'$now_bc));
        
header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T'$now_bc $cachetime_bc));
        
header("Etag: ".$etag);

        
fwrite($fp_bc$compressed_out_bc);                         // save the contents of output buffer to the file
        
flock($fp_bcLOCK_UN);
        
fclose($fp_bc);

        echo
"$compressed_out_bc";
        exit();
     }
}

function 
sanitize_output($buffer)
{
    
$search = array(
         
'/\>[^\S ]+/s',                                            // strip whitespaces after tags, except space
         
'/[^\S ]+\</s',                                            // strip whitespaces before tags, except space
         
'/(\s)+/s'                                                 // shorten multiple whitespace sequences
     
);
    
$replace = array(
         
'>',
         
'<',
         
'\\1'
     
);
    
$buffer preg_replace($search$replace$buffer);
    return 
$buffer;
}

The names of the variables have _bc behind them to avoid a conflict

Configuration

The code:

We check if the request is a get.
Define the settings
We add the files with the marker "__" to the ignore list
Check if the requested file is to be ignored
Send headers if the file is in the browser cache and not expired
We serve a cachefile if it is valid and exists.
If not we will try to get a lock, if we can't we serve an old cachefile if it exists,
If we are still in the program we need to run end_cache.php

Usually this method will reduce the file size by 30%-70%
The page will load faster the onload time is reduced by around 25%

When you see compressed html directly or after refreshing the page (F5) you need to put this rule in .htaccess or change the value in php.ini:
php_flag zlib.output_compression Off

Finaly we need to delete old cachefiles. Rule of thumb delete them as often as your $cachetime_bc.
Crontab should run for the following php once a day


<?php
$cachedir_bc  
"";   // the directory where the cachefiles are
$cachetime_bc "";   // the time the cachefiles stay valid
$cachedir_bc .= ( substr($cachepath, -1) !== "/" ) ? "/" "";
$files array_diffscandir(), array(".."".",".htaccess","index.html") ); // skip these
foreach($files as $file)
    if ( 
time() - $cachetime_bc filemtime($cachedir_bc.$file) )                 // delete cachefiles if they are too old
        
@unlink($cachedir_bc.$file);
?>


Or add the folowing line to crontab
10 5 * * * find /path/to/cachedir/ -name "*.htm" -mtime +1 -delete

Example cleans cache dir at 5:10 AM every day assuming your files have extension .htm and your $cachetime_bc = 1 day

I welcome and publish comments and optimizations of this code mail me



Test the program look at the headers
?>