pepe
pepe

Reputation: 9919

CodeIgniter - how to selectively cache output?

My application allows registered users to post stuff. On the top bar, like many social apps, when logged in one can see their avatar etc.

My issue is that the user-specific content in the header gets cached when using CodeIgniter's output cache.

This is because first I load user-specific logic via a MY_Controller, which is then extended by all other controllers.

When I place

$this->output->cache(MINUTES);

in the controller that loads my home page, it also ends up caching the avatar and name of the user that generated that cached page.

Does anyone have a suggestion on what would be the best way to selectively cache only the public content? Glad to post more code if needed.

Upvotes: 3

Views: 2659

Answers (2)

Chanatip Yim
Chanatip Yim

Reputation: 366

Codeigniter do not separate cache by visitor and authenticated user.

Otherwise you can do it by yourself by override output class. Create MY_Output.php in CI/application/core

This is my code for latest project.

class MY_Output extends CI_Output

{

/**
 * Write Cache
 *
 * @param   string  $output Output data to cache
 * @return  void
 */
public function _write_cache($output)
{
    $CI =& get_instance();

    //-XXX CUSTOM------------------------------------
    $cache_path = $this->cachePath();
    //-----------------------------------------------

    if ( ! is_dir($cache_path) OR ! is_really_writable($cache_path))
    {
        log_message('error', 'Unable to write cache file: '.$cache_path);
        return;
    }

    $uri = $CI->config->item('base_url')
        .$CI->config->item('index_page')
        .$CI->uri->uri_string();

    if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
    {
        if (is_array($cache_query_string))
        {
            $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
        }
        else
        {
            $uri .= '?'.$_SERVER['QUERY_STRING'];
        }
    }

    $cache_path .= md5($uri);

    if ( ! $fp = @fopen($cache_path, 'w+b'))
    {
        log_message('error', 'Unable to write cache file: '.$cache_path);
        return;
    }

    if ( ! flock($fp, LOCK_EX))
    {
        log_message('error', 'Unable to secure a file lock for file at: '.$cache_path);
        fclose($fp);
        return;
    }

    // If output compression is enabled, compress the cache
    // itself, so that we don't have to do that each time
    // we're serving it
    if ($this->_compress_output === TRUE)
    {
        $output = gzencode($output);

        if ($this->get_header('content-type') === NULL)
        {
            $this->set_content_type($this->mime_type);
        }
    }

    $expire = time() + ($this->cache_expiration * 60);

    // Put together our serialized info.
    $cache_info = serialize(array(
        'expire'    => $expire,
        'headers'   => $this->headers
    ));

    $output = $cache_info.'ENDCI--->'.$output;

    for ($written = 0, $length = self::strlen($output); $written < $length; $written += $result)
    {
        if (($result = fwrite($fp, self::substr($output, $written))) === FALSE)
        {
            break;
        }
    }

    flock($fp, LOCK_UN);
    fclose($fp);

    if ( ! is_int($result))
    {
        @unlink($cache_path);
        log_message('error', 'Unable to write the complete cache content at: '.$cache_path);
        return;
    }

    chmod($cache_path, 0640);
    log_message('debug', 'Cache file written: '.$cache_path);

    // Send HTTP cache-control headers to browser to match file cache settings.
    $this->set_cache_header($_SERVER['REQUEST_TIME'], $expire);
}

// --------------------------------------------------------------------

/**
 * Update/serve cached output
 *
 * @uses    CI_Config
 * @uses    CI_URI
 *
 * @param   object  &$CFG   CI_Config class instance
 * @param   object  &$URI   CI_URI class instance
 * @return  bool    TRUE on success or FALSE on failure
 */
public function _display_cache(&$CFG, &$URI)
{
    //-XXX CUSTOM------------------------------------
    $cache_path = $this->cachePath($CFG);
    //$cache_path = ($CFG->item('cache_path') === '') ? APPPATH.'cache/' : $CFG->item('cache_path');
    //-----------------------------------------------

    // Build the file path. The file name is an MD5 hash of the full URI
    $uri = $CFG->item('base_url').$CFG->item('index_page').$URI->uri_string;

    if (($cache_query_string = $CFG->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
    {
        if (is_array($cache_query_string))
        {
            $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
        }
        else
        {
            $uri .= '?'.$_SERVER['QUERY_STRING'];
        }
    }

    $filepath = $cache_path.md5($uri);

    if ( ! file_exists($filepath) OR ! $fp = @fopen($filepath, 'rb'))
    {
        return FALSE;
    }

    flock($fp, LOCK_SH);

    $cache = (filesize($filepath) > 0) ? fread($fp, filesize($filepath)) : '';

    flock($fp, LOCK_UN);
    fclose($fp);

    // Look for embedded serialized file info.
    if ( ! preg_match('/^(.*)ENDCI--->/', $cache, $match))
    {
        return FALSE;
    }

    $cache_info = unserialize($match[1]);
    $expire = $cache_info['expire'];

    $last_modified = filemtime($filepath);

    // Has the file expired?
    if ($_SERVER['REQUEST_TIME'] >= $expire && is_really_writable($cache_path))
    {
        // If so we'll delete it.
        @unlink($filepath);
        log_message('debug', 'Cache file has expired. File deleted.');
        return FALSE;
    }

    // Send the HTTP cache control headers
    $this->set_cache_header($last_modified, $expire);

    // Add headers from cache file.
    foreach ($cache_info['headers'] as $header)
    {
        $this->set_header($header[0], $header[1]);
    }

    //-XXX CUSTOM------------------------------------
    $exTime = $this->executionTime();
    setcookie('exe_time', "$exTime", time()+120, '/');
    //-----------------------------------------------

    // Display the cache
    $this->_display(self::substr($cache, self::strlen($match[0])));
    log_message('debug', 'Cache file is current. Sending it to browser.');
    return TRUE;
}

// --------------------------------------------------------------------

/**
 * Delete cache
 *
 * @param   string  $uri    URI string
 * @return  bool
 */
public function delete_cache($uri = '')
{
    $CI =& get_instance();
    //-XXX CUSTOM------------------------------------
    $cache_path = $CI->config->item('cache_path');
    $cache_path = ($cache_path === '') ? APPPATH.'cache/' : $cache_path;
    //-----------------------------------------------


    if ( ! is_dir($cache_path))
    {
        log_message('error', 'Unable to find cache path: '.$cache_path);
        return FALSE;
    }

    if (empty($uri))
    {
        $uri = $CI->uri->uri_string();

        if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING']))
        {
            if (is_array($cache_query_string))
            {
                $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string)));
            }
            else
            {
                $uri .= '?'.$_SERVER['QUERY_STRING'];
            }
        }
    }


