<?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";
 
    }
 
}
 
 |