MediaWiki master
FileCacheBase.php
Go to the documentation of this file.
1<?php
10namespace MediaWiki\Cache;
11
16use Wikimedia\IPUtils;
18use Wikimedia\Timestamp\TimestampFormat as TS;
19
25abstract class FileCacheBase {
26
27 private const CONSTRUCTOR_OPTIONS = [
33 ];
34
36 protected $mKey;
38 protected $mType = 'object';
40 protected $mExt = 'cache';
42 protected $mFilePath;
44 protected $mUseGzip;
46 protected $mCached;
48 protected $options;
49
50 /* @todo configurable? */
51 private const MISS_FACTOR = 15; // log 1 every MISS_FACTOR cache misses
52 private const MISS_TTL_SEC = 3600; // how many seconds ago is "recent"
53
54 protected function __construct() {
55 $this->options = new ServiceOptions(
56 self::CONSTRUCTOR_OPTIONS,
57 MediaWikiServices::getInstance()->getMainConfig()
58 );
59 $this->mUseGzip = (bool)$this->options->get( MainConfigNames::UseGzip );
60 }
61
66 final protected function baseCacheDirectory() {
67 return $this->options->get( MainConfigNames::FileCacheDirectory );
68 }
69
74 abstract protected function cacheDirectory();
75
80 protected function cachePath() {
81 if ( $this->mFilePath !== null ) {
82 return $this->mFilePath;
83 }
84
85 $dir = $this->cacheDirectory();
86 # Build directories (methods include the trailing "/")
87 $subDirs = $this->typeSubdirectory() . $this->hashSubdirectory();
88 # Avoid extension confusion
89 $key = str_replace( '.', '%2E', urlencode( $this->mKey ) );
90 # Build the full file path
91 $this->mFilePath = "{$dir}/{$subDirs}{$key}.{$this->mExt}";
92 if ( $this->useGzip() ) {
93 $this->mFilePath .= '.gz';
94 }
95
96 return $this->mFilePath;
97 }
98
103 public function isCached() {
104 $this->mCached ??= is_file( $this->cachePath() );
105
106 return $this->mCached;
107 }
108
113 public function cacheTimestamp() {
114 $timestamp = filemtime( $this->cachePath() );
115
116 return ( $timestamp !== false )
117 ? wfTimestamp( TS::MW, $timestamp )
118 : false;
119 }
120
127 public function isCacheGood( $timestamp = '' ) {
128 $cacheEpoch = $this->options->get( MainConfigNames::CacheEpoch );
129
130 if ( !$this->isCached() ) {
131 return false;
132 }
133
134 $cachetime = $this->cacheTimestamp();
135 $good = ( $timestamp <= $cachetime && $cacheEpoch <= $cachetime );
136 wfDebug( __METHOD__ .
137 ": cachetime $cachetime, touched '{$timestamp}' epoch {$cacheEpoch}, good " . wfBoolToStr( $good ) );
138
139 return $good;
140 }
141
146 protected function useGzip() {
147 return $this->mUseGzip;
148 }
149
154 public function fetchText() {
155 if ( $this->useGzip() ) {
156 $fh = gzopen( $this->cachePath(), 'rb' );
157
158 return stream_get_contents( $fh );
159 } else {
160 return file_get_contents( $this->cachePath() );
161 }
162 }
163
169 public function saveText( $text ) {
170 if ( $this->useGzip() ) {
171 $text = gzencode( $text );
172 }
173
174 $this->checkCacheDirs(); // build parent dir
175 if ( !file_put_contents( $this->cachePath(), $text, LOCK_EX ) ) {
176 wfDebug( __METHOD__ . "() failed saving " . $this->cachePath() );
177 $this->mCached = null;
178
179 return false;
180 }
181
182 $this->mCached = true;
183
184 return $text;
185 }
186
191 public function clearCache() {
192 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
193 @unlink( $this->cachePath() );
194 $this->mCached = false;
195 }
196
201 protected function checkCacheDirs() {
202 wfMkdirParents( dirname( $this->cachePath() ), null, __METHOD__ );
203 }
204
212 protected function typeSubdirectory() {
213 return $this->mType . '/';
214 }
215
221 protected function hashSubdirectory() {
222 $fileCacheDepth = $this->options->get( MainConfigNames::FileCacheDepth );
223
224 $subdir = '';
225 if ( $fileCacheDepth > 0 ) {
226 $hash = md5( $this->mKey );
227 for ( $i = 1; $i <= $fileCacheDepth; $i++ ) {
228 $subdir .= substr( $hash, 0, $i ) . '/';
229 }
230 }
231
232 return $subdir;
233 }
234
240 public function incrMissesRecent( WebRequest $request ) {
241 if ( mt_rand( 1, self::MISS_FACTOR ) == 1 ) {
242 # Get a large IP range that should include the user even if that
243 # person's IP address changes
244 $ip = $request->getIP();
245 if ( !IPUtils::isValid( $ip ) ) {
246 return;
247 }
248
249 $ip = IPUtils::isIPv6( $ip )
250 ? IPUtils::sanitizeRange( "$ip/32" )
251 : IPUtils::sanitizeRange( "$ip/16" );
252
253 # Bail out if a request already came from this range...
254 $cache = MediaWikiServices::getInstance()->getObjectCacheFactory()
255 ->getLocalClusterInstance();
256 $key = $cache->makeKey( static::class, 'attempt', $this->mType, $this->mKey, $ip );
257 if ( !$cache->add( $key, 1, self::MISS_TTL_SEC ) ) {
258 return; // possibly the same user
259 }
260
261 # Increment the number of cache misses...
262 $cache->incrWithInit( $this->cacheMissKey( $cache ), self::MISS_TTL_SEC );
263 }
264 }
265
270 public function getMissesRecent() {
271 $cache = MediaWikiServices::getInstance()->getObjectCacheFactory()
272 ->getLocalClusterInstance();
273
274 return self::MISS_FACTOR * $cache->get( $this->cacheMissKey( $cache ) );
275 }
276
281 protected function cacheMissKey( BagOStuff $cache ) {
282 return $cache->makeKey( static::class, 'misses', $this->mType, $this->mKey );
283 }
284}
285
287class_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.
wfTimestamp( $outputtype=TS::UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfMkdirParents( $dir, $mode=null, $caller=null)
Make directory, and make all parent directories if they don't exist.
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:73
makeKey( $keygroup,... $components)
Make a cache key from the given components, in the default keyspace.