MediaWiki master
FileCacheBase.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Cache;
25
26use BagOStuff;
31use ObjectCache;
32use Wikimedia\AtEase\AtEase;
33use Wikimedia\IPUtils;
34
40abstract class FileCacheBase {
42 private const CONSTRUCTOR_OPTIONS = [
48 ];
49
50 protected $mKey;
51 protected $mType = 'object';
52 protected $mExt = 'cache';
53 protected $mFilePath;
54 protected $mUseGzip;
56 protected $mCached;
58 protected $options;
59
60 /* @todo configurable? */
61 private const MISS_FACTOR = 15; // log 1 every MISS_FACTOR cache misses
62 private const MISS_TTL_SEC = 3600; // how many seconds ago is "recent"
63
64 protected function __construct() {
65 $this->options = new ServiceOptions(
66 self::CONSTRUCTOR_OPTIONS,
67 MediaWikiServices::getInstance()->getMainConfig()
68 );
69 $this->mUseGzip = (bool)$this->options->get( MainConfigNames::UseGzip );
70 }
71
76 final protected function baseCacheDirectory() {
77 return $this->options->get( MainConfigNames::FileCacheDirectory );
78 }
79
84 abstract protected function cacheDirectory();
85
90 protected function cachePath() {
91 if ( $this->mFilePath !== null ) {
92 return $this->mFilePath;
93 }
94
95 $dir = $this->cacheDirectory();
96 # Build directories (methods include the trailing "/")
97 $subDirs = $this->typeSubdirectory() . $this->hashSubdirectory();
98 # Avoid extension confusion
99 $key = str_replace( '.', '%2E', urlencode( $this->mKey ) );
100 # Build the full file path
101 $this->mFilePath = "{$dir}/{$subDirs}{$key}.{$this->mExt}";
102 if ( $this->useGzip() ) {
103 $this->mFilePath .= '.gz';
104 }
105
106 return $this->mFilePath;
107 }
108
113 public function isCached() {
114 $this->mCached ??= is_file( $this->cachePath() );
115
116 return $this->mCached;
117 }
118
123 public function cacheTimestamp() {
124 $timestamp = filemtime( $this->cachePath() );
125
126 return ( $timestamp !== false )
127 ? wfTimestamp( TS_MW, $timestamp )
128 : false;
129 }
130
137 public function isCacheGood( $timestamp = '' ) {
138 $cacheEpoch = $this->options->get( MainConfigNames::CacheEpoch );
139
140 if ( !$this->isCached() ) {
141 return false;
142 }
143
144 $cachetime = $this->cacheTimestamp();
145 $good = ( $timestamp <= $cachetime && $cacheEpoch <= $cachetime );
146 wfDebug( __METHOD__ .
147 ": cachetime $cachetime, touched '{$timestamp}' epoch {$cacheEpoch}, good " . wfBoolToStr( $good ) );
148
149 return $good;
150 }
151
156 protected function useGzip() {
157 return $this->mUseGzip;
158 }
159
164 public function fetchText() {
165 if ( $this->useGzip() ) {
166 $fh = gzopen( $this->cachePath(), 'rb' );
167
168 return stream_get_contents( $fh );
169 } else {
170 return file_get_contents( $this->cachePath() );
171 }
172 }
173
179 public function saveText( $text ) {
180 if ( $this->useGzip() ) {
181 $text = gzencode( $text );
182 }
183
184 $this->checkCacheDirs(); // build parent dir
185 if ( !file_put_contents( $this->cachePath(), $text, LOCK_EX ) ) {
186 wfDebug( __METHOD__ . "() failed saving " . $this->cachePath() );
187 $this->mCached = null;
188
189 return false;
190 }
191
192 $this->mCached = true;
193
194 return $text;
195 }
196
201 public function clearCache() {
202 AtEase::suppressWarnings();
203 unlink( $this->cachePath() );
204 AtEase::restoreWarnings();
205 $this->mCached = false;
206 }
207
212 protected function checkCacheDirs() {
213 wfMkdirParents( dirname( $this->cachePath() ), null, __METHOD__ );
214 }
215
223 protected function typeSubdirectory() {
224 return $this->mType . '/';
225 }
226
232 protected function hashSubdirectory() {
233 $fileCacheDepth = $this->options->get( MainConfigNames::FileCacheDepth );
234
235 $subdir = '';
236 if ( $fileCacheDepth > 0 ) {
237 $hash = md5( $this->mKey );
238 for ( $i = 1; $i <= $fileCacheDepth; $i++ ) {
239 $subdir .= substr( $hash, 0, $i ) . '/';
240 }
241 }
242
243 return $subdir;
244 }
245
251 public function incrMissesRecent( WebRequest $request ) {
252 if ( mt_rand( 0, self::MISS_FACTOR - 1 ) == 0 ) {
253 # Get a large IP range that should include the user even if that
254 # person's IP address changes
255 $ip = $request->getIP();
256 if ( !IPUtils::isValid( $ip ) ) {
257 return;
258 }
259
260 $ip = IPUtils::isIPv6( $ip )
261 ? IPUtils::sanitizeRange( "$ip/32" )
262 : IPUtils::sanitizeRange( "$ip/16" );
263
264 # Bail out if a request already came from this range...
266 $key = $cache->makeKey( static::class, 'attempt', $this->mType, $this->mKey, $ip );
267 if ( !$cache->add( $key, 1, self::MISS_TTL_SEC ) ) {
268 return; // possibly the same user
269 }
270
271 # Increment the number of cache misses...
272 $cache->incrWithInit( $this->cacheMissKey( $cache ), self::MISS_TTL_SEC );
273 }
274 }
275
280 public function getMissesRecent() {
282
283 return self::MISS_FACTOR * $cache->get( $this->cacheMissKey( $cache ) );
284 }
285
290 protected function cacheMissKey( BagOStuff $cache ) {
291 return $cache->makeKey( static::class, 'misses', $this->mType, $this->mKey );
292 }
293}
294
296class_alias( FileCacheBase::class, 'FileCacheBase' );
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.
isCached()
Check if the cache file exists.
checkCacheDirs()
Create parent directors of $this->cachePath()
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.
saveText( $text)
Save and compress text to the cache.
baseCacheDirectory()
Get the base file cache directory.
fetchText()
Get the uncompressed text from the cache.
clearCache()
Clear the cache for this page.
cacheTimestamp()
Get the last-modified timestamp of the cache file.
cachePath()
Get the path to the cache file.
useGzip()
Check if the cache is gzipped.
bool null $mCached
lazy loaded
isCacheGood( $timestamp='')
Check if up to date cache file exists.
typeSubdirectory()
Get the cache type subdirectory (with trailing slash) An extending class could use that method to alt...
cacheDirectory()
Get the base cache directory (not specific to this file)
incrMissesRecent(WebRequest $request)
Roughly increments the cache misses in the last hour by unique visitors.
A class for passing options to services.
A class containing constants representing the names of configuration variables.
const FileCacheDirectory
Name constant for the FileCacheDirectory setting, for use with Config::get()
const MimeType
Name constant for the MimeType setting, for use with Config::get()
const CacheEpoch
Name constant for the CacheEpoch setting, for use with Config::get()
const FileCacheDepth
Name constant for the FileCacheDepth setting, for use with Config::get()
const UseGzip
Name constant for the UseGzip setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
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...
Functions to get cache objects.
static getLocalClusterInstance()
Get the main cluster-local cache object.