forked from vimeo/psalm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Cache.php
149 lines (123 loc) · 3.71 KB
/
Cache.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
<?php
namespace Psalm\Internal;
use Psalm\Config;
use Psalm\Internal\Provider\Providers;
use RuntimeException;
use function file_exists;
use function file_put_contents;
use function gzdeflate;
use function gzinflate;
use function igbinary_serialize;
use function igbinary_unserialize;
use function lz4_compress;
use function lz4_uncompress;
use function serialize;
use function unlink;
use function unserialize;
use const LOCK_EX;
/**
* @internal
*/
class Cache
{
private Config $config;
public bool $use_igbinary;
protected int $error_count = 0;
public function __construct(Config $config)
{
$this->config = $config;
$this->use_igbinary = $config->use_igbinary;
}
/**
* @return array|object|string|null
*/
public function getItem(string $path)
{
if (!file_exists($path)) {
return null;
}
$cache = Providers::safeFileGetContents($path);
if ($cache === '') {
return null;
}
if ($this->config->compressor === 'deflate') {
$inflated = @gzinflate($cache);
} elseif ($this->config->compressor === 'lz4') {
/**
* @psalm-suppress UndefinedFunction
* @var string|false $inflated
*/
$inflated = lz4_uncompress($cache);
} else {
$inflated = $cache;
}
// invalid cache data
if ($inflated === false) {
$this->handleInvalidCache($path);
return null;
}
if ($this->config->use_igbinary) {
/** @var object|false $unserialized */
$unserialized = @igbinary_unserialize($inflated);
} else {
/** @var object|false $unserialized */
$unserialized = @unserialize($inflated);
}
if ($unserialized === false) {
$this->handleInvalidCache($path);
return null;
}
return $unserialized;
}
protected function handleInvalidCache(string $path): void
{
$this->deleteItem($path);
$this->error_count++;
// if 10 previous items were invalid, abort since the cache is invalid and inform the user
// we don't report it to the user immediately, since it can happen that a few files get corrupted somehow
// however the impact on performance is minimal, therefore we ignore it
if ($this->error_count > 10) {
throw new RuntimeException(
'The cache data is corrupted. Please delete the cache directory and run Psalm again'
);
}
}
public function deleteItem(string $path): void
{
if (file_exists($path)) {
@unlink($path);
}
}
/**
* @param array|object|string $item
*/
public function saveItem(string $path, $item): void
{
if ($this->config->use_igbinary) {
$serialized = igbinary_serialize($item);
} else {
$serialized = serialize($item);
}
if ($this->config->compressor === 'deflate') {
$compressed = gzdeflate($serialized);
} elseif ($this->config->compressor === 'lz4') {
/**
* @psalm-suppress UndefinedFunction
* @var string|false $compressed
*/
$compressed = lz4_compress($serialized, 4);
} else {
$compressed = $serialized;
}
if ($compressed === false) {
throw new RuntimeException(
'Failed to compress cache data',
);
}
file_put_contents($path, $compressed, LOCK_EX);
}
public function getCacheDirectory(): ?string
{
return $this->config->getCacheDirectory();
}
}