MediaWiki REL1_40
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 $this->mCached ??= is_file( $this->cachePath() );
110
111 return $this->mCached;
112 }
113
118 public function cacheTimestamp() {
119 $timestamp = filemtime( $this->cachePath() );
120
121 return ( $timestamp !== false )
122 ? wfTimestamp( TS_MW, $timestamp )
123 : false;
124 }
125
132 public function isCacheGood( $timestamp = '' ) {
133 $cacheEpoch = $this->options->get( MainConfigNames::CacheEpoch );
134
135 if ( !$this->isCached() ) {
136 return false;
137 }
138
139 $cachetime = $this->cacheTimestamp();
140 $good = ( $timestamp <= $cachetime && $cacheEpoch <= $cachetime );
141 wfDebug( __METHOD__ .
142 ": cachetime $cachetime, touched '{$timestamp}' epoch {$cacheEpoch}, good " . wfBoolToStr( $good ) );
143
144 return $good;
145 }
146
151 protected function useGzip() {
152 return $this->mUseGzip;
153 }
154
159 public function fetchText() {
160 if ( $this->useGzip() ) {
161 $fh = gzopen( $this->cachePath(), 'rb' );
162
163 return stream_get_contents( $fh );
164 } else {
165 return file_get_contents( $this->cachePath() );
166 }
167 }
168
174 public function saveText( $text ) {
175 if ( $this->useGzip() ) {
176 $text = gzencode( $text );
177 }
178
179 $this->checkCacheDirs(); // build parent dir
180 if ( !file_put_contents( $this->cachePath(), $text, LOCK_EX ) ) {
181 wfDebug( __METHOD__ . "() failed saving " . $this->cachePath() );
182 $this->mCached = null;
183
184 return false;
185 }
186
187 $this->mCached = true;
188
189 return $text;
190 }
191
196 public function clearCache() {
197 AtEase::suppressWarnings();
198 unlink( $this->cachePath() );
199 AtEase::restoreWarnings();
200 $this->mCached = false;
201 }
202
207 protected function checkCacheDirs() {
208 wfMkdirParents( dirname( $this->cachePath() ), null, __METHOD__ );
209 }
210
218 protected function typeSubdirectory() {
219 return $this->mType . '/';
220 }
221
227 protected function hashSubdirectory() {
228 $fileCacheDepth = $this->options->get( MainConfigNames::FileCacheDepth );
229
230 $subdir = '';
231 if ( $fileCacheDepth > 0 ) {
232 $hash = md5( $this->mKey );
233 for ( $i = 1; $i <= $fileCacheDepth; $i++ ) {
234 $subdir .= substr( $hash, 0, $i ) . '/';
235 }
236 }
237
238 return $subdir;
239 }
240
246 public function incrMissesRecent( WebRequest $request ) {
247 if ( mt_rand( 0, self::MISS_FACTOR - 1 ) == 0 ) {
248 # Get a large IP range that should include the user even if that
249 # person's IP address changes
250 $ip = $request->getIP();
251 if ( !IPUtils::isValid( $ip ) ) {
252 return;
253 }
254
255 $ip = IPUtils::isIPv6( $ip )
256 ? IPUtils::sanitizeRange( "$ip/32" )
257 : IPUtils::sanitizeRange( "$ip/16" );
258
259 # Bail out if a request already came from this range...
260 $cache = ObjectCache::getLocalClusterInstance();
261 $key = $cache->makeKey( static::class, 'attempt', $this->mType, $this->mKey, $ip );
262 if ( !$cache->add( $key, 1, self::MISS_TTL_SEC ) ) {
263 return; // possibly the same user
264 }
265
266 # Increment the number of cache misses...
267 $cache->incrWithInit( $this->cacheMissKey( $cache ), self::MISS_TTL_SEC );
268 }
269 }
270
275 public function getMissesRecent() {
276 $cache = ObjectCache::getLocalClusterInstance();
277
278 return self::MISS_FACTOR * $cache->get( $this->cacheMissKey( $cache ) );
279 }
280
285 protected function cacheMissKey( BagOStuff $cache ) {
286 return $cache->makeKey( static::class, 'misses', $this->mType, $this->mKey );
287 }
288}
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( $collection,... $components)
Make a cache key for the global keyspace and given components.
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...