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