Caching and partial caching in CodeIgnitor

Caching in CI works great: it reduces memory consumption by 45%, uses less processor time and serves pages up to 3 times faster.
So when creating static pages that is the way to go.

Unfortunately there are not that many pages that remain completely static.
Often your page will have static and dynamic elements, see example below:

File partType
Headerstatic
User login informationdynamic
Advertisementdynamic
Weather forecaststatic for a day
Footerstatic

The solution is partial caching.
There are several libraries around that can do the job: They work like:

File partFunctionType
$headercache library callcache function
$dynamicLoginDatacall from modelfunction or database query not cached
$dynamicAdvertisementcall from modelfunction or database query not cached
$weathercache library call cache function
$footercache library callcache function

Good results can be expected when you cache complex functions or complex database queries.
The benefits of partial caching for views are marginal:

page fully cached by CIphp time = 0.0085mb = 0.54
header and footer of the page cachedphp time = 0.0173mb = 0.94
page not cachedphp time = 0.0184mb = 0.94

You can cache a part of the page but when there are three parts of a page that need to be cached, you need to call these libraries three times.
The libraries have to open and read three cache files making the benefits for views marginal at best.
Improvement over a non cached page 11 milliseconds and some processor time on a 5kb cache
A good approach could be combine all elements of a page that need to be cached in one file and put a seperator between them.

So I decided to give it a try to create a library with the name cacheall:
1) Store all static parts of a page in one cache file.
2) The cache library needs to be able to handle data (from models libraries or functions) and views

The write function of the library takes 2 arguments:
An array with parts to cache and an unique name for the cachefile
The array with parts to cache can hold strings (views or a seperator = '||||') or an array (data from functions queries or models or a seperator = '||||' )
Looking like this:

(
      array(
            "nameofview1",
            "nameofview2",
            array(
                  $data1,
                  "||||",
                  $data2,
                  $data3
            ),
            "||||",
            "nameofview3"
      );
      string $cacheFilename
)


Now the library can distinguish between views and data:
If a part of the array is an array it holds data or the seperator
If not it is a view or the seperator

array ('header','||||',array($weather),'footer') ,$cachefilename) ;

returns string $header.'||||'.$weather.$footer

Now in your controler you can do:

