• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

keradus / PHP-CS-Fixer / 17253322895

26 Aug 2025 11:52PM UTC coverage: 94.753% (+0.008%) from 94.745%
17253322895

push

github

keradus
add to git-blame-ignore-revs

28316 of 29884 relevant lines covered (94.75%)

45.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.86
/src/Cache/FileHandler.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of PHP CS Fixer.
7
 *
8
 * (c) Fabien Potencier <fabien@symfony.com>
9
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14

15
namespace PhpCsFixer\Cache;
16

17
use Symfony\Component\Filesystem\Exception\IOException;
18

19
/**
20
 * @author Andreas Möller <am@localheinz.com>
21
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
22
 *
23
 * @internal
24
 *
25
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
26
 */
27
final class FileHandler implements FileHandlerInterface
28
{
29
    private \SplFileInfo $fileInfo;
30

31
    private int $fileMTime = 0;
32

33
    public function __construct(string $file)
34
    {
35
        $this->fileInfo = new \SplFileInfo($file);
9✔
36
    }
37

38
    public function getFile(): string
39
    {
40
        return $this->fileInfo->getPathname();
1✔
41
    }
42

43
    public function read(): ?CacheInterface
44
    {
45
        if (!$this->fileInfo->isFile() || !$this->fileInfo->isReadable()) {
3✔
46
            return null;
1✔
47
        }
48

49
        $fileObject = $this->fileInfo->openFile('r');
2✔
50

51
        $cache = $this->readFromHandle($fileObject);
2✔
52
        $this->fileMTime = $this->getFileCurrentMTime();
2✔
53

54
        unset($fileObject); // explicitly close file handler
2✔
55

56
        return $cache;
2✔
57
    }
58

59
    public function write(CacheInterface $cache): void
60
    {
61
        $this->ensureFileIsWriteable();
5✔
62

63
        $fileObject = $this->fileInfo->openFile('r+');
3✔
64

65
        if (method_exists($cache, 'backfillHashes') && $this->fileMTime < $this->getFileCurrentMTime()) {
3✔
66
            $resultOfFlock = $fileObject->flock(\LOCK_EX);
3✔
67
            if (false === $resultOfFlock) {
3✔
68
                // Lock failed, OK - we continue without the lock.
69
                // noop
70
            }
71

72
            $oldCache = $this->readFromHandle($fileObject);
3✔
73

74
            $fileObject->rewind();
3✔
75

76
            if (null !== $oldCache) {
3✔
77
                $cache->backfillHashes($oldCache);
×
78
            }
79
        }
80

81
        $resultOfTruncate = $fileObject->ftruncate(0);
3✔
82
        if (false === $resultOfTruncate) {
3✔
83
            // Truncate failed. OK - we do not save the cache.
84
            return;
×
85
        }
86

87
        $resultOfWrite = $fileObject->fwrite($cache->toJson());
3✔
88
        if (false === $resultOfWrite) {
3✔
89
            // Write failed. OK - we did not save the cache.
90
            return;
×
91
        }
92

93
        $resultOfFlush = $fileObject->fflush();
3✔
94
        if (false === $resultOfFlush) {
3✔
95
            // Flush failed. OK - part of cache can be missing, in case this was last chunk in this pid.
96
            // noop
97
        }
98

99
        $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
3✔
100
    }
101

102
    private function getFileCurrentMTime(): int
103
    {
104
        clearstatcache(true, $this->fileInfo->getPathname());
5✔
105

106
        $mtime = $this->fileInfo->getMTime();
5✔
107

108
        if (false === $mtime) {
5✔
109
            // cannot check mtime? OK - let's pretend file is old.
110
            $mtime = 0;
×
111
        }
112

113
        return $mtime;
5✔
114
    }
115

116
    private function readFromHandle(\SplFileObject $fileObject): ?CacheInterface
117
    {
118
        try {
119
            $size = $fileObject->getSize();
5✔
120
            if (false === $size || 0 === $size) {
5✔
121
                return null;
3✔
122
            }
123

124
            $content = $fileObject->fread($size);
2✔
125

126
            if (false === $content) {
2✔
127
                return null;
×
128
            }
129

130
            return Cache::fromJson($content);
2✔
131
        } catch (\InvalidArgumentException $exception) {
1✔
132
            return null;
1✔
133
        }
134
    }
135

136
    private function ensureFileIsWriteable(): void
137
    {
138
        if ($this->fileInfo->isFile() && $this->fileInfo->isWritable()) {
5✔
139
            // all good
140
            return;
×
141
        }
142

143
        if ($this->fileInfo->isDir()) {
5✔
144
            throw new IOException(
1✔
145
                \sprintf('Cannot write cache file "%s" as the location exists as directory.', $this->fileInfo->getRealPath()),
1✔
146
                0,
1✔
147
                null,
1✔
148
                $this->fileInfo->getPathname()
1✔
149
            );
1✔
150
        }
151

152
        if ($this->fileInfo->isFile() && !$this->fileInfo->isWritable()) {
4✔
153
            throw new IOException(
×
154
                \sprintf('Cannot write to file "%s" as it is not writable.', $this->fileInfo->getRealPath()),
×
155
                0,
×
156
                null,
×
157
                $this->fileInfo->getPathname()
×
158
            );
×
159
        }
160

161
        $this->createFile($this->fileInfo->getPathname());
4✔
162
    }
163

164
    private function createFile(string $file): void
165
    {
166
        $dir = \dirname($file);
4✔
167

168
        // Ensure path is created, but ignore if already exists. FYI: ignore EA suggestion in IDE,
169
        // `mkdir()` returns `false` for existing paths, so we can't mix it with `is_dir()` in one condition.
170
        if (!@is_dir($dir)) {
4✔
171
            @mkdir($dir, 0777, true);
2✔
172
        }
173

174
        if (!@is_dir($dir)) {
4✔
175
            throw new IOException(
1✔
176
                \sprintf('Directory of cache file "%s" does not exists and couldn\'t be created.', $file),
1✔
177
                0,
1✔
178
                null,
1✔
179
                $file
1✔
180
            );
1✔
181
        }
182

183
        @touch($file);
3✔
184
        @chmod($file, 0666);
3✔
185
    }
186
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc