📜 ⬆️ ⬇️

Primary cache in Kohana 3 using tags

The given example is the result of solving a single task that arose when developing a site management system on the Kohana 3.1 framework, in which one administrator account and many unregistered readers are assumed.

It took a long time to cache the results of the work of the model methods that access the database. In fact, it was required to create copies of data sets from the database in order to reduce the load on the DBMS. To immediately update the cache when adding new data or updating old ones, it was necessary to clear the cache by tags.

Considering all this, and due to the limitations of the used hosting, the requirements were as follows:

The standard class Cache_File does not support tags, for this reason it was necessary to write its own class, it was given the name JetCache .
')
The class is designed on the template "loner". Consider the example of the class in the model for the file bank. During model initialization, an instance is created:

 $this->cache = JetCache::instance(); 


The creation of the data cache will be considered on the example of a function for extracting a list of files of a specific rubric (here some arguments and some code are removed from it for easier reading):

  //    /** * * @param int $rubricId Id    * @return array */ public function getFiles($rubricId) { //!!!  ,      , //   $key = 'filebank_get_files'.$rubricId; $arResult = $this->cache->get($key); if (is_array($arResult)) { return $arResult; } $arResult = array(); $arParams = array(); $arParams[':rubricId'] = $rubricId; $query = " SELECT * FROM `filebank_files` WHERE `rubric_id`=:rubricId ORDER BY `time` DESC, `name` ASC "; $arResult['files'] = DB::query(Database::SELECT, $query) ->parameters($arParams) ->execute() ->as_array(); //!!!      $this->cache->set($key, $arResult, array('filebank_rubrics', 'filebank_files')); return $arResult; } 


Thus, an entry to the cache is made with the key $key = 'filebank_get_files'.$rubricId and the tags “filebank_rubrics” and “filebank_files”, that is, this entry is required when updating information about rubrics and files directly.

For an example of clearing the cache by tags, consider the function for deleting a rubric. The cacheRegExp property contains a regular expression for the names of the files (keys) from which to extract tags for verification. That is, the double check: first check the file name by a regular expression, then check the tags.

  protected $cacheRegExp = '/^filebank/'; //  public function delRubric($rubricId) { $query = ' SELECT `name` FROM `filebank_files` WHERE `rubric_id`=:rubricId '; $arFiles = DB::query(Database::SELECT, $query) ->param(':rubricId', $rubricId) ->execute() ->as_array(); $arFiles = Arr::path($arFiles, '*.name'); $this->delFiles($arFiles); $query = ' DELETE FROM `filebank_rubrics` WHERE `rubric_id`=:rubricId '; DB::query(Database::DELETE, $query) ->param(':rubricId', $rubricId) ->execute(); //!!!     $tags = array('filebank_rubrics', 'filebank_files'); $this->cache->delete_by_tags($tags, $this->cacheRegExp); } 

That is, after deleting a rubric, the data cache associated with rubrics and rubric positions is cleared.

Thus, the primary long-term data caching feature has been added. At the controller level, you can also use short-term caching using the standard “Cache” module with a file driver.

In the case of large projects that require high speed to clear the cache or add information by users, of course, it is better to use special solutions. For example, the “Memcached-tag” or “Xcache” drivers of the “Cache” module. But for small sites administered by one person or a small group of people, when using hosting sites without providing special tools for caching, this solution fits well.

The files in which the cache is stored are contained in one directory and have the following structure:
, (unix timestamp)\n
\n
\n


Finally, I’ll give the full class code:
 <?php defined('SYSPATH') or die('No direct access allowed.'); class JetCache { protected static $instance = NULL; protected static $config; protected static $cache_dir; protected static $cache_time; public static function instance() { if (is_null(self::$instance)) { self::$instance = new self(); } return self::$instance; } protected function __construct() { self::$config = Kohana::config('jethelix')->default; self::$cache_dir = self::$config['jet_cache_dir']; if (!is_dir(self::$cache_dir)) { $oldUmask = umask(0000); if (!mkdir(self::$cache_dir, 0777, TRUE)) { $message = '    JetCache'; throw new Exception($message); } umask($oldUmask); } self::$cache_time = self::$config['jet_cache']; } protected function __clone() { } public function set($id, $data, array $tags=array(), $lifetime=NULL) { if (!$lifetime) { $lifetime = self::$cache_time; } $filename = self::$cache_dir . '/' . $id . '.txt'; $expires = time() + (int)$lifetime; $tagString = implode(',', $tags); $serData = serialize($data); $content = $expires . "\n" . $tagString . "\n" . $serData; try { file_put_contents($filename, $content); } catch (Exception $e) { return FALSE; } return TRUE; } public function get($id) { $filename = self::$cache_dir . '/' . $id . '.txt'; if (!is_file($filename)) { return NULL; } try { $content = file_get_contents($filename); } catch (Exception $e) { return NULL; } $arContent = explode("\n", $content); unset ($content); try { if ($arContent[0] < time()) { return NULL; } $data = unserialize($arContent[2]); return $data; } catch (Exception $e) { return NULL; } } public function delete($id) { $filename = self::$cache_dir . '/' . $id . '.txt'; try { unlink($filename); } catch (Exception $e) { return FALSE; } return TRUE; } public function garbage_collect() { $dir = opendir(self::$cache_dir); while ($file = readdir($dir)) { $fullName = self::$cache_dir . '/'. $file; if (!is_file($fullName)) { continue; } try { $this->_deleteIfExpires($fullName); } catch (Exception $e) { return FALSE; } } return TRUE; } protected function _deleteIfExpires($filename) { $fhandle = fopen($filename, 'r'); $expires = (int)fgets($fhandle); fclose($fhandle); if ($expires < time()) { unlink($filename); } } public function delete_by_tags(array $tags, $filenameRegExp=NULL) { $this->garbage_collect(); try { $arFiles = $this->_getTaggedFiles($tags, $filenameRegExp); $this->_deleteFiles($arFiles); } catch (Exception $e) { return FALSE; } return TRUE; } protected function _getTaggedFiles(array $needTags, $filenameRegExp) { $taggedFiles = array(); $dir = opendir(self::$cache_dir); while ($file = readdir($dir)) { $fullName = self::$cache_dir . '/' . $file; if (!is_file($fullName)) { continue; } if ($filenameRegExp && !preg_match($filenameRegExp, $file)) { continue; } $hasTags = $this->_getTagsFromFile($fullName); $isValid = $this->_tagsValidate($needTags, $hasTags); if ($isValid) { $taggedFiles[] = $fullName; } } return $taggedFiles; } protected function _getTagsFromFile($filename) { $fhandler = fopen($filename, 'r'); fgets($fhandler); $tagString = fgets($fhandler); fclose($fhandler); $tagString = trim($tagString); $arTags = explode(',', $tagString); return $arTags; } protected function _tagsValidate(array $needTags, array $hasTags) { foreach ($needTags as $tag) { if (in_array($tag, $hasTags)) { return TRUE; } } return FALSE; } protected function _deleteFiles(array $files) { foreach ($files as $filename) { unlink($filename); } } } 

Source: https://habr.com/ru/post/119588/


All Articles