1<?php 2 3/* 4 * This file is part of Twig. 5 * 6 * (c) Fabien Potencier 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Twig\Cache; 13 14/** 15 * Implements a cache on the filesystem. 16 * 17 * @author Andrew Tch <andrew@noop.lv> 18 */ 19class FilesystemCache implements CacheInterface, RemovableCacheInterface 20{ 21 public const FORCE_BYTECODE_INVALIDATION = 1; 22 23 private $directory; 24 private $options; 25 26 public function __construct(string $directory, int $options = 0) 27 { 28 $this->directory = rtrim($directory, '\/').'/'; 29 $this->options = $options; 30 } 31 32 public function generateKey(string $name, string $className): string 33 { 34 $hash = hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $className); 35 36 return $this->directory.$hash[0].$hash[1].'/'.$hash.'.php'; 37 } 38 39 public function load(string $key): void 40 { 41 if (is_file($key)) { 42 @include_once $key; 43 } 44 } 45 46 public function write(string $key, string $content): void 47 { 48 $dir = \dirname($key); 49 if (!is_dir($dir)) { 50 if (false === @mkdir($dir, 0777, true)) { 51 clearstatcache(true, $dir); 52 if (!is_dir($dir)) { 53 throw new \RuntimeException(\sprintf('Unable to create the cache directory (%s).', $dir)); 54 } 55 } 56 } elseif (!is_writable($dir)) { 57 throw new \RuntimeException(\sprintf('Unable to write in the cache directory (%s).', $dir)); 58 } 59 60 $tmpFile = tempnam($dir, basename($key)); 61 if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $key)) { 62 @chmod($key, 0666 & ~umask()); 63 64 if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) { 65 // Compile cached file into bytecode cache 66 if (\function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) { 67 @opcache_invalidate($key, true); 68 } elseif (\function_exists('apc_compile_file')) { 69 apc_compile_file($key); 70 } 71 } 72 73 return; 74 } 75 76 throw new \RuntimeException(\sprintf('Failed to write cache file "%s".', $key)); 77 } 78 79 public function remove(string $name, string $cls): void 80 { 81 $key = $this->generateKey($name, $cls); 82 if (!@unlink($key) && file_exists($key)) { 83 throw new \RuntimeException(\sprintf('Failed to delete cache file "%s".', $key)); 84 } 85 } 86 87 public function getTimestamp(string $key): int 88 { 89 if (!is_file($key)) { 90 return 0; 91 } 92 93 return (int) @filemtime($key); 94 } 95} 96