MediaWiki master
FileCacheBase.php
Go to the documentation of this file.
1<?php
28use Wikimedia\AtEase\AtEase;
29use Wikimedia\IPUtils;
30
36abstract class FileCacheBase {
38 private const CONSTRUCTOR_OPTIONS = [
39 MainConfigNames::CacheEpoch,
40 MainConfigNames::FileCacheDepth,
41 MainConfigNames::FileCacheDirectory,
42 MainConfigNames::MimeType,
43 MainConfigNames::UseGzip,
44 ];
45
46 protected $mKey;
47 protected $mType = 'object';
48 protected $mExt = 'cache';
49 protected $mFilePath;
50 protected $mUseGzip;
52 protected $mCached;
54 protected $options;
55
56 /* @todo configurable? */
57 private const MISS_FACTOR = 15; // log 1 every MISS_FACTOR cache misses
58 private const MISS_TTL_SEC = 3600; // how many seconds ago is "recent"
59
60 protected function __construct() {
61 $this->options = new ServiceOptions(
62 self::CONSTRUCTOR_OPTIONS,
63 MediaWikiServices::getInstance()->getMainConfig()
64 );
65 $this->mUseGzip = (bool)$this->options->get( MainConfigNames::UseGzip );
66 }
67
72 final protected function baseCacheDirectory() {
73 return $this->options->get( MainConfigNames::FileCacheDirectory );
74 }
75
80 abstract protected function cacheDirectory();
81
86 protected function cachePath() {
87 if ( $this->mFilePath !== null ) {
88 return $this->mFilePath;
89 }
90
91 $dir = $this->cacheDirectory();
92 # Build directories (methods include the trailing "/")
93 $subDirs = $this->typeSubdirectory() . $this->hashSubdirectory();
94 # Avoid extension confusion
95 $key = str_replace( '.', '%2E', urlencode( $this->mKey ) );
96 # Build the full file path
97 $this->mFilePath = "{$dir}/{$subDirs}{$key}.{$this->mExt}";
98 if ( $this->useGzip() ) {
99 $this->mFilePath .= '.gz';
100 }
101
102 return $this->mFilePath;
103 }
104
109 public function isCached() {
110 $this->mCached ??= is_file( $this->cachePath() );
111
112 return $this->mCached;
113 }
114
119 public function cacheTimestamp() {
120 $timestamp = filemtime( $this->cachePath() );
121
122 return ( $timestamp !== false )
123 ? wfTimestamp( TS_MW, $timestamp )
124 : false;
125 }
126
133 public function isCacheGood( $timestamp = '' ) {
134 $cacheEpoch = $this->options->get( MainConfigNames::CacheEpoch );
135
136 if ( !$this->isCached() ) {
137 return false;
138 }
139
140 $cachetime = $this->cacheTimestamp();
141 $good = ( $timestamp <= $cachetime && $cacheEpoch <= $cachetime );
142 wfDebug( __METHOD__ .
143 ": cachetime $cachetime, touched '{$timestamp}' epoch {$cacheEpoch}, good " . wfBoolToStr( $good ) );
144
145 return $good;
146 }
147
152 protected function useGzip() {
153 return $this->mUseGzip;
154 }
155
160 public function fetchText() {
161 if ( $this->useGzip() ) {
162 $fh = gzopen( $this->cachePath(), 'rb' );
163
164 return stream_get_contents( $fh );
165 } else {
166 return file_get_contents( $this->cachePath() );
167 }
168 }
169
175 public function saveText( $text ) {
176 if ( $this->useGzip() ) {
177 $text = gzencode( $text );
178 }
179
180 $this->checkCacheDirs(); // build parent dir
181 if ( !file_put_contents( $this->cachePath(), $text, LOCK_EX ) ) {
182 wfDebug( __METHOD__ . "() failed saving " . $this->cachePath() );
183 $this->mCached = null;
184
185 return false;
186 }
187
188 $this->mCached = true;
189
190 return $text;
191 }
192
197 public function clearCache() {
198 AtEase::suppressWarnings();
199 unlink( $this->cachePath() );
200 AtEase::restoreWarnings();
201 $this->mCached = false;
202 }
203
208 protected function checkCacheDirs() {
209 wfMkdirParents( dirname( $this->cachePath() ), null, __METHOD__ );
210 }
211
219 protected function typeSubdirectory() {
220 return $this->mType . '/';
221 }
222
228 protected function hashSubdirectory() {
229 $fileCacheDepth = $this->options->get( MainConfigNames::FileCacheDepth );
230
231 $subdir = '';
232 if ( $fileCacheDepth > 0 ) {
233 $hash = md5( $this->mKey );
234 for ( $i = 1; $i <= $fileCacheDepth; $i++ ) {
235 $subdir .= substr( $hash, 0, $i ) . '/';
236 }
237 }
238
239 return $subdir;
240 }
241
247 public function incrMissesRecent( WebRequest $request ) {
248 if ( mt_rand( 0, self::MISS_FACTOR - 1 ) == 0 ) {
249 # Get a large IP range that should include the user even if that
250 # person's IP address changes
251 $ip = $request->getIP();
252 if ( !IPUtils::isValid( $ip ) ) {
253 return;
254 }
255
256 $ip = IPUtils::isIPv6( $ip )
257 ? IPUtils::sanitizeRange( "$ip/32" )
258 : IPUtils::sanitizeRange( "$ip/16" );
259
260 # Bail out if a request already came from this range...
261 $cache = ObjectCache::getLocalClusterInstance();
262 $key = $cache->makeKey( static::class, 'attempt', $this->mType, $this->mKey, $ip );
263 if ( !$cache->add( $key, 1, self::MISS_TTL_SEC ) ) {
264 return; // possibly the same user
265 }
266
267 # Increment the number of cache misses...
268 $cache->incrWithInit( $this->cacheMissKey( $cache ), self::MISS_TTL_SEC );
269 }
270 }
271
276 public function getMissesRecent() {
277 $cache = ObjectCache::getLocalClusterInstance();
278
279 return self::MISS_FACTOR * $cache->get( $this->cacheMissKey( $cache ) );
280 }
281
286 protected function cacheMissKey( BagOStuff $cache ) {
287 return $cache->makeKey( static::class, 'misses', $this->mType, $this->mKey );
288 }
289}
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfMkdirParents( $dir, $mode=null, $caller=null)
Make directory, and make all parent directories if they don't exist.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfBoolToStr( $value)
Convenience function converts boolean values into "true" or "false" (string) values.
Class representing a cache/ephemeral data store.
Definition BagOStuff.php:85
makeKey( $keygroup,... $components)
Make a cache key from the given components, in the default keyspace.
Base class for data storage in the file system.
isCacheGood( $timestamp='')
Check if up to date cache file exists.
useGzip()
Check if the cache is gzipped.
cachePath()
Get the path to the cache file.
saveText( $text)
Save and compress text to the cache.
fetchText()
Get the uncompressed text from the cache.
hashSubdirectory()
Return relative multi-level hash subdirectory (with trailing slash) or the empty string if not $wgFil...
getMissesRecent()
Roughly gets the cache misses in the last hour by unique visitors.
ServiceOptions $options
cacheDirectory()
Get the base cache directory (not specific to this file)
checkCacheDirs()
Create parent directors of $this->cachePath()
cacheTimestamp()
Get the last-modified timestamp of the cache file.
clearCache()
Clear the cache for this page.
typeSubdirectory()
Get the cache type subdirectory (with trailing slash) An extending class could use that method to alt...
cacheMissKey(BagOStuff $cache)
baseCacheDirectory()
Get the base file cache directory.
incrMissesRecent(WebRequest $request)
Roughly increments the cache misses in the last hour by unique visitors.
isCached()
Check if the cache file exists.
bool null $mCached
lazy loaded
A class for passing options to services.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
getIP()
Work out the IP address based on various globals For trusted proxies, use the XFF client IP (first of...