관리-도구
편집 파일: FileSystemCache.php
<?php declare(strict_types=1); namespace CuyZ\Valinor\Cache; use CuyZ\Valinor\Cache\Exception\CacheDirectoryNotWritable; use CuyZ\Valinor\Cache\Exception\CompiledPhpCacheFileNotWritten; use CuyZ\Valinor\Cache\Exception\CorruptedCompiledPhpCacheFile; use Error; use FilesystemIterator; use function bin2hex; use function file_exists; use function file_put_contents; use function is_dir; use function mkdir; use function random_bytes; use function rename; use function rmdir; use function str_contains; use function umask; use function unlink; /** * @api * * @template EntryType * @implements Cache<EntryType> */ final class FileSystemCache implements Cache { private const GENERATED_MESSAGE = 'Generated by ' . self::class; public function __construct( private string $cacheDir, ) {} /** @internal */ public function get(string $key, mixed ...$arguments): mixed { $filename = $this->cacheDir . DIRECTORY_SEPARATOR . $key . '.php'; if (! file_exists($filename)) { return null; } try { return (require $filename)(...$arguments); // @phpstan-ignore callable.nonCallable } catch (Error) { throw new CorruptedCompiledPhpCacheFile($filename); } } /** @internal */ public function set(string $key, CacheEntry $entry): void { $tmpDir = $this->cacheDir . DIRECTORY_SEPARATOR . '.valinor.tmp'; $filename = $this->cacheDir . DIRECTORY_SEPARATOR . $key . '.php'; if (! is_dir($tmpDir) && ! @mkdir($tmpDir, 0777, true)) { throw new CacheDirectoryNotWritable($this->cacheDir); } /** @infection-ignore-all */ $tmpFilename = $tmpDir . DIRECTORY_SEPARATOR . bin2hex(random_bytes(16)); try { $code = '<?php // ' . self::GENERATED_MESSAGE . PHP_EOL . "return $entry->code;" . PHP_EOL; if (! @file_put_contents($tmpFilename, $code)) { throw new CompiledPhpCacheFileNotWritten($tmpFilename); } if (! @rename($tmpFilename, $filename)) { throw new CompiledPhpCacheFileNotWritten($filename); } @chmod($filename, 0666 & ~umask()); } finally { @unlink($tmpFilename); } } public function clear(): void { if (! is_dir($this->cacheDir)) { return; } $shouldDeleteRootDir = true; /** @var FilesystemIterator $file */ foreach (new FilesystemIterator($this->cacheDir) as $file) { if ($file->getFilename() === '.valinor.tmp') { @rmdir($file->getPathname()); continue; } if (! $file->isFile()) { $shouldDeleteRootDir = false; continue; } $line = $file->openFile()->getCurrentLine(); if (! $line || ! str_contains($line, self::GENERATED_MESSAGE)) { $shouldDeleteRootDir = false; continue; } @unlink($file->getPathname()); } if ($shouldDeleteRootDir) { @rmdir($this->cacheDir); } } }