if ( ! $cache = $this->cacheall->read('myHomePage')
{
     $this->load->model('weather');
     $weather = $this->weather->get_todays_weather();
     $cache = $this->cacheall->write($items2cache = array ('header', '||||', array($weather), 'footer'), 'myHomePage');
}
list($cachepart1, $cachepart2) = explode('||||', $cache);
$output = $cachepart11 . $dynamicLoginData . $dynamicAdvertisement . $cachepart22;
$this->output->set_output($output);


1 = $cachepart1 is 'header'
2 = $cachepart2 is $weather . 'footer'

Of course you can have several seperators.

Note the separator is optional occasionally you can do this:

if ( ! $cache = $this->cacheall->read('myHomePage') ){
     $this->load->model('db');
     $queryresult = $this->db->do_query();
     $cache = $this->cacheall->write(items2cache=array('header',array($queryresult),'footer'), 'myHomePage');
}
$this->output->set_output($cache);3


3. You can also use a view for the output let's use "myview.php"
$data['output'] = $cachepart1 . $dynamicData . $cachepart2 . $moreDynamicData . cachepart3;
$this->load->view('myview',$data);
and in myview.php
echo $output;


page fully cached by CIphp time = 0.0070mb = 0.54
header and footer of the page cached by Cacheallphp time = 0.0155mb = 0.95
page not cachedphp time = 0.0215mb = 0.96

Improvement over a non cached page 60 milliseconds 0.01mb less memory and some processor time on a 5kb cache
Better results are expected if you cache complex functions or database queries.


The library has 4 methods:
1 $this->cacheall->read() → reads the cache (described above)
2 $this->cacheall->write() → writes the cache (described above)
3 $this->cacheall->delete() → deletes a specific cachefile:
4 $this->cacheall->delete_all() → deletes all cachefiles (run it if cacheall configuration changes)
I tried to make the library as fast as I could, for this reason the configuration is stored in CI's config file and not in a seperate config file.

Download the library for more examples

INSTALLATION:

#

# Load the library
Add the next line to /application/config/autoload.php
$autoload['libraries'] = array('cacheall');

# Configure the library
Add the next 4 lines to application/config/config.php
$config['library_cacheall_expiration'] = 86400;    // in seconds 60*60*24 = 86400 = 1 day
$config['library_cacheall_path'] = '/application/cache/';    // your path to CI cache → should be writeable
$config['library_cacheall_seperator'] = '||||';    // the seperator you want
$config['library_cacheall_serialize'] = 0;    // 1 = serialize the cache     0 = don't serialize the cache


# Put the library in place
Copy the library to application/libraries

I also included cacheall_info.php this version has all the functionality of the cacheall class but has an extra info method for obtaining information about the cachefiles, cache directory and cacheall settings, it also enables you to delete 1 or all cachefiles.
It takes no arguments and returns a html string or false.
$config['library_cacheall_info_password'] = 'secret';     // password is required for showing information → should be passed as segment
$config['library_cacheall_ip'] = '127.0.0.1'; // option protect info function with your ip address
If you don't set the last one only the segment is checked
$this->cacheall_info->info() → retrieves information about the library settings, cachefiles and cache directory.


<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
/**
*
* Cacheall Class
* Stacked partial caching for CodeIgnitor
* This library can cache several parts of a page (views and data) and creates one cachefile
* Making partial caching more efficient
* See readme.txt or cache_all_info.php for example use
*
* @category       Libraries
* @author         RvH <ricardo1956@live.nl>
* @version        0.9.4  date 2013-06-11
* @license        cacheall by RvH is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License
* @copyright     (c) 2013 RvH
* @link           http://www.rvh1.nl/index.php/home/cicaching
*/

class Cacheall
{
    private 
$_ci;
    private 
$_expiration;
    private 
$_path;
    private 
$_seperator;
    private 
$_serialize;

    
/**
     * Constructor - Initializes and references CI
     */
    
function __construct($params=array())
    {
        
$this->_ci = & get_instance();
        
// The values below should be defined in application/config/config.php or at this point directly
        
$this->_expiration $this->_ci->config->item('library_cacheall_expiration'); // $config['library_cacheall__expiration'] = 86400; in seconds = 1 day
        
$this->_path       $this->_ci->config->item('library_cacheall_path');       // $config['library_cacheall__path']       = '/your_pathtoCI/application/cache/';
        
$this->_seperator  $this->_ci->config->item('library_cacheall_seperator');  // $config['library_cacheall__seperator']  = '||||';
        
$this->_serialize  $this->_ci->config->item('library_cacheall_serialize');  // $config['library_cacheall__serialize']  = 0;
    
}

    
/**
     * Read Cache File
     *
     * @access   public
     * @param    string
     * @return   cache or false
     */
    
function read($cachefilename)
    {
        
$file_path $this->_path.md5($cachefilename);

        if (@
is_file($file_path))                                               // is_file() is faster than file_exists()
        
{
            if ( isset(
$_SERVER['REQUEST_TIME']) )                              // not always available, but if so faster dan time()
                
$now $_SERVER['REQUEST_TIME'];
            else
                
$now time();

            
$time_of_cachefile filemtime($file_path);

            if ( (
$now -  $time_of_cachefile) < $this->_expiration)             // only read the cachefile if it is valid
            
{
                
$cache '';

                if ( ! 
$this->_serialize)
                    
$cache file_get_contents($file_path);
                else
                    
$cache un_serialize(file_get_contents($file_path));

                return 
$cache;                                                  // cache ok so stop
            
}
            @
unlink($file_path);                                                // cache too old delete it
        
}
        return 
false;                                                           // cache file does not exist or is too old
    
}

    
/**
     * Write Cache File
     *
     * @access   public
     * @param    array
     * @param    string
     * @return   cache
     */
    
function write($cached_items=array(),$cachefilename)
    {
        
$output '';
        
$error  0;

        if (
is_dir($this->_path) && is_really_writable($this->_path))
        {
            
ignore_user_abort(true);                                            // create the cache even if the user leaves the page

            
$file_path $this->_path.md5($cachefilename);

            foreach (
$cached_items as $item)
            {
                if ( 
is_array($item) && sizeof($item) )
                    foreach(
$item as $data)                                     // data or seperator
                        
$output .= $data;
                else if (
$item === $this->_seperator)
                    
$output .= $this->_seperator;                               // seperator
                
else
                    
$output .= $this->_ci->load->view($item,'',true);           // views
            
}

            if ( 
$this->_serialize)
                
$output serialize($output);

            if (!@
file_put_contents($file_path,$outputLOCK_EX))
                
$error 1;
        }
        else
            
$error 1;
        if (
$error)
            
log_message('error'"Could not write cachefile : $file_path check permissions");
        return 
$output;
    }

    
/**
     * Delete Cache File
     *
     * @access    public
     * @param     string
     * @return    void
     */
    
function delete($cachefilename)
    {
        
$file_path $this->_path.md5($cachefilename);
        if ( !@
unlink($file_path))
            
log_message('error'"Could not delete cachefile $file_path check permissions");
    }

    
/**
     * Delete Full Cache
     *
     * @access    public
     * @param     void
     * @return    void
     */
    
function delete_all()
    {
        
$files array_diff(scandir$this->_path), array('..''.','.htaccess','index.html'));
        foreach(
$files as $file)
            if ( ! @
unlink($this->_path.$file) )
                
log_message('error'"Could not delete cachefile $this->_path.$file check permissions");
    }
}



Creative Commons License
cacheall by RvH is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.