MediaWiki master
FileCacheBase.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Cache;
25
30use Wikimedia\AtEase\AtEase;
31use Wikimedia\IPUtils;
33
39abstract class FileCacheBase {
41 private const CONSTRUCTOR_OPTIONS = [
47 ];
48
49 protected $mKey;
50 protected $mType = 'object';
51 protected $mExt = 'cache';
52 protected $mFilePath;
53 protected $mUseGzip;
55 protected $mCached;
57 protected $options;
58
59 /* @todo configurable? */
60 private const MISS_FACTOR = 15; // log 1 every MISS_FACTOR cache misses
61 private const MISS_TTL_SEC = 3600; // how many seconds ago is "recent"
62
63 protected function __construct() {
64 $this->options = new ServiceOptions(
65 self::CONSTRUCTOR_OPTIONS,
66 MediaWikiServices::getInstance()->getMainConfig()
67 );
68 $this->mUseGzip = (bool)$this->options->get( MainConfigNames::UseGzip );
69 }
70
75 final protected function baseCacheDirectory() {
76 return $this->options->get( MainConfigNames::FileCacheDirectory );
77 }
78
83 abstract protected function cacheDirectory();
84
89 protected function cachePath() {
90 if ( $this->mFilePath !== null ) {
91 return $this->mFilePath;
92 }
93
94 $dir = $this->cacheDirectory();
95 # Build directories (methods include the trailing "/")
96 $subDirs = $this->typeSubdirectory() . $this->hashSubdirectory();
97 # Avoid extension confusion
98 $key = str_replace( '.', '%2E', urlencode( $this->mKey ) );
99 # Build the full file path
100 $this->mFilePath = "{$dir}/{$subDirs}{$key}.{$this->mExt}";
101 if ( $this->useGzip() ) {
102 $this->mFilePath .= '.gz';
103 }
104
105 return $this->mFilePath;
106 }
107
112 public function isCached() {
113 $this->mCached ??= is_file( $this->cachePath() );
114
115 return $this->mCached;
116 }
117
122 public function cacheTimestamp() {
123 $timestamp = filemtime( $this->cachePath() );
124
125 return ( $timestamp !== false )
126 ? wfTimestamp( TS_MW, $timestamp )
127 : false;
128 }
129
136 public function isCacheGood( $timestamp = '' ) {
137 $cacheEpoch = $this->options->get( MainConfigNames::CacheEpoch );
138
139 if ( !$this->isCached() ) {
140 return false;
141 }
142
143 $cachetime = $this->cacheTimestamp();
144 $good = ( $timestamp <= $cachetime && $cacheEpoch <= $cachetime );
145 wfDebug( __METHOD__ .
146 ": cachetime $cachetime, touched '{$timestamp}' epoch {$cacheEpoch}, good " . wfBoolToStr( $good ) );
147
148 return $good;
149 }
150
155 protected function useGzip() {
156 return $this->mUseGzip;
157 }
158
163 public function fetchText() {
164 if ( $this->useGzip() ) {
165 $fh = gzopen( $this->cachePath(), 'rb' );
166
167 return stream_get_contents( $fh );
168 } else {
169 return file_get_contents( $this->cachePath() );
170 }
171 }
172
178 public function saveText( $text ) {
179 if ( $this->useGzip() ) {
180 $text = gzencode( $text );
181 }
182
183 $this->checkCacheDirs(); // build parent dir
184 if ( !file_put_contents( $this->cachePath(), $text, LOCK_EX ) ) {
185 wfDebug( __METHOD__ . "() failed saving " . $this->cachePath() );
186 $this->mCached = null;
187
188 return false;
189 }
190
191 $this->mCached = true;
192
193 return $text;
194 }
195
200 public function clearCache() {
201 AtEase::suppressWarnings();
202 unlink( $this->cachePath() );
203 AtEase::restoreWarnings();
204 $this->mCached = false;
205 }
206
211 protected function checkCacheDirs() {
212 wfMkdirParents( dirname( $this->cachePath() ), null, __METHOD__ );
213 }
214
222 protected function typeSubdirectory() {
223 return $this->mType . '/';
224 }
225
231 protected function hashSubdirectory() {
232 $fileCacheDepth = $this->options->get( MainConfigNames::FileCacheDepth );
233
234 $subdir = '';
235 if ( $fileCacheDepth > 0 ) {
236 $hash = md5( $this->mKey );
237 for ( $i = 1; $i <= $fileCacheDepth; $i++ ) {
238 $subdir .= substr( $hash, 0, $i ) . '/';
239 }
240 }
241
242 return $subdir;
243 }
244
250 public function incrMissesRecent( WebRequest $request ) {
251 if ( mt_rand( 0, self::MISS_FACTOR - 1 ) == 0 ) {
252 # Get a large IP range that should include the user even if that
253 # person's IP address changes
254 $ip = $request->getIP();
255 if ( !IPUtils::isValid( $ip ) ) {
256 return;
257 }
258
259 $ip = IPUtils::isIPv6( $ip )
260 ? IPUtils::sanitizeRange( "$ip/32" )
261 : IPUtils::sanitizeRange( "$ip/16" );
262
263 # Bail out if a request already came from this range...
264 $cache = MediaWikiServices::getInstance()->getObjectCacheFactory()
265 ->getLocalClusterInstance();
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() {
281 $cache = MediaWikiServices::getInstance()->getObjectCacheFactory()
282 ->getLocalClusterInstance();
283
284 return self::MISS_FACTOR * $cache->get( $this->cacheMissKey( $cache ) );
285 }
286
291 protected function cacheMissKey( BagOStuff $cache ) {
292 return $cache->makeKey( static::class, 'misses', $this->mType, $this->mKey );
293 }
294}
295
297class_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.
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,...
getIP()
Work out the IP address based on various globals For trusted proxies, use the XFF client IP (first of...
Class representing a cache/ephemeral data store.
Definition BagOStuff.php:88
makeKey( $keygroup,... $components)
Make a cache key from the given components, in the default keyspace.