Subversion Repositories ALCASAR

Rev

Details | Last modification | View Log

Rev Author Line No. Line
2809 rexy 1
<?php
2
/**
3
 * Smarty Internal Plugin
4
 *
5
 * @package    Smarty
6
 * @subpackage Cacher
7
 */
8
 
9
/**
10
 * Smarty Cache Handler Base for Key/Value Storage Implementations
11
 * This class implements the functionality required to use simple key/value stores
12
 * for hierarchical cache groups. key/value stores like memcache or APC do not support
13
 * wildcards in keys, therefore a cache group cannot be cleared like "a|*" - which
14
 * is no problem to filesystem and RDBMS implementations.
15
 * This implementation is based on the concept of invalidation. While one specific cache
16
 * can be identified and cleared, any range of caches cannot be identified. For this reason
17
 * each level of the cache group hierarchy can have its own value in the store. These values
18
 * are nothing but microtimes, telling us when a particular cache group was cleared for the
19
 * last time. These keys are evaluated for every cache read to determine if the cache has
20
 * been invalidated since it was created and should hence be treated as inexistent.
21
 * Although deep hierarchies are possible, they are not recommended. Try to keep your
22
 * cache groups as shallow as possible. Anything up 3-5 parents should be ok. So
23
 * »a|b|c« is a good depth where »a|b|c|d|e|f|g|h|i|j|k« isn't. Try to join correlating
24
 * cache groups: if your cache groups look somewhat like »a|b|$page|$items|$whatever«
25
 * consider using »a|b|c|$page-$items-$whatever« instead.
26
 *
27
 * @package    Smarty
28
 * @subpackage Cacher
29
 * @author     Rodney Rehm
30
 */
31
abstract class Smarty_CacheResource_KeyValueStore extends Smarty_CacheResource
32
{
33
    /**
34
     * cache for contents
35
     *
36
     * @var array
37
     */
38
    protected $contents = array();
39
 
40
    /**
41
     * cache for timestamps
42
     *
43
     * @var array
44
     */
45
    protected $timestamps = array();
46
 
47
    /**
48
     * populate Cached Object with meta data from Resource
49
     *
50
     * @param Smarty_Template_Cached   $cached    cached object
51
     * @param Smarty_Internal_Template $_template template object
52
     *
53
     * @return void
54
     */
55
    public function populate(Smarty_Template_Cached $cached, Smarty_Internal_Template $_template)
56
    {
57
        $cached->filepath = $_template->source->uid . '#' . $this->sanitize($cached->source->resource) . '#' .
58
                            $this->sanitize($cached->cache_id) . '#' . $this->sanitize($cached->compile_id);
59
        $this->populateTimestamp($cached);
60
    }
61
 
62
    /**
63
     * populate Cached Object with timestamp and exists from Resource
64
     *
65
     * @param Smarty_Template_Cached $cached cached object
66
     *
67
     * @return void
68
     */
69
    public function populateTimestamp(Smarty_Template_Cached $cached)
70
    {
71
        if (!$this->fetch(
72
            $cached->filepath,
73
            $cached->source->name,
74
            $cached->cache_id,
75
            $cached->compile_id,
76
            $content,
77
            $timestamp,
78
            $cached->source->uid
79
        )
80
        ) {
81
            return;
82
        }
83
        $cached->content = $content;
84
        $cached->timestamp = (int)$timestamp;
85
        $cached->exists = !!$cached->timestamp;
86
    }
87
 
88
    /**
89
     * Read the cached template and process the header
90
     *
91
     * @param \Smarty_Internal_Template $_smarty_tpl do not change variable name, is used by compiled template
92
     * @param Smarty_Template_Cached    $cached      cached object
93
     * @param boolean                   $update      flag if called because cache update
94
     *
95
     * @return boolean                 true or false if the cached content does not exist
96
     */
97
    public function process(
98
        Smarty_Internal_Template $_smarty_tpl,
99
        Smarty_Template_Cached $cached = null,
100
        $update = false
101
    ) {
102
        if (!$cached) {
103
            $cached = $_smarty_tpl->cached;
104
        }
105
        $content = $cached->content ? $cached->content : null;
106
        $timestamp = $cached->timestamp ? $cached->timestamp : null;
107
        if ($content === null || !$timestamp) {
108
            if (!$this->fetch(
109
                $_smarty_tpl->cached->filepath,
110
                $_smarty_tpl->source->name,
111
                $_smarty_tpl->cache_id,
112
                $_smarty_tpl->compile_id,
113
                $content,
114
                $timestamp,
115
                $_smarty_tpl->source->uid
116
            )
117
            ) {
118
                return false;
119
            }
120
        }
121
        if (isset($content)) {
122
            eval('?>' . $content);
123
            return true;
124
        }
125
        return false;
126
    }
127
 
128
    /**
129
     * Write the rendered template output to cache
130
     *
131
     * @param Smarty_Internal_Template $_template template object
132
     * @param string                   $content   content to cache
133
     *
134
     * @return boolean                  success
135
     */
136
    public function writeCachedContent(Smarty_Internal_Template $_template, $content)
137
    {
138
        $this->addMetaTimestamp($content);
139
        return $this->write(array($_template->cached->filepath => $content), $_template->cache_lifetime);
140
    }
141
 
142
    /**
143
     * Read cached template from cache
144
     *
145
     * @param Smarty_Internal_Template $_template template object
146
     *
147
     * @return string|false  content
148
     */
149
    public function readCachedContent(Smarty_Internal_Template $_template)
150
    {
151
        $content = $_template->cached->content ? $_template->cached->content : null;
152
        $timestamp = null;
153
        if ($content === null) {
154
            if (!$this->fetch(
155
                $_template->cached->filepath,
156
                $_template->source->name,
157
                $_template->cache_id,
158
                $_template->compile_id,
159
                $content,
160
                $timestamp,
161
                $_template->source->uid
162
            )
163
            ) {
164
                return false;
165
            }
166
        }
167
        if (isset($content)) {
168
            return $content;
169
        }
170
        return false;
171
    }
172
 
173
    /**
174
     * Empty cache
175
     * {@internal the $exp_time argument is ignored altogether }}
176
     *
177
     * @param Smarty  $smarty   Smarty object
178
     * @param integer $exp_time expiration time [being ignored]
179
     *
180
     * @return integer number of cache files deleted [always -1]
181
     * @uses   purge() to clear the whole store
182
     * @uses   invalidate() to mark everything outdated if purge() is inapplicable
183
     */
184
    public function clearAll(Smarty $smarty, $exp_time = null)
185
    {
186
        if (!$this->purge()) {
187
            $this->invalidate(null);
188
        }
189
        return -1;
190
    }
191
 
192
    /**
193
     * Empty cache for a specific template
194
     * {@internal the $exp_time argument is ignored altogether}}
195
     *
196
     * @param Smarty  $smarty        Smarty object
197
     * @param string  $resource_name template name
198
     * @param string  $cache_id      cache id
199
     * @param string  $compile_id    compile id
200
     * @param integer $exp_time      expiration time [being ignored]
201
     *
202
     * @return int number of cache files deleted [always -1]
203
     * @throws \SmartyException
204
     * @uses   buildCachedFilepath() to generate the CacheID
205
     * @uses   invalidate() to mark CacheIDs parent chain as outdated
206
     * @uses   delete() to remove CacheID from cache
207
     */
208
    public function clear(Smarty $smarty, $resource_name, $cache_id, $compile_id, $exp_time)
209
    {
210
        $uid = $this->getTemplateUid($smarty, $resource_name);
211
        $cid = $uid . '#' . $this->sanitize($resource_name) . '#' . $this->sanitize($cache_id) . '#' .
212
               $this->sanitize($compile_id);
213
        $this->delete(array($cid));
214
        $this->invalidate($cid, $resource_name, $cache_id, $compile_id, $uid);
215
        return -1;
216
    }
217
 
218
    /**
219
     * Get template's unique ID
220
     *
221
     * @param Smarty $smarty        Smarty object
222
     * @param string $resource_name template name
223
     *
224
     * @return string filepath of cache file
225
     * @throws \SmartyException
226
     */
227
    protected function getTemplateUid(Smarty $smarty, $resource_name)
228
    {
229
        if (isset($resource_name)) {
230
            $source = Smarty_Template_Source::load(null, $smarty, $resource_name);
231
            if ($source->exists) {
232
                return $source->uid;
233
            }
234
        }
235
        return '';
236
    }
237
 
238
    /**
239
     * Sanitize CacheID components
240
     *
241
     * @param string $string CacheID component to sanitize
242
     *
243
     * @return string sanitized CacheID component
244
     */
245
    protected function sanitize($string)
246
    {
247
        $string = trim($string, '|');
248
        if (!$string) {
249
            return '';
250
        }
251
        return preg_replace('#[^\w\|]+#S', '_', $string);
252
    }
253
 
254
    /**
255
     * Fetch and prepare a cache object.
256
     *
257
     * @param string  $cid           CacheID to fetch
258
     * @param string  $resource_name template name
259
     * @param string  $cache_id      cache id
260
     * @param string  $compile_id    compile id
261
     * @param string  $content       cached content
262
     * @param integer &$timestamp    cached timestamp (epoch)
263
     * @param string  $resource_uid  resource's uid
264
     *
265
     * @return boolean success
266
     */
267
    protected function fetch(
268
        $cid,
269
        $resource_name = null,
270
        $cache_id = null,
271
        $compile_id = null,
272
        &$content = null,
273
        &$timestamp = null,
274
        $resource_uid = null
275
    ) {
276
        $t = $this->read(array($cid));
277
        $content = !empty($t[ $cid ]) ? $t[ $cid ] : null;
278
        $timestamp = null;
279
        if ($content && ($timestamp = $this->getMetaTimestamp($content))) {
280
            $invalidated =
281
                $this->getLatestInvalidationTimestamp($cid, $resource_name, $cache_id, $compile_id, $resource_uid);
282
            if ($invalidated > $timestamp) {
283
                $timestamp = null;
284
                $content = null;
285
            }
286
        }
287
        return !!$content;
288
    }
289
 
290
    /**
291
     * Add current microtime to the beginning of $cache_content
292
     * {@internal the header uses 8 Bytes, the first 4 Bytes are the seconds, the second 4 Bytes are the microseconds}}
293
     *
294
     * @param string &$content the content to be cached
295
     */
296
    protected function addMetaTimestamp(&$content)
297
    {
298
        $mt = explode(' ', microtime());
299
        $ts = pack('NN', $mt[ 1 ], (int)($mt[ 0 ] * 100000000));
300
        $content = $ts . $content;
301
    }
302
 
303
    /**
304
     * Extract the timestamp the $content was cached
305
     *
306
     * @param string &$content the cached content
307
     *
308
     * @return float  the microtime the content was cached
309
     */
310
    protected function getMetaTimestamp(&$content)
311
    {
312
        extract(unpack('N1s/N1m/a*content', $content));
313
        /**
314
         * @var  int $s
315
         * @var  int $m
316
         */
317
        return $s + ($m / 100000000);
318
    }
319
 
320
    /**
321
     * Invalidate CacheID
322
     *
323
     * @param string $cid           CacheID
324
     * @param string $resource_name template name
325
     * @param string $cache_id      cache id
326
     * @param string $compile_id    compile id
327
     * @param string $resource_uid  source's uid
328
     *
329
     * @return void
330
     */
331
    protected function invalidate(
332
        $cid = null,
333
        $resource_name = null,
334
        $cache_id = null,
335
        $compile_id = null,
336
        $resource_uid = null
337
    ) {
338
        $now = microtime(true);
339
        $key = null;
340
        // invalidate everything
341
        if (!$resource_name && !$cache_id && !$compile_id) {
342
            $key = 'IVK#ALL';
343
        } // invalidate all caches by template
344
        else {
345
            if ($resource_name && !$cache_id && !$compile_id) {
346
                $key = 'IVK#TEMPLATE#' . $resource_uid . '#' . $this->sanitize($resource_name);
347
            } // invalidate all caches by cache group
348
            else {
349
                if (!$resource_name && $cache_id && !$compile_id) {
350
                    $key = 'IVK#CACHE#' . $this->sanitize($cache_id);
351
                } // invalidate all caches by compile id
352
                else {
353
                    if (!$resource_name && !$cache_id && $compile_id) {
354
                        $key = 'IVK#COMPILE#' . $this->sanitize($compile_id);
355
                    } // invalidate by combination
356
                    else {
357
                        $key = 'IVK#CID#' . $cid;
358
                    }
359
                }
360
            }
361
        }
362
        $this->write(array($key => $now));
363
    }
364
 
365
    /**
366
     * Determine the latest timestamp known to the invalidation chain
367
     *
368
     * @param string $cid           CacheID to determine latest invalidation timestamp of
369
     * @param string $resource_name template name
370
     * @param string $cache_id      cache id
371
     * @param string $compile_id    compile id
372
     * @param string $resource_uid  source's filepath
373
     *
374
     * @return float  the microtime the CacheID was invalidated
375
     */
376
    protected function getLatestInvalidationTimestamp(
377
        $cid,
378
        $resource_name = null,
379
        $cache_id = null,
380
        $compile_id = null,
381
        $resource_uid = null
382
    ) {
383
        // abort if there is no CacheID
384
        if (false && !$cid) {
385
            return 0;
386
        }
387
        // abort if there are no InvalidationKeys to check
388
        if (!($_cid = $this->listInvalidationKeys($cid, $resource_name, $cache_id, $compile_id, $resource_uid))) {
389
            return 0;
390
        }
391
        // there are no InValidationKeys
392
        if (!($values = $this->read($_cid))) {
393
            return 0;
394
        }
395
        // make sure we're dealing with floats
396
        $values = array_map('floatval', $values);
397
        return max($values);
398
    }
399
 
400
    /**
401
     * Translate a CacheID into the list of applicable InvalidationKeys.
402
     * Splits 'some|chain|into|an|array' into array( '#clearAll#', 'some', 'some|chain', 'some|chain|into', ... )
403
     *
404
     * @param string $cid           CacheID to translate
405
     * @param string $resource_name template name
406
     * @param string $cache_id      cache id
407
     * @param string $compile_id    compile id
408
     * @param string $resource_uid  source's filepath
409
     *
410
     * @return array  list of InvalidationKeys
411
     * @uses   $invalidationKeyPrefix to prepend to each InvalidationKey
412
     */
413
    protected function listInvalidationKeys(
414
        $cid,
415
        $resource_name = null,
416
        $cache_id = null,
417
        $compile_id = null,
418
        $resource_uid = null
419
    ) {
420
        $t = array('IVK#ALL');
421
        $_name = $_compile = '#';
422
        if ($resource_name) {
423
            $_name .= $resource_uid . '#' . $this->sanitize($resource_name);
424
            $t[] = 'IVK#TEMPLATE' . $_name;
425
        }
426
        if ($compile_id) {
427
            $_compile .= $this->sanitize($compile_id);
428
            $t[] = 'IVK#COMPILE' . $_compile;
429
        }
430
        $_name .= '#';
431
        $cid = trim($cache_id, '|');
432
        if (!$cid) {
433
            return $t;
434
        }
435
        $i = 0;
436
        while (true) {
437
            // determine next delimiter position
438
            $i = strpos($cid, '|', $i);
439
            // add complete CacheID if there are no more delimiters
440
            if ($i === false) {
441
                $t[] = 'IVK#CACHE#' . $cid;
442
                $t[] = 'IVK#CID' . $_name . $cid . $_compile;
443
                $t[] = 'IVK#CID' . $_name . $_compile;
444
                break;
445
            }
446
            $part = substr($cid, 0, $i);
447
            // add slice to list
448
            $t[] = 'IVK#CACHE#' . $part;
449
            $t[] = 'IVK#CID' . $_name . $part . $_compile;
450
            // skip past delimiter position
451
            $i++;
452
        }
453
        return $t;
454
    }
455
 
456
    /**
457
     * Check is cache is locked for this template
458
     *
459
     * @param Smarty                 $smarty Smarty object
460
     * @param Smarty_Template_Cached $cached cached object
461
     *
462
     * @return boolean               true or false if cache is locked
463
     */
464
    public function hasLock(Smarty $smarty, Smarty_Template_Cached $cached)
465
    {
466
        $key = 'LOCK#' . $cached->filepath;
467
        $data = $this->read(array($key));
468
        return $data && time() - $data[ $key ] < $smarty->locking_timeout;
469
    }
470
 
471
    /**
472
     * Lock cache for this template
473
     *
474
     * @param Smarty                 $smarty Smarty object
475
     * @param Smarty_Template_Cached $cached cached object
476
     *
477
     * @return bool|void
478
     */
479
    public function acquireLock(Smarty $smarty, Smarty_Template_Cached $cached)
480
    {
481
        $cached->is_locked = true;
482
        $key = 'LOCK#' . $cached->filepath;
483
        $this->write(array($key => time()), $smarty->locking_timeout);
484
    }
485
 
486
    /**
487
     * Unlock cache for this template
488
     *
489
     * @param Smarty                 $smarty Smarty object
490
     * @param Smarty_Template_Cached $cached cached object
491
     *
492
     * @return bool|void
493
     */
494
    public function releaseLock(Smarty $smarty, Smarty_Template_Cached $cached)
495
    {
496
        $cached->is_locked = false;
497
        $key = 'LOCK#' . $cached->filepath;
498
        $this->delete(array($key));
499
    }
500
 
501
    /**
502
     * Read values for a set of keys from cache
503
     *
504
     * @param array $keys list of keys to fetch
505
     *
506
     * @return array list of values with the given keys used as indexes
507
     */
508
    abstract protected function read(array $keys);
509
 
510
    /**
511
     * Save values for a set of keys to cache
512
     *
513
     * @param array $keys   list of values to save
514
     * @param int   $expire expiration time
515
     *
516
     * @return boolean true on success, false on failure
517
     */
518
    abstract protected function write(array $keys, $expire = null);
519
 
520
    /**
521
     * Remove values from cache
522
     *
523
     * @param array $keys list of keys to delete
524
     *
525
     * @return boolean true on success, false on failure
526
     */
527
    abstract protected function delete(array $keys);
528
 
529
    /**
530
     * Remove *all* values from cache
531
     *
532
     * @return boolean true on success, false on failure
533
     */
534
    protected function purge()
535
    {
536
        return false;
537
    }
538
}