| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 | <?phpnamespace Gregwar\Cache;/** * A cache system based on files * * @author Gregwar <g.passault@gmail.com> */class Cache implements CacheInterface{    /**     * Cache directory     */    protected $cacheDirectory;    /**     * Use a different directory as actual cache     * @var string     */    protected $actualCacheDirectory = null;    /**     * Prefix directories size     *     * For instance, if the file is helloworld.txt and the prefix size is     * 5, the cache file will be: h/e/l/l/o/helloworld.txt     *     * This is useful to avoid reaching a too large number of files into the     * cache system directories     * @var int     */    protected $prefixSize = 5;    /**     * Directory mode     *     * Allows setting of the access mode for the directories created.     * @var int     */    protected $directoryMode = 0755;    /**     * Constructs the cache system     *     * @param string $cacheDirectory the cache directory     */    public function __construct($cacheDirectory = 'cache')    {        $this->cacheDirectory = $cacheDirectory;    }    /**     * Sets the cache directory     *     * @param string $cacheDirectory the cache directory     * @return self     */    public function setCacheDirectory($cacheDirectory)    {        $this->cacheDirectory = $cacheDirectory;        return $this;    }    /**     * Gets the cache directory     *     * @return string the cache directory     */    public function getCacheDirectory()    {        return $this->cacheDirectory;    }    /**     * Sets the actual cache directory     *     * @param string $actualCacheDirectory the actual cache directory     * @return self     */    public function setActualCacheDirectory($actualCacheDirectory = null)    {        $this->actualCacheDirectory = $actualCacheDirectory;        return $this;    }    /**     * Returns the actual cache directory     */    public function getActualCacheDirectory()    {        return $this->actualCacheDirectory ?: $this->cacheDirectory;    }    /**     * Change the prefix size     *     * @param int $prefixSize the size of the prefix directories     * @return self     */    public function setPrefixSize($prefixSize)    {        $this->prefixSize = $prefixSize;        return $this;    }    /**     * Change the directory mode     *     * @param int $directoryMode the directory mode to use     * @return self     */    public function setDirectoryMode($directoryMode)    {        if (!$directoryMode) {            $directoryMode = 0755;        }        $this->directoryMode = $directoryMode;        return $this;    }    /**     * Creates a directory     *     * @param string $directory the target directory     */    protected function mkdir($directory)    {        if (!is_dir($directory)) {            @mkdir($directory, $this->directoryMode, true);        }    }    /**     * Gets the cache file name     *     * @param string $filename the name of the cache file     * @param bool $actual get the actual file or the public file     * @param bool $mkdir a boolean to enable/disable the construction of the     *        cache file directory     * @return string     */    public function getCacheFile($filename, $actual = false, $mkdir = false)    {        $path = array();        // Getting the length of the filename before the extension        $parts = explode('.', $filename);        $len = strlen($parts[0]);        for ($i=0; $i<min($len, $this->prefixSize); $i++) {            $path[] = $filename[$i];        }        $path = implode('/', $path);        if ($mkdir) {            $actualDir = $this->getActualCacheDirectory() . '/' . $path;            $this->mkdir($actualDir);        }        $path .= '/' . $filename;        if ($actual) {            return $this->getActualCacheDirectory() . '/' . $path;        } else {            return $this->getCacheDirectory() . '/' . $path;        }    }    /**     * Checks that the cache conditions are respected     *     * @param string $cacheFile the cache file     * @param array $conditions an array of conditions to check     * @return bool     * @throws \Exception     */    protected function checkConditions($cacheFile, array $conditions = array())    {        // Implicit condition: the cache file should exist        if (!file_exists($cacheFile)) {            return false;        }        foreach ($conditions as $type => $value) {            switch ($type) {            case 'maxage':            case 'max-age':                // Return false if the file is older than $value                $age = time() - filemtime($cacheFile);                if ($age > $value) {                    return false;                }                break;            case 'younger-than':            case 'youngerthan':                // Return false if the file is older than the file $value, or the files $value                $check = function($filename) use ($cacheFile) {                    return !file_exists($filename) || filemtime($cacheFile) < filemtime($filename);                };                if (!is_array($value)) {                    if (!$this->isRemote($value) && $check($value)) {                        return false;                    }                } else {                    foreach ($value as $file) {                        if (!$this->isRemote($file) && $check($file)) {                            return false;                        }                    }                }                break;            default:                throw new \Exception('Cache condition '.$type.' not supported');            }        }        return true;    }    /**     * Checks if the target filename exists in the cache and if the conditions     * are respected     *     * @param string $filename the filename     * @param array $conditions the conditions to respect     * @return bool     */    public function exists($filename, array $conditions = array())    {        $cacheFile = $this->getCacheFile($filename, true);        return $this->checkConditions($cacheFile, $conditions);    }    /**     * Alias for exists     *     * @param string $filename the filename     * @param array $conditions the conditions to respect     * @return bool     */    public function check($filename, array $conditions = array())    {        return $this->exists($filename, $conditions);    }    /**     * Write data in the cache     *     * @param string $filename the name of the cache file     * @param string $contents the contents to store     * @return self     */    public function set($filename, $contents = '')    {        $cacheFile = $this->getCacheFile($filename, true, true);        file_put_contents($cacheFile, $contents, \LOCK_EX);        return $this;    }    /**     * Alias for set()     *     * @param string $filename the name of the cache file     * @param string $contents the contents to store     * @return self     */    public function write($filename, $contents = '')    {        return $this->set($filename, $contents);    }    /**     * Get data from the cache     *     * @param string $filename the cache file name     * @param array $conditions     * @return null|string     */    public function get($filename, array $conditions = array())    {        if ($this->exists($filename, $conditions)) {            return file_get_contents($this->getCacheFile($filename, true));        } else {            return null;        }    }    /**     * Is this URL remote?     *     * @param string $file     * @return bool     */    protected function isRemote($file)    {        if (preg_match('/^([a-z]+):\/\//', $file, $match)) {            return ($match[1] != 'file');        }        return false;    }    /**     * Get or create the cache entry     *     * @param string $filename the cache file name     * @param array $conditions an array of conditions about expiration     * @param \Closure $function the closure to call if the file does not exist     * @param bool $file returns the cache file or the file contents     * @param bool $actual returns the actual cache file     * @return string     * @throws \InvalidArgumentException     */    public function getOrCreate($filename, array $conditions = array(), $function, $file = false, $actual = false)    {        if (!is_callable($function)) {            throw new \InvalidArgumentException('The argument $function should be callable');        }        $cacheFile = $this->getCacheFile($filename, true, true);        $data = null;        if (!$this->check($filename, $conditions)) {            if(file_exists($cacheFile)) {                unlink($cacheFile);            }            $data = call_user_func($function, $cacheFile);            // Test if the closure wrote the file or if it returned the data            if (!file_exists($cacheFile)) {                $this->set($filename, $data);            } else {                $data = file_get_contents($cacheFile);            }        }        return $file ? $this->getCacheFile($filename, $actual) : file_get_contents($cacheFile);    }    /**     * Alias to getOrCreate with $file = true     *     * @param string $filename the cache file name     * @param array $conditions an array of conditions about expiration     * @param \Closure $function the closure to call if the file does not exist     * @param bool $actual returns the actual cache file     * @return string     * @throws \InvalidArgumentException     */    public function getOrCreateFile($filename, array $conditions = array(), $function, $actual = false)    {        return $this->getOrCreate($filename, $conditions, $function, true, $actual);    }}
 |