MediaWiki  master
FileCacheBase.php
Go to the documentation of this file.
1 <?php
27 use Wikimedia\AtEase\AtEase;
28 use Wikimedia\IPUtils;
29 
35 abstract 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  if ( $this->mCached === null ) {
110  $this->mCached = is_file( $this->cachePath() );
111  }
112 
113  return $this->mCached;
114  }
115 
120  public function cacheTimestamp() {
121  $timestamp = filemtime( $this->cachePath() );
122 
123  return ( $timestamp !== false )
124  ? wfTimestamp( TS_MW, $timestamp )
125  : false;
126  }
127 
134  public function isCacheGood( $timestamp = '' ) {
135  $cacheEpoch = $this->options->get( MainConfigNames::CacheEpoch );
136 
137  if ( !$this->isCached() ) {
138  return false;
139  }
140 
141  $cachetime = $this->cacheTimestamp();
142  $good = ( $timestamp <= $cachetime && $cacheEpoch <= $cachetime );
143  wfDebug( __METHOD__ .
144  ": cachetime $cachetime, touched '{$timestamp}' epoch {$cacheEpoch}, good " . wfBoolToStr( $good ) );
145 
146  return $good;
147  }
148 
153  protected function useGzip() {
154  return $this->mUseGzip;
155  }
156 
161  public function fetchText() {
162  if ( $this->useGzip() ) {
163  $fh = gzopen( $this->cachePath(), 'rb' );
164 
165  return stream_get_contents( $fh );
166  } else {
167  return file_get_contents( $this->cachePath() );
168  }
169  }
170 
176  public function saveText( $text ) {
177  if ( $this->useGzip() ) {
178  $text = gzencode( $text );
179  }
180 
181  $this->checkCacheDirs(); // build parent dir
182  if ( !file_put_contents( $this->cachePath(), $text, LOCK_EX ) ) {
183  wfDebug( __METHOD__ . "() failed saving " . $this->cachePath() );
184  $this->mCached = null;
185 
186  return false;
187  }
188 
189  $this->mCached = true;
190 
191  return $text;
192  }
193 
198  public function clearCache() {
199  AtEase::suppressWarnings();
200  unlink( $this->cachePath() );
201  AtEase::restoreWarnings();
202  $this->mCached = false;
203  }
204 
209  protected function checkCacheDirs() {
210  wfMkdirParents( dirname( $this->cachePath() ), null, __METHOD__ );
211  }
212 
220  protected function typeSubdirectory() {
221  return $this->mType . '/';
222  }
223 
229  protected function hashSubdirectory() {
230  $fileCacheDepth = $this->options->get( MainConfigNames::FileCacheDepth );
231 
232  $subdir = '';
233  if ( $fileCacheDepth > 0 ) {
234  $hash = md5( $this->mKey );
235  for ( $i = 1; $i <= $fileCacheDepth; $i++ ) {
236  $subdir .= substr( $hash, 0, $i ) . '/';
237  }
238  }
239 
240  return $subdir;
241  }
242 
248  public function incrMissesRecent( WebRequest $request ) {
249  if ( mt_rand( 0, self::MISS_FACTOR - 1 ) == 0 ) {
250  # Get a large IP range that should include the user even if that
251  # person's IP address changes
252  $ip = $request->getIP();
253  if ( !IPUtils::isValid( $ip ) ) {
254  return;
255  }
256 
257  $ip = IPUtils::isIPv6( $ip )
258  ? IPUtils::sanitizeRange( "$ip/32" )
259  : IPUtils::sanitizeRange( "$ip/16" );
260 
261  # Bail out if a request already came from this range...
263  $key = $cache->makeKey( static::class, 'attempt', $this->mType, $this->mKey, $ip );
264  if ( !$cache->add( $key, 1, self::MISS_TTL_SEC ) ) {
265  return; // possibly the same user
266  }
267 
268  # Increment the number of cache misses...
269  $cache->incrWithInit( $this->cacheMissKey( $cache ), self::MISS_TTL_SEC );
270  }
271  }
272 
277  public function getMissesRecent() {
279 
280  return self::MISS_FACTOR * $cache->get( $this->cacheMissKey( $cache ) );
281  }
282 
287  protected function cacheMissKey( BagOStuff $cache ) {
288  return $cache->makeKey( static::class, 'misses', $this->mType, $this->mKey );
289  }
290 }
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:87
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.
static getLocalClusterInstance()
Get the main cluster-local cache object.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:43
getIP()
Work out the IP address based on various globals For trusted proxies, use the XFF client IP (first of...
$cache
Definition: mcc.php:33