1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
|
<?php
/**
* This class stores an arbitrary value along with its dependencies.
* Users should typically only use DependencyWrapper::getValueFromCache(),
* rather than instantiating one of these objects directly.
* @ingroup Cache
*/
class DependencyWrapper {
var $value;
var $deps;
/**
* Create an instance.
* @param $value Mixed: the user-supplied value
* @param $deps Mixed: a dependency or dependency array. All dependencies
* must be objects implementing CacheDependency.
*/
function __construct( $value = false, $deps = array() ) {
$this->value = $value;
if ( !is_array( $deps ) ) {
$deps = array( $deps );
}
$this->deps = $deps;
}
/**
* Returns true if any of the dependencies have expired
*
* @return bool
*/
function isExpired() {
foreach ( $this->deps as $dep ) {
if ( $dep->isExpired() ) {
return true;
}
}
return false;
}
/**
* Initialise dependency values in preparation for storing. This must be
* called before serialization.
*/
function initialiseDeps() {
foreach ( $this->deps as $dep ) {
$dep->loadDependencyValues();
}
}
/**
* Get the user-defined value
* @return bool|\Mixed
*/
function getValue() {
return $this->value;
}
/**
* Store the wrapper to a cache
*
* @param $cache BagOStuff
* @param $key
* @param $expiry
*/
function storeToCache( $cache, $key, $expiry = 0 ) {
$this->initialiseDeps();
$cache->set( $key, $this, $expiry );
}
/**
* Attempt to get a value from the cache. If the value is expired or missing,
* it will be generated with the callback function (if present), and the newly
* calculated value will be stored to the cache in a wrapper.
*
* @param $cache BagOStuff a cache object such as $wgMemc
* @param $key String: the cache key
* @param $expiry Integer: the expiry timestamp or interval in seconds
* @param $callback Mixed: the callback for generating the value, or false
* @param $callbackParams Array: the function parameters for the callback
* @param $deps Array: the dependencies to store on a cache miss. Note: these
* are not the dependencies used on a cache hit! Cache hits use the stored
* dependency array.
*
* @return mixed The value, or null if it was not present in the cache and no
* callback was defined.
*/
static function getValueFromCache( $cache, $key, $expiry = 0, $callback = false,
$callbackParams = array(), $deps = array() )
{
$obj = $cache->get( $key );
if ( is_object( $obj ) && $obj instanceof DependencyWrapper && !$obj->isExpired() ) {
$value = $obj->value;
} elseif ( $callback ) {
$value = call_user_func_array( $callback, $callbackParams );
# Cache the newly-generated value
$wrapper = new DependencyWrapper( $value, $deps );
$wrapper->storeToCache( $cache, $key, $expiry );
} else {
$value = null;
}
return $value;
}
}
/**
* @ingroup Cache
*/
abstract class CacheDependency {
/**
* Returns true if the dependency is expired, false otherwise
*/
abstract function isExpired();
/**
* Hook to perform any expensive pre-serialize loading of dependency values.
*/
function loadDependencyValues() { }
}
/**
* @ingroup Cache
*/
class FileDependency extends CacheDependency {
var $filename, $timestamp;
/**
* Create a file dependency
*
* @param $filename String: the name of the file, preferably fully qualified
* @param $timestamp Mixed: the unix last modified timestamp, or false if the
* file does not exist. If omitted, the timestamp will be loaded from
* the file.
*
* A dependency on a nonexistent file will be triggered when the file is
* created. A dependency on an existing file will be triggered when the
* file is changed.
*/
function __construct( $filename, $timestamp = null ) {
$this->filename = $filename;
$this->timestamp = $timestamp;
}
/**
* @return array
*/
function __sleep() {
$this->loadDependencyValues();
return array( 'filename', 'timestamp' );
}
function loadDependencyValues() {
if ( is_null( $this->timestamp ) ) {
if ( !file_exists( $this->filename ) ) {
# Dependency on a non-existent file
# This is a valid concept!
$this->timestamp = false;
} else {
$this->timestamp = filemtime( $this->filename );
}
}
}
/**
* @return bool
*/
function isExpired() {
if ( !file_exists( $this->filename ) ) {
if ( $this->timestamp === false ) {
# Still nonexistent
return false;
} else {
# Deleted
wfDebug( "Dependency triggered: {$this->filename} deleted.\n" );
return true;
}
} else {
$lastmod = filemtime( $this->filename );
if ( $lastmod > $this->timestamp ) {
# Modified or created
wfDebug( "Dependency triggered: {$this->filename} changed.\n" );
return true;
} else {
# Not modified
return false;
}
}
}
}
/**
* @ingroup Cache
*/
class TitleDependency extends CacheDependency {
var $titleObj;
var $ns, $dbk;
var $touched;
/**
* Construct a title dependency
* @param $title Title
*/
function __construct( Title $title ) {
$this->titleObj = $title;
$this->ns = $title->getNamespace();
$this->dbk = $title->getDBkey();
}
function loadDependencyValues() {
$this->touched = $this->getTitle()->getTouched();
}
/**
* Get rid of bulky Title object for sleep
*
* @return array
*/
function __sleep() {
return array( 'ns', 'dbk', 'touched' );
}
/**
* @return Title
*/
function getTitle() {
if ( !isset( $this->titleObj ) ) {
$this->titleObj = Title::makeTitle( $this->ns, $this->dbk );
}
return $this->titleObj;
}
/**
* @return bool
*/
function isExpired() {
$touched = $this->getTitle()->getTouched();
if ( $this->touched === false ) {
if ( $touched === false ) {
# Still missing
return false;
} else {
# Created
return true;
}
} elseif ( $touched === false ) {
# Deleted
return true;
} elseif ( $touched > $this->touched ) {
# Updated
return true;
} else {
# Unmodified
return false;
}
}
}
/**
* @ingroup Cache
*/
class TitleListDependency extends CacheDependency {
var $linkBatch;
var $timestamps;
/**
* Construct a dependency on a list of titles
* @param $linkBatch LinkBatch
*/
function __construct( LinkBatch $linkBatch ) {
$this->linkBatch = $linkBatch;
}
/**
* @return array
*/
function calculateTimestamps() {
# Initialise values to false
$timestamps = array();
foreach ( $this->getLinkBatch()->data as $ns => $dbks ) {
if ( count( $dbks ) > 0 ) {
$timestamps[$ns] = array();
foreach ( $dbks as $dbk => $value ) {
$timestamps[$ns][$dbk] = false;
}
}
}
# Do the query
if ( count( $timestamps ) ) {
$dbr = wfGetDB( DB_SLAVE );
$where = $this->getLinkBatch()->constructSet( 'page', $dbr );
$res = $dbr->select(
'page',
array( 'page_namespace', 'page_title', 'page_touched' ),
$where,
__METHOD__
);
foreach ( $res as $row ) {
$timestamps[$row->page_namespace][$row->page_title] = $row->page_touched;
}
}
return $timestamps;
}
function loadDependencyValues() {
$this->timestamps = $this->calculateTimestamps();
}
/**
* @return array
*/
function __sleep() {
return array( 'timestamps' );
}
/**
* @return LinkBatch
*/
function getLinkBatch() {
if ( !isset( $this->linkBatch ) ) {
$this->linkBatch = new LinkBatch;
$this->linkBatch->setArray( $this->timestamps );
}
return $this->linkBatch;
}
/**
* @return bool
*/
function isExpired() {
$newTimestamps = $this->calculateTimestamps();
foreach ( $this->timestamps as $ns => $dbks ) {
foreach ( $dbks as $dbk => $oldTimestamp ) {
$newTimestamp = $newTimestamps[$ns][$dbk];
if ( $oldTimestamp === false ) {
if ( $newTimestamp === false ) {
# Still missing
} else {
# Created
return true;
}
} elseif ( $newTimestamp === false ) {
# Deleted
return true;
} elseif ( $newTimestamp > $oldTimestamp ) {
# Updated
return true;
} else {
# Unmodified
}
}
}
return false;
}
}
/**
* @ingroup Cache
*/
class GlobalDependency extends CacheDependency {
var $name, $value;
function __construct( $name ) {
$this->name = $name;
$this->value = $GLOBALS[$name];
}
/**
* @return bool
*/
function isExpired() {
if( !isset($GLOBALS[$this->name]) ) {
return true;
}
return $GLOBALS[$this->name] != $this->value;
}
}
/**
* @ingroup Cache
*/
class ConstantDependency extends CacheDependency {
var $name, $value;
function __construct( $name ) {
$this->name = $name;
$this->value = constant( $name );
}
/**
* @return bool
*/
function isExpired() {
return constant( $this->name ) != $this->value;
}
}
|