<?php
/**
* MultiCache class class provides a convenient way to work with caches.
* MultiCacheFile is a class for work with file system storage.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*/
class MultiCacheFile extends MultiCache {
/**
* File cache directory.
*
* @var string
*/
public $cachedir = 'cache';
/**
* Number of cache subderictories.
*
* @var string
*/
public $subdircount = 1;
/**
* Length of cache subderictories.
*
* @var string
*/
public $subdirlength = 2;
/**
* Cache statistics. The structure is: array(count, size).
*
* @var array Cache statistics
*/
private $stats = null;
/**
* Gets data.
*
* @param mixed $key The key that will be associated with the item.
* @param mixed $default Default value.
*
* @return mixed Stored data.
*/
public function get($key, $default = null) {
// Get file name
$fname = $this->getPathByKey($key);
// Read file
if (($data = @file_get_contents($fname)) && ($data = @unserialize($data))) {
list($value, $expire) = $data;
if ($expire > 0 && $expire < time()) {
$this->remove($key);
} else {
return $value;
}
}
return $default;
}
/**
* Stores data.
* If expiration time set in seconds it must be not greater then 2592000 (30 days).
*
* @param string $key The key that will be associated with the item.
* @param mixed $value The variable to store.
* @param integer $expire Expiration time of the item. Unix timestamp
* or number of seconds.
*/
public function set($key, $value, $expire = null) {
parent::set($key, $value, $expire);
// Get file name
$fname = $this->getPathByKey($key, true);
if ($expire > 0 && $expire <= 2592000) {
$expire = time() + $expire;
}
// Create file and save new data
if (!($fh = fopen($fname, 'wb'))) {
throw new Exception("File $fname not created!");
}
flock($fh, LOCK_EX);
fwrite($fh, serialize(array($value, $expire)));
flock($fh, LOCK_UN);
fclose($fh);
}
/**
* Removes data from the cache.
*
* @param string $key The key that will be associated with the item.
*/
public function remove($key) {
// Get file name
$fname = $this->getPathByKey($key);
// Delete file
if (is_file($fname)) {
if (!unlink($fname)) {
throw new Exception("File $fname not deleted!");
}
if ($this->stats && $this->stats[0] > 0) {
$this->stats[0]--;
}
}
}
/**
* Removes all cached data.
*/
public function removeAll() {
self::rmdir($this->cachedir);
$this->stats = null;
}
/**
* Cleans expired cached data.
*/
public function clean() {
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->cachedir)) as $file) {
$this->get(@base64_decode(basename($file)));
}
$this->stats = null;
}
/**
* Gets items count.
*
* @return integer Items count.
*/
public function getItemsCount() {
if ($this->stats != null) {
$this->stats = $this->getStats();
}
return $this->stats[0];
}
/**
* Gets cached data size.
*
* @return integer Cache size, bytes.
*/
public function getSize() {
if ($this->stats != null) {
$this->stats = $this->getStats();
}
return $this->stats[1];
}
/**
* Gets cache statistics.
*
* @return array Cache statistics.
*/
private function getStats() {
$cnt = 0;
$size = 0;
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->cachedir)) as $file) {
$cnt++;
$size += filesize($file);
}
return array($cnt, $size);
}
/**
* Removes all files and subdirectories.
*
* @param string $dir Directory name.
*/
private static function rmdir($dir) {
$dir = new RecursiveDirectoryIterator($dir);
foreach (new RecursiveIteratorIterator($dir) as $file) {
@unlink($file);
}
foreach($dir as $subDir) {
if(!@rmdir($subDir)) {
self::rmdir($subDir);
@rmdir($subDir);
}
}
}
/**
* Gets file path by key.
*
* @param string $key The key that will be associated with the item.
* @param boolean $ismkdir If true this function creates new subdirectories.
*
* @return string File path.
*/
private function getPathByKey($key, $ismkdir = false) {
$fname = $fcode = base64_encode($key);
if (strlen($fname) > 250) {
throw new Exception("Hash for key [$key] is bigger then 250 characters!");
}
$dir = $this->cachedir;
$len = $this->subdirlength;
for ($i = $this->subdircount; $i > 0; $i--) {
$dcode = substr($fcode, 0, $len);
if (strlen($dcode) < $len) {
break;
}
$dir .= "/$dcode";
if ($ismkdir && !is_dir($dir) && !mkdir($dir, 0777)) {
throw new Exception("Directory $dir not created!");
}
$fcode = substr($fcode, $len);
}
return "$dir/$fname";
}
}
|