null, 'enable_purge' => true, ]; $this->config = array_merge($default, $config); if (!$this->config['path']) { throw new \Exception('The FileCache needs a path value'); } // Normalize with a single trailing slash $this->config['path'] = rtrim($this->config['path'], '/') . '/'; } public function getConfig() { return $this->config; } public function loadData(int $timeout = 86400) { clearstatcache(); if (!file_exists($this->getCacheFile())) { return null; } $modificationTime = filemtime($this->getCacheFile()); if (time() - $timeout < $modificationTime) { $data = unserialize(file_get_contents($this->getCacheFile())); if ($data === false) { Logger::warning(sprintf('Failed to unserialize: %s', $this->getCacheFile())); // Intentionally not throwing an exception return null; } return $data; } // It's a good idea to delete the expired item here, but commented out atm // unlink($this->getCacheFile()); return null; } public function saveData($data): void { $bytes = file_put_contents($this->getCacheFile(), serialize($data), LOCK_EX); if ($bytes === false) { throw new \Exception(sprintf('Failed to write to: %s', $this->getCacheFile())); } } public function getTime(): ?int { clearstatcache(); $cacheFile = $this->getCacheFile(); if (file_exists($cacheFile)) { $time = filemtime($cacheFile); if ($time !== false) { return $time; } return null; } return null; } public function purgeCache(int $timeout = 86400): void { if (! $this->config['enable_purge']) { return; } $cachePath = $this->getScope(); if (!file_exists($cachePath)) { return; } $cacheIterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($cachePath), \RecursiveIteratorIterator::CHILD_FIRST ); foreach ($cacheIterator as $cacheFile) { $basename = $cacheFile->getBasename(); $excluded = [ '.' => true, '..' => true, '.gitkeep' => true, ]; if (isset($excluded[$basename])) { continue; } elseif ($cacheFile->isFile()) { $filepath = $cacheFile->getPathname(); if (filemtime($filepath) < time() - $timeout) { // todo: sometimes this file doesn't exists unlink($filepath); } } } } public function setScope(string $scope): void { $this->scope = $this->config['path'] . trim($scope, " \t\n\r\0\x0B\\\/") . '/'; } public function setKey(array $key): void { $this->key = json_encode($key); } private function getScope() { if (is_null($this->scope)) { throw new \Exception('Call "setScope" first!'); } if (!is_dir($this->scope)) { if (mkdir($this->scope, 0755, true) !== true) { throw new \Exception('mkdir: Unable to create file cache folder'); } } return $this->scope; } private function getCacheFile() { return $this->getScope() . $this->getCacheName(); } private function getCacheName() { if (is_null($this->key)) { throw new \Exception('Call "setKey" first!'); } return hash('md5', $this->key) . '.cache'; } }