123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- <?php
- declare(strict_types=1);
- /*
- * This file is part of PHP CS Fixer.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- * Dariusz Rumiński <dariusz.ruminski@gmail.com>
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
- namespace PhpCsFixer\Cache;
- use Symfony\Component\Filesystem\Exception\IOException;
- /**
- * @author Andreas Möller <am@localheinz.com>
- * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
- *
- * @internal
- */
- final class FileHandler implements FileHandlerInterface
- {
- private \SplFileInfo $fileInfo;
- private int $fileMTime = 0;
- public function __construct(string $file)
- {
- $this->fileInfo = new \SplFileInfo($file);
- }
- public function getFile(): string
- {
- return $this->fileInfo->getPathname();
- }
- public function read(): ?CacheInterface
- {
- if (!$this->fileInfo->isFile() || !$this->fileInfo->isReadable()) {
- return null;
- }
- $fileObject = $this->fileInfo->openFile('r');
- $cache = $this->readFromHandle($fileObject);
- $this->fileMTime = $this->getFileCurrentMTime();
- unset($fileObject); // explicitly close file handler
- return $cache;
- }
- public function write(CacheInterface $cache): void
- {
- $this->ensureFileIsWriteable();
- $fileObject = $this->fileInfo->openFile('r+');
- if (method_exists($cache, 'backfillHashes') && $this->fileMTime < $this->getFileCurrentMTime()) {
- $resultOfFlock = $fileObject->flock(LOCK_EX);
- if (false === $resultOfFlock) {
- // Lock failed, OK - we continue without the lock.
- // noop
- }
- $oldCache = $this->readFromHandle($fileObject);
- $fileObject->rewind();
- if (null !== $oldCache) {
- $cache->backfillHashes($oldCache);
- }
- }
- $resultOfTruncate = $fileObject->ftruncate(0);
- if (false === $resultOfTruncate) {
- // Truncate failed. OK - we do not save the cache.
- return;
- }
- $resultOfWrite = $fileObject->fwrite($cache->toJson());
- if (false === $resultOfWrite) {
- // Write failed. OK - we did not save the cache.
- return;
- }
- $resultOfFlush = $fileObject->fflush();
- if (false === $resultOfFlush) {
- // Flush failed. OK - part of cache can be missing, in case this was last chunk in this pid.
- // noop
- }
- $this->fileMTime = time(); // we could take the fresh `mtime` of file that we just modified with `$this->getFileCurrentMTime()`, but `time()` should be good enough here and reduce IO operation
- }
- private function getFileCurrentMTime(): int
- {
- clearstatcache(true, $this->fileInfo->getPathname());
- $mtime = $this->fileInfo->getMTime();
- if (false === $mtime) {
- // cannot check mtime? OK - let's pretend file is old.
- $mtime = 0;
- }
- return $mtime;
- }
- private function readFromHandle(\SplFileObject $fileObject): ?CacheInterface
- {
- try {
- $size = $fileObject->getSize();
- if (false === $size || 0 === $size) {
- return null;
- }
- $content = $fileObject->fread($size);
- if (false === $content) {
- return null;
- }
- return Cache::fromJson($content);
- } catch (\InvalidArgumentException $exception) {
- return null;
- }
- }
- private function ensureFileIsWriteable(): void
- {
- if ($this->fileInfo->isFile() && $this->fileInfo->isWritable()) {
- // all good
- return;
- }
- if ($this->fileInfo->isDir()) {
- throw new IOException(
- sprintf('Cannot write cache file "%s" as the location exists as directory.', $this->fileInfo->getRealPath()),
- 0,
- null,
- $this->fileInfo->getPathname()
- );
- }
- if ($this->fileInfo->isFile() && !$this->fileInfo->isWritable()) {
- throw new IOException(
- sprintf('Cannot write to file "%s" as it is not writable.', $this->fileInfo->getRealPath()),
- 0,
- null,
- $this->fileInfo->getPathname()
- );
- }
- $this->createFile($this->fileInfo->getPathname());
- }
- private function createFile(string $file): void
- {
- $dir = \dirname($file);
- // Ensure path is created, but ignore if already exists. FYI: ignore EA suggestion in IDE,
- // `mkdir()` returns `false` for existing paths, so we can't mix it with `is_dir()` in one condition.
- if (!@is_dir($dir)) {
- @mkdir($dir, 0777, true);
- }
- if (!@is_dir($dir)) {
- throw new IOException(
- sprintf('Directory of cache file "%s" does not exists and couldn\'t be created.', $file),
- 0,
- null,
- $file
- );
- }
- @touch($file);
- @chmod($file, 0666);
- }
- }
|