etsis_Cache_Cookie ()
eduTrac SIS Cookie Cache Class.
Source file: app/src/Core/Cache/etsis_Cache_Cookie.php
Methods
- __construct
- _exists — Serves as a utility method to determine whether a key exists in the cache.
- cacheHits — Counts the number of cache hits and writes it to a file.
- cacheMisses — Counts the number of cache misses and writes it to a file.
- create — Creates the cache file.
- dec — Decrements numeric cache item's value.
- delete — Deletes a cache file based on unique key.
- flush — Flushes the file system cache completely.
- flushNamespace — Removes all cache items from a particular namespace.
- getStats — Echoes the stats of the cache.
- inc — Increments numeric cache item's value.
- keyToPath — Retrieve the cache file.
- read — Fetches cached data.
- remove_dir — Deletes cache/namespace directory.
- removeStaleCache — Removes any and all stale items from the cache.
- set — Sets the data contents into the cache.
- uniqueKey — Generates a unique cache key.
- update — Updates a cache file based on unique ID.
Source
class etsis_Cache_Cookie extends \app\src\Core\Cache\etsis_Abstract_Cache { /** * Holds the application object. * * @since 6.2.0 * @var object */ public $app; /** * Cache directory object. * * @since 6.2.0 * @var string */ protected $_dir; /** * Holds the cached objects. * * @since 6.2.0 * @var array */ protected $_cache = []; /** * Holds the memory limit object. * * @since 6.2.0 * @var int */ protected $_memory_limit; /** * Holds the memory limit object * * @since 6.2.0 * @var int */ protected $_memory_low; /** * Should the cache persist or not. * * @since 6.2.0 * @var bool */ public $persist; /** * Sets if cache is enabled or not. * * @since 6.2.0 * @var bool */ public $enable; public function __construct(\Liten\Liten $liten = null) { $this->app = !empty($liten) ? $liten : \Liten\Liten::getInstance(); if (ETSIS_FILE_CACHE_LOW_RAM && function_exists('memory_get_usage')) { $limit = _trim(ini_get('memory_limit')); $mod = strtolower($limit[strlen($limit) - 1]); switch ($mod) { case 'g': $limit *= 1073741824; break; case 'm': $limit *= 1048576; break; case 'k': $limit *= 1024; break; } if ($limit <= 0) { $limit = 0; } $this->_memory_limit = $limit; $limit = _trim(ETSIS_FILE_CACHE_LOW_RAM); $mod = strtolower($limit[strlen($limit) - 1]); switch ($mod) { case 'g': $limit *= 1073741824; break; case 'm': $limit *= 1048576; break; case 'k': $limit *= 1024; break; } $this->_memory_low = $limit; } else { $this->_memory_limit = 0; $this->_memory_low = 0; } /** * Filter sets whether caching is enabled or not. * * @since 6.2.0 * @var bool */ $this->enable = $this->app->hook->apply_filter('enable_caching', true); $this->persist = $this->enable && true; /** * File system cache directory. */ $dir = $this->app->config('file.savepath') . 'cache'; /** * Fiter the file cache directory in order to override it * in case some systems are having issues. * * @since 6.2.0 * @param string $dir * The directory where file system cache files are saved. */ $cacheDir = $this->app->hook->apply_filter('filesystem_cache_dir', $dir); /** * If the cache directory does not exist, the create it first * before trying to call it for use. */ if (!is_dir($cacheDir) || !etsis_file_exists($cacheDir, false)) { try { _mkdir($cacheDir); } catch (IOException $e) { Cascade::getLogger('error')->error(sprintf('IOSTATE[%s]: Forbidden: %s', $e->getCode(), $e->getMessage())); } catch (Exception $e) { Cascade::getLogger('error')->error(sprintf('IOSTATE[%s]: Forbidden: %s', $e->getCode(), $e->getMessage())); } } /** * If the directory isn't writable, throw an exception. */ if (!etsis_is_writable($cacheDir)) { throw new IOException(_t('Could not create the file cache directory.')); } /** * Cache directory is set. */ $this->_dir = $cacheDir . DS; } /** * Creates the cache file. * * {@inheritDoc} * * @see \app\src\Cache\etsis_Abstract_Cache::create() * * @since 6.2.0 * @param int|string $key * Unique key of the cache file. * @param mixed $data * Data that should be cached. * @param string $namespace * Optional. Where to namespace the cache contents. Default: 'default'. * @param int $ttl * Time to live sets the life of the cache file. Default: 0 = expires immediately after request. */ public function create($key, $data, $namespace = 'default', $ttl = 0) { if (!$this->enable) { return false; } if (empty($namespace)) { $namespace = 'default'; } return $this->set($key, $data, $namespace, (int) $ttl); } /** * Fetches cached data. * * {@inheritDoc} * * @see \app\src\Cache\etsis_Abstract_Cache::read() * * @since 6.2.0 * @param int|string $key * Unique key of the cache file. * @param string $namespace * Optional. Where the cache contents are namespaced. Default: 'default'. */ public function read($key, $namespace = 'default') { if (!$this->enable) { return false; } if (empty($namespace)) { $namespace = 'default'; } $x = isset($this->app->req->cookie[md5($key)]) ? $this->app->req->cookie[md5($key)] : false; if ($x == false) { $this->cacheMisses(); return null; } else { if (!$this->_exists($key, $namespace)) { $this->cacheMisses(); return false; } if (isset($this->_cache[$namespace], $this->_cache[$namespace][$key])) { $this->cacheHits(); return $this->_cache[$namespace][$key]; } $this->cacheHits(); $filename = $this->keyToPath($key, $namespace); } $get_data = _file_get_contents($filename, LOCK_EX); $data = maybe_unserialize($get_data); if ($this->persist) { if ($this->_memory_limit) { $usage = memory_get_usage(); if ($this->_memory_limit - $usage < $this->_memory_low) { $this->cacheMisses(); return false; } } $files = glob($filename); if (empty($files) || !isset($files[0])) { $this->cacheMisses(); return false; } if (is_readable($files[0])) { $result = $files[0]; $time = $data[0] - file_mod_time($result); $now = time(); if ((file_mod_time($result) + $time < $now)) { $this->cacheMisses(); unlink($result); return false; } if ((file_mod_time($result) + $time > $now)) { $this->cacheHits(); settype($result, 'string'); $this->_cache[$namespace][$key] = $data[1]; $result = $this->_cache[$namespace][$key]; return is_object($result) ? clone ($result) : $result; } } $this->app->cookies->remove(md5($key)); unlink($files[0]); } } /** * Updates a cache file based on unique ID. * This method only exists for * CRUD completeness purposes and just basically calls the create method. * * {@inheritDoc} * * @see \app\src\Cache\etsis_Abstract_Cache::update() * * @since 6.2.0 * @param int|string $key * Unique key of the cache file. * @param mixed $data * Data that should be cached. * @param string $namespace * Optional. Where to namespace the cache contents. Default: 'default'. * @param int $ttl * Time to live sets the life of the cache file. Default: no expiration. */ public function update($key, $data, $namespace = 'default', $ttl = 0) { if (!$this->enable) { return false; } if (empty($namespace)) { $namespace = 'default'; } return $this->create($key, $data, $namespace, (int) $ttl); } /** * Deletes a cache file based on unique key. * * {@inheritDoc} * * @see \app\src\Cache\etsis_Abstract_Cache::delete() * * @since 6.2.0 * @param int|string $key * Unique key of cache file. * @param string $namespace * Optional. Where to namespace the cache contents. Default: 'default'. * @return bool Returns true if the cache was deleted or false otherwise. */ public function delete($key, $namespace = 'default') { if (empty($namespace)) { $namespace = 'default'; } unset($this->_cache[$namespace][$key]); if (!$this->_exists($key, $namespace)) { return false; } $this->app->cookies->remove(md5($key)); $filename = $this->keyToPath($key, $namespace); return rename($filename, $filename . $this->inc($key, 1, $namespace)); } /** * Flushes the file system cache completely. * * {@inheritDoc} * * @see \app\src\Cache\etsis_Abstract_Cache::flush() * * @since 6.2.0 * @return bool Returns true if the cache was purged or false otherwise. */ public function flush() { $this->remove_dir($this->_dir); $this->_cache = []; return true; } /** * Removes all cache items from a particular namespace. * * {@inheritDoc} * * @see \app\src\Cache\etsis_Abstract_Cache::flushNamespace() * * @since 6.2.0 * @param int|string $namespace * Optional. Where the cache contents are namespaced. Default: 'default'. * @return bool Returns true if the namespace was purged or false otherwise. */ public function flushNamespace($namespace = 'default') { if (empty($namespace)) { $namespace = 'default'; } $dir = $this->_dir . $namespace; $this->remove_dir($dir); return true; } /** * Sets the data contents into the cache. * * {@inheritDoc} * * @see \app\src\Cache\etsis_Abstract_Cache::set() * * @since 6.2.0 * @param int|string $key * Unique key of the cache file. * @param mixed $data * Data that should be cached. * @param string $namespace * Optional. Where the cache contents are namespaced. Default: 'default'. * @param int $ttl * Time to live sets the life of the cache file. Default: 0 = expires immediately after request. * @return bool Returns true if the cache was set and false otherwise. */ public function set($key, $data, $namespace = 'default', $ttl = 0) { if (!$this->enable) { return false; } if (empty($namespace)) { $namespace = 'default'; } /** * Removes any and all stale items from the cache before * adding more items to the specified namespace. */ $this->removeStaleCache($key, $namespace, (int) $ttl); if ($this->_memory_limit) { $usage = memory_get_usage(); if ($this->_memory_limit - $usage < $this->_memory_low) { $this->app->cookies->remove(md5($key)); unlink($this->keyToPath($key, $namespace)); return false; } } if (is_object($data)) { $data = clone ($data); } $this->_cache[$namespace][$key] = $data; $filename = $this->keyToPath($key, $namespace); if ($this->_exists($key, $namespace)) { return false; } // Opening the file in read/write mode $h = fopen($filename, 'a+'); // If there is an issue with the handler, throw an exception. if (!$h) { throw new Exception(_t('Could not write to cache'), 'cookie_cache'); } $this->app->cookies->set(md5($key), $key, $ttl); // exclusive lock, will get released when the file is closed flock($h, LOCK_EX); // go to the start of the file fseek($h, 0); // truncate the file ftruncate($h, 0); // Serializing along with the TTL $data = maybe_serialize(array( time() + (int) $ttl, $data )); if (fwrite($h, $data) === false) { throw new IOException(_t('Could not write to cache')); } fclose($h); return true; } /** * Echoes the stats of the cache. * * Gives the cache hits, cache misses and cache uptime. * * @since 6.2.0 */ public function getStats() { if (!$this->enable) { return false; } echo "<p>"; echo "<strong>" . _t('Cache Hits:') . "</strong> " . _file_get_contents($this->_dir . 'cache_hits.txt') . "<br />"; echo "<strong>" . _t('Cache Misses:') . "</strong> " . _file_get_contents($this->_dir . 'cache_misses.txt') . "<br />"; echo "<strong>" . _t('Uptime:') . "</strong> " . timeAgo(file_mod_time($this->_dir)) . "<br />"; echo "</p>"; } /** * Increments numeric cache item's value. * * {@inheritDoc} * * @see \app\src\Cache\etsis_Abstract_Cache::inc() * * @since 6.2.0 * @param int|string $key * The cache key to increment * @param int $offset * Optional. The amount by which to increment the item's value. Default: 1. * @param string $namespace * Optional. The namespace the key is in. Default: 'default'. * @return false|int False on failure, the item's new value on success. */ public function inc($key, $offset = 1, $namespace = 'default') { if (empty($namespace)) { $namespace = 'default'; } if (!$this->_exists($key, $namespace)) { return false; } if (!is_numeric($this->_cache[$namespace][$key])) { $this->_cache[$namespace][$key] = 0; } $offset = (int) $offset; $this->_cache[$namespace][$key] += $offset; if ($this->_cache[$namespace][$key] < 0) { $this->_cache[$namespace][$key] = 0; } return $this->_cache[$namespace][$key]; } /** * Decrements numeric cache item's value. * * {@inheritDoc} * * @see \app\src\Cache\etsis_Abstract_Cache::dec() * * @since 6.2.0 * @param int|string $key * The cache key to decrement. * @param int $offset * Optional. The amount by which to decrement the item's value. Default: 1. * @param string $namespace * Optional. The namespace the key is in. Default: 'default'. * @return false|int False on failure, the item's new value on success. */ public function dec($key, $offset = 1, $namespace = 'default') { if (empty($namespace)) { $namespace = 'default'; } if (!$this->_exists($key, $namespace)) { return false; } if (!is_numeric($this->_cache[$namespace][$key])) { $this->_cache[$namespace][$key] = 0; } $offset = (int) $offset; $this->_cache[$namespace][$key] -= $offset; if ($this->_cache[$namespace][$key] < 0) { $this->_cache[$namespace][$key] = 0; } return $this->_cache[$namespace][$key]; } /** * Generates a unique cache key. * * {@inheritDoc} * * @see \app\src\Cache\etsis_Abstract_Cache::uniqueKey() * * @since 6.2.0 * @access protected * @param int|string $key * Unique key for cache file. * @param string $namespace * Optional. Where the cache contents are namespaced. Default: 'default'. */ protected function uniqueKey($key, $namespace = 'default') { if (empty($namespace)) { $namespace = 'default'; } return $this->_cache[$namespace][$key] = $namespace . ':' . $key; } /** * Serves as a utility method to determine whether a key exists in the cache. * * {@inheritDoc} * * @see \app\src\Cache\etsis_Abstract_Cache::_exists() * * @since 6.2.0 * @access protected * @param int|string $key * Cache key to check for existence. * @param string $namespace * Cache namespace for the key existence check. * @return bool Whether the cache item exists in the cache for the given key and namespace. */ protected function _exists($key, $namespace) { if (empty($namespace)) { $namespace = 'default'; } if (is_readable($this->keyToPath($key, $namespace))) { return true; } } /** * Deletes cache/namespace directory. * * @since 6.2.0 * @param string $dir * Directory that should be removed. */ protected function remove_dir($dir) { if (!is_dir($dir)) { return; } $dh = opendir($dir); if (!is_resource($dh)) { return; } _rmdir($dir); closedir($dh); } /** * Counts the number of cache hits * and writes it to a file. * * @since 6.2.0 */ protected function cacheHits() { $filename = $this->_dir . 'cache_hits.txt'; if (!is_readable($filename)) { $fp = fopen($filename, 'w'); fwrite($fp, 0); fclose($fp); return false; } $fp = fopen($filename, 'c+'); flock($fp, LOCK_EX); $count = (int) fread($fp, filesize($filename)); ftruncate($fp, 0); fseek($fp, 0); fwrite($fp, $count + 1); flock($fp, LOCK_UN); fclose($fp); } /** * Counts the number of cache misses * and writes it to a file. * * @since 6.2.0 */ protected function cacheMisses() { $filename = $this->_dir . 'cache_misses.txt'; if (!is_readable($filename)) { $fp = fopen($filename, 'w'); fwrite($fp, 0); fclose($fp); return false; } $fp = fopen($filename, 'c+'); flock($fp, LOCK_EX); $count = (int) fread($fp, filesize($filename)); ftruncate($fp, 0); fseek($fp, 0); fwrite($fp, $count + 1); flock($fp, LOCK_UN); fclose($fp); } /** * Removes any and all stale items from the cache. * * @since 6.2.0 * @param int|string $key * Unqiue key of cache. * @param int|string $namespace * Optional. Where the cache contents are namespaced. Default: 'default'. * @param int $ttl * Time to live sets the life of the cache file. Default: 0. */ protected function removeStaleCache($key, $namespace = 'default', $ttl = 0) { if (empty($namespace)) { $namespace = 'default'; } if (!$this->_exists($key, $namespace)) { $this->app->cookies->remove(md5($key)); } $stale = glob($this->_dir . $namespace . DS . '*'); if (is_array($stale)) { foreach ($stale as $filename) { if (etsis_file_exists($filename, false)) { if (time() - file_mod_time($filename) > (int) $ttl) { unlink($filename); } } } } } /** * Retrieve the cache file. * * @since 6.2.0 * @access protected * @param int|string $key * Unqiue key of cache. * @param int|string $namespace * Optional. Where the cache contents are namespaced. Default: 'default'. */ private function keyToPath($key, $namespace) { $dir = $this->_dir . urlencode($namespace); if (!etsis_file_exists($dir, false)) { try { _mkdir($dir); } catch (IOException $e) { Cascade::getLogger('error')->error(sprintf('IOSTATE[%s]: Forbidden: %s', $e->getCode(), $e->getMessage())); return; } catch (Exception $e) { Cascade::getLogger('error')->error(sprintf('IOSTATE[%s]: Forbidden: %s', $e->getCode(), $e->getMessage())); return; } } return $this->_dir . urlencode($namespace) . DS . urlencode(md5($key)); } }
Expand full source code Collapse full source code View on Github
Changelog
- Since: eduTrac SIS 6.2.0