    //-XXX CUSTOM------------------------------------
    $passed = TRUE;
    $path1 = $cache_path.'xmember/'.md5($CI->config->item('base_url').$CI->config->item('index_page').ltrim($uri, '/'));
    if ( ! @unlink($path1))
    {
        log_message('error', 'Unable to delete cache file for '.$uri);
        $passed = FALSE;
    }

    $path2 = $cache_path.'xvisitor/'.md5($CI->config->item('base_url').$CI->config->item('index_page').ltrim($uri, '/'));
    if ( ! @unlink($path2))
    {
        log_message('error', 'Unable to delete cache file for '.$uri);
        $passed = FALSE;
    }
    //-----------------------------------------------

    return $passed;
}


private function cachePath(&$CFG=false)
{
    $hasSession = !empty($_COOKIE[COOKIE_CUSTOMER_SESSION_ID]);

    if(empty($CFG)) {
        $CI =& get_instance();
        $CFG = $CI->config;
    }
    $path = $CFG->item('cache_path');
    $path = empty($path) ? APPPATH.'cache/' : $path;
    $path .= $hasSession?'xmember/':'xvisitor/';

    return $path;
}

function executionTime()
{
    $time = microtime();
    $time = explode(' ', $time);
    $time = $time[1] + $time[0];
    $total_time = round(($time - LOAD_PAGE_START), 4); //second unit
    return $total_time;
}

}

Upvotes: 1

SwiftD
SwiftD

Reputation: 6079

I'm sure someone will correct me if I'm wrong but I believe codeigniter caching only allows the caching of full pages. There are some additional libraries which enable partial page caching, check out phil sturgeons effort here: http://getsparks.org/packages/cache/show

My personal approach is to not bother with page caching and just use database caching which is more selective - but if you want/need page caching then I think the above is the only way to go

Upvotes: 3

Related Questions