setCacheDir($cacheDir); $this->setCachePath($storePath); $this->setTokenArray($cacheTokenArray); $this->lifetime = $cacheLifetime; } /** * Retrieve the cache lifetime for current query. * @return int|null lifetime in seconds (int) or no lifetime with null */ public function getLifetime() { return $this->lifetime; } /** * Retrieve the path to cache folder of current store. * @return string path to cache directory */ public function getCachePath(): string { return $this->cachePath; } /** * Retrieve the cache token used as filename to store cache file. * @return string unique token for current query. */ public function getToken(): string { $tokenArray = $this->getTokenArray(); $tokenArray = self::convertClosuresToString($tokenArray); return md5(json_encode($tokenArray)); } /** * Delete all cache files for current store. * @return bool */ public function deleteAll(): bool { return IoHelper::deleteFiles(glob($this->getCachePath()."*")); } /** * Delete all cache files with no lifetime (null) in current store. * @return bool */ public function deleteAllWithNoLifetime(): bool { $noLifetimeFileString = self::NO_LIFETIME_FILE_STRING; return IoHelper::deleteFiles(glob($this->getCachePath()."*.$noLifetimeFileString.json")); } /** * Save content for current query as a cache file. * @param array $content * @throws IOException if cache folder is not writable or saving failed. */ public function set(array $content){ $lifetime = $this->getLifetime(); $cachePath = $this->getCachePath(); $token = $this->getToken(); $noLifetimeFileString = self::NO_LIFETIME_FILE_STRING; $cacheFile = $cachePath . $token . ".$noLifetimeFileString.json"; if(is_int($lifetime)){ $cacheFile = $cachePath . $token . ".$lifetime.json"; } IoHelper::writeContentToFile($cacheFile, json_encode($content)); } /** * Retrieve content of cache file. * @return array|null array on success, else null * @throws IOException if cache file is not readable or does not exist. */ public function get(){ $cachePath = $this->getCachePath(); $token = $this->getToken(); $cacheFile = null; IoHelper::checkRead($cachePath); $cacheFiles = glob($cachePath.$token."*.json"); if($cacheFiles !== false && count($cacheFiles) > 0){ $cacheFile = $cacheFiles[0]; } if(!empty($cacheFile)){ $cacheParts = explode(".", $cacheFile); if(count($cacheParts) >= 3){ $lifetime = $cacheParts[count($cacheParts) - 2]; if(is_numeric($lifetime)){ if($lifetime === "0"){ return json_decode(IoHelper::getFileContent($cacheFile), true); } $fileExpiredAfter = filemtime($cacheFile) + (int) $lifetime; if(time() <= $fileExpiredAfter){ return json_decode(IoHelper::getFileContent($cacheFile), true); } IoHelper::deleteFile($cacheFile); } else if($lifetime === self::NO_LIFETIME_FILE_STRING){ return json_decode(IoHelper::getFileContent($cacheFile), true); } } } return null; } /** * Delete cache file/s for current query. * @return bool */ public function delete(): bool { return IoHelper::deleteFiles(glob($this->getCachePath().$this->getToken()."*.json")); } /** * @param string $storePath * @return Cache */ private function setCachePath(string $storePath): Cache { $cachePath = ""; $cacheDir = $this->getCacheDir(); if(!empty($storePath)){ IoHelper::normalizeDirectory($storePath); $cachePath = $storePath . $cacheDir; } $this->cachePath = $cachePath; return $this; } /** * Set the cache token array used for cache token string generation. * @param array $tokenArray * @return Cache */ private function setTokenArray(array &$tokenArray): Cache { $this->tokenArray = &$tokenArray; return $this; } /** * Retrieve the cache token array. * @return array */ private function getTokenArray(): array { return $this->tokenArray; } /** * Convert one or multiple closures to string. If array provided, recursively. * @param mixed $data * @return mixed */ private static function convertClosuresToString($data){ if(!is_array($data)){ if($data instanceof \Closure){ return self::getClosureAsString($data); } return $data; } foreach ($data as $key => $token){ if(is_array($token)){ $data[$key] = self::convertClosuresToString($token); } else if($token instanceof \Closure){ $data[$key] = self::getClosureAsString($token); } } return $data; } /** * Retrieve a string representation of a closure that can be used to differentiate between closures * when generating the cache token string. * @param Closure $closure * @return false|string string representation of closure or false on failure. */ private static function getClosureAsString(Closure $closure) { try{ $reflectionFunction = new ReflectionFunction($closure); // get reflection object } catch (Exception $exception){ return false; } $filePath = $reflectionFunction->getFileName(); // absolute path of php file containing function $startLine = $reflectionFunction->getStartLine(); // start line of function $endLine = $reflectionFunction->getEndLine(); // end line of function $lineSeparator = PHP_EOL; // line separator "\n" $staticVariables = $reflectionFunction->getStaticVariables(); $staticVariables = var_export($staticVariables, true); if($filePath === false || $startLine === false || $endLine === false){ return false; } $startEndDifference = $endLine - $startLine; $startLine--; // -1 to use it with the array representation of the file if($startLine < 0 || $startEndDifference < 0){ return false; } // get content of file containing function $fp = fopen($filePath, 'rb'); $fileContent = ""; if(flock($fp, LOCK_SH)){ $fileContent = @stream_get_contents($fp); } flock($fp, LOCK_UN); fclose($fp); if(empty($fileContent)){ return false; } // separate the file into an array containing every line as one element $fileContentArray = explode($lineSeparator, $fileContent); if(count($fileContentArray) < $endLine){ return false; } // return the part of the file containing the function as a string. $functionString = implode("", array_slice($fileContentArray, $startLine, $startEndDifference + 1)); $functionString .= "|staticScopeVariables:".$staticVariables; return $functionString; } /** * Set the cache directory name. * @param string $cacheDir * @return Cache */ private function setCacheDir(string $cacheDir): Cache { IoHelper::normalizeDirectory($cacheDir); $this->cacheDir = $cacheDir; return $this; } /** * Retrieve the cache directory name or the default cache directory name if empty. * @return string */ private function getCacheDir(): string { return (!empty($this->cacheDir)) ? $this->cacheDir : self::DEFAULT_CACHE_DIR; } }