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 {
40
41 private const CONSTRUCTOR_OPTIONS = [
47 ];
48
50 protected $mKey;
52 protected $mType = 'object';
54 protected $mExt = 'cache';
56 protected $mFilePath;
58 protected $mUseGzip;
60 protected $mCached;
62 protected $options;
63
64 /* @todo configurable? */
65 private const MISS_FACTOR = 15; // log 1 every MISS_FACTOR cache misses
66 private const MISS_TTL_SEC = 3600; // how many seconds ago is "recent"
67
68 protected function __construct() {
69 $this->options = new ServiceOptions(
70 self::CONSTRUCTOR_OPTIONS,
71 MediaWikiServices::getInstance()->getMainConfig()
72 );
73 $this->mUseGzip = (bool)$this->options->get( MainConfigNames::UseGzip );
74 }
75
80 final protected function baseCacheDirectory() {
81 return $this->options->get( MainConfigNames::FileCacheDirectory );
82 }
83
88 abstract protected function cacheDirectory();
89
94 protected function cachePath() {
95 if ( $this->mFilePath !== null ) {
96 return $this->mFilePath;
97 }
98
99 $dir = $this->cacheDirectory();
100 # Build directories (methods include the trailing "/")
101 $subDirs = $this->typeSubdirectory() . $this->hashSubdirectory();
102 # Avoid extension confusion
103 $key = str_replace( '.', '%2E', urlencode( $this->mKey ) );
104 # Build the full file path
105 $this->mFilePath = "{$dir}/{$subDirs}{$key}.{$this->mExt}";
106 if ( $this->useGzip() ) {
107 $this->mFilePath .= '.gz';
108 }
109
110 return $this->mFilePath;
111 }
112
117 public function isCached() {
118 $this->mCached ??= is_file( $this->cachePath() );
119
120 return $this->mCached;
121 }
122
127 public function cacheTimestamp() {
128 $timestamp = filemtime( $this->cachePath() );
129
130 return ( $timestamp !== false )
131 ? wfTimestamp( TS_MW, $timestamp )
132 : false;
133 }
134
141 public function isCacheGood( $timestamp = '' ) {
142 $cacheEpoch = $this->options->get( MainConfigNames::CacheEpoch );
143
144 if ( !$this->isCached() ) {
145 return false;
146 }
147
148 $cachetime = $this->cacheTimestamp();
149 $good = ( $timestamp <= $cachetime && $cacheEpoch <= $cachetime );
150 wfDebug( __METHOD__ .
151 ": cachetime $cachetime, touched '{$timestamp}' epoch {$cacheEpoch}, good " . wfBoolToStr( $good ) );
152
153 return $good;
154 }
155
160 protected function useGzip() {
161 return $this->mUseGzip;
162 }
163
168 public function fetchText() {
169 if ( $this->useGzip() ) {
170 $fh = gzopen( $this->cachePath(), 'rb' );
171
172 return stream_get_contents( $fh );
173 } else {
174 return file_get_contents( $this->cachePath() );
175 }
176 }
177
183 public function saveText( $text ) {
184 if ( $this->useGzip() ) {
185 $text = gzencode( $text );
186 }
187
188 $this->checkCacheDirs(); // build parent dir
189 if ( !file_put_contents( $this->cachePath(), $text, LOCK_EX ) ) {
190 wfDebug( __METHOD__ . "() failed saving " . $this->cachePath() );
191 $this->mCached = null;
192
193 return false;
194 }
195
196 $this->mCached = true;
197
198 return $text;
199 }
200
205 public function clearCache() {
206 AtEase::suppressWarnings();
207 unlink( $this->cachePath() );
208 AtEase::restoreWarnings();
209 $this->mCached = false;
210 }
211
216 protected function checkCacheDirs() {
217 wfMkdirParents( dirname( $this->cachePath() ), null, __METHOD__ );
218 }
219
227 protected function typeSubdirectory() {
228 return $this->mType . '/';
229 }
230
236 protected function hashSubdirectory() {
237 $fileCacheDepth = $this->options->get( MainConfigNames::FileCacheDepth );
238
239 $subdir = '';
240 if ( $fileCacheDepth > 0 ) {
241 $hash = md5( $this->mKey );
242 for ( $i = 1; $i <= $fileCacheDepth; $i++ ) {
243 $subdir .= substr( $hash, 0, $i ) . '/';
244 }
245 }
246
247 return $subdir;
248 }
249
255 public function incrMissesRecent( WebRequest $request ) {
256 if ( mt_rand( 1, self::MISS_FACTOR ) == 1 ) {
257 # Get a large IP range that should include the user even if that
258 # person's IP address changes
259 $ip = $request->getIP();
260 if ( !IPUtils::isValid( $ip ) ) {
261 return;
262 }
263
264 $ip = IPUtils::isIPv6( $ip )
265 ? IPUtils::sanitizeRange( "$ip/32" )
266 : IPUtils::sanitizeRange( "$ip/16" );
267
268 # Bail out if a request already came from this range...
269 $cache = MediaWikiServices::getInstance()->getObjectCacheFactory()
270 ->getLocalClusterInstance();
271 $key = $cache->makeKey( static::class, 'attempt', $this->mType, $this->mKey, $ip );
272 if ( !$cache->add( $key, 1, self::MISS_TTL_SEC ) ) {
273 return; // possibly the same user
274 }
275
276 # Increment the number of cache misses...
277 $cache->incrWithInit( $this->cacheMissKey( $cache ), self::MISS_TTL_SEC );
278 }
279 }
280
285 public function getMissesRecent() {
286 $cache = MediaWikiServices::getInstance()->getObjectCacheFactory()
287 ->getLocalClusterInstance();
288
289 return self::MISS_FACTOR * $cache->get( $this->cacheMissKey( $cache ) );
290 }
291
296 protected function cacheMissKey( BagOStuff $cache ) {
297 return $cache->makeKey( static::class, 'misses', $this->mType, $this->mKey );
298 }
299}
300
302class_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...
Abstract class for any ephemeral data store.
Definition BagOStuff.php:89
makeKey( $keygroup,... $components)
Make a cache key from the given components, in the default keyspace.