Reputation: 9919
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
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
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