123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 |
- <?php
- namespace 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);
- }
- }
|