MediaWiki REL1_39
FileCacheBase.php
Go to the documentation of this file.
1<?php
27use Wikimedia\AtEase\AtEase;
28use Wikimedia\IPUtils;
29
35abstract class FileCacheBase {
37 private const CONSTRUCTOR_OPTIONS = [
38 MainConfigNames::CacheEpoch,
39 MainConfigNames::FileCacheDepth,
40 MainConfigNames::FileCacheDirectory,
41 MainConfigNames::MimeType,
42 MainConfigNames::UseGzip,
43 ];
44
45 protected $mKey;
46 protected $mType = 'object';
47 protected $mExt = 'cache';
48 protected $mFilePath;
49 protected $mUseGzip;
51 protected $mCached;
53 protected $options;
54
55 /* @todo configurable? */
56 private const MISS_FACTOR = 15; // log 1 every MISS_FACTOR cache misses
57 private const MISS_TTL_SEC = 3600; // how many seconds ago is "recent"
58
59 protected function __construct() {
60 $this->options = new ServiceOptions(
61 self::CONSTRUCTOR_OPTIONS,
62 MediaWikiServices::getInstance()->getMainConfig()
63 );
64 $this->mUseGzip = (bool)$this->options->get( MainConfigNames::UseGzip );
65 }
66
71 final protected function baseCacheDirectory() {
72 return $this->options->get( MainConfigNames::FileCacheDirectory );
73 }
74
79 abstract protected function cacheDirectory();
80
85 protected function cachePath() {
86 if ( $this->mFilePath !== null ) {
87 return $this->mFilePath;
88 }
89
90 $dir = $this->cacheDirectory();
91 # Build directories (methods include the trailing "/")
92 $subDirs = $this->typeSubdirectory() . $this->hashSubdirectory();
93 # Avoid extension confusion
94 $key = str_replace( '.', '%2E', urlencode( $this->mKey ) );
95 # Build the full file path
96 $this->mFilePath = "{$dir}/{$subDirs}{$key}.{$this->mExt}";
97 if ( $this->useGzip() ) {
98 $this->mFilePath .= '.gz';
99 }
100
101 return $this->mFilePath;
102 }
103
108 public function isCached() {
109 if ( $this->mCached === null ) {
110 $this->mCached = is_file( $this->cachePath() );
111 }
112
113 return $this->mCached;
114 }
115
120 public function cacheTimestamp() {
121 $timestamp = filemtime( $this->cachePath() );
122
123 return ( $timestamp !== false )
124 ? wfTimestamp( TS_MW, $timestamp )
125 : false;
126 }
127
134 public function isCacheGood( $timestamp = '' ) {
135 $cacheEpoch = $this->options->get( MainConfigNames::CacheEpoch );
136
137 if ( !$this->isCached() ) {
138 return false;
139 }
140
141 $cachetime = $this->cacheTimestamp();
142 $good = ( $timestamp <= $cachetime && $cacheEpoch <= $cachetime );
143 wfDebug( __METHOD__ .
144 ": cachetime $cachetime, touched '{$timestamp}' epoch {$cacheEpoch}, good " . wfBoolToStr( $good ) );
145
146 return $good;
147 }
148
153 protected function useGzip() {
154 return $this->mUseGzip;
155 }
156
161 public function fetchText() {
162 if ( $this->useGzip() ) {
163 $fh = gzopen( $this->cachePath(), 'rb' );
164
165 return stream_get_contents( $fh );
166 } else {
167 return file_get_contents( $this->cachePath() );
168 }
169 }
170
176 public function saveText( $text ) {
177 if ( $this->useGzip() ) {
178 $text = gzencode( $text );
179 }
180
181 $this->checkCacheDirs(); // build parent dir
182 if ( !file_put_contents( $this->cachePath(), $text, LOCK_EX ) ) {
183 wfDebug( __METHOD__ . "() failed saving " . $this->cachePath() );
184 $this->mCached = null;
185
186 return false;
187 }
188
189 $this->mCached = true;
190
191 return $text;
192 }
193
198 public function clearCache() {
199 AtEase::suppressWarnings();
200 unlink( $this->cachePath() );
201 AtEase::restoreWarnings();
202 $this->mCached = false;
203 }
204
209 protected function checkCacheDirs() {
210 wfMkdirParents( dirname( $this->cachePath() ), null, __METHOD__ );
211 }
212
220 protected function typeSubdirectory() {
221 return $this->mType . '/';
222 }
223
229 protected function hashSubdirectory() {
230 $fileCacheDepth = $this->options->get( MainConfigNames::FileCacheDepth );
231
232 $subdir = '';
233 if ( $fileCacheDepth > 0 ) {
234 $hash = md5( $this->mKey );
235 for ( $i = 1; $i <= $fileCacheDepth; $i++ ) {
236 $subdir .= substr( $hash, 0, $i ) . '/';
237 }
238 }
239
240 return $subdir;
241 }
242
248 public function incrMissesRecent( WebRequest $request ) {
249 if ( mt_rand( 0, self::MISS_FACTOR - 1 ) == 0 ) {
250 # Get a large IP range that should include the user even if that
251 # person's IP address changes
252 $ip = $request->getIP();
253 if ( !IPUtils::isValid( $ip ) ) {
254 return;
255 }
256
257 $ip = IPUtils::isIPv6( $ip )
258 ? IPUtils::sanitizeRange( "$ip/32" )
259 : IPUtils::sanitizeRange( "$ip/16" );
260
261 # Bail out if a request already came from this range...
262 $cache = ObjectCache::getLocalClusterInstance();
263 $key = $cache->makeKey( static::class, 'attempt', $this->mType, $this->mKey, $ip );
264 if ( !$cache->add( $key, 1, self::MISS_TTL_SEC ) ) {
265 return; // possibly the same user
266 }
267
268 # Increment the number of cache misses...
269 $cache->incrWithInit( $this->cacheMissKey( $cache ), self::MISS_TTL_SEC );
270 }
271 }
272
277 public function getMissesRecent() {
278 $cache = ObjectCache::getLocalClusterInstance();
279
280 return self::MISS_FACTOR * $cache->get( $this->cacheMissKey( $cache ) );
281 }
282
287 protected function cacheMissKey( BagOStuff $cache ) {
288 return $cache->makeKey( static::class, 'misses', $this->mType, $this->mKey );
289 }
290}
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
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...
$cache
Definition mcc.php:33