You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

184 lines
5.1 KiB

<?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);
}
}