xref: /dokuwiki/inc/Cache/Cache.php (revision 0db5771e6b5f779df34a039ad49d4652eaf21893)
1<?php
2
3namespace dokuwiki\Cache;
4
5/**
6 * Generic handling of caching
7 */
8class Cache
9{
10    public $key = '';          // primary identifier for this item
11    public $ext = '';          // file ext for cache data, secondary identifier for this item
12    public $cache = '';        // cache file name
13    public $depends = array(); // array containing cache dependency information,
14    //   used by _useCache to determine cache validity
15
16    public $_event = '';       // event to be triggered during useCache
17    public $_time;
18    public $_nocache = false;  // if set to true, cache will not be used or stored
19
20    /**
21     * @param string $key primary identifier
22     * @param string $ext file extension
23     */
24    public function __construct($key, $ext)
25    {
26        $this->key = $key;
27        $this->ext = $ext;
28        $this->cache = getCacheName($key, $ext);
29    }
30
31    /**
32     * public method to determine whether the cache can be used
33     *
34     * to assist in centralisation of event triggering and calculation of cache statistics,
35     * don't override this function override _useCache()
36     *
37     * @param  array $depends array of cache dependencies, support dependecies:
38     *                            'age'   => max age of the cache in seconds
39     *                            'files' => cache must be younger than mtime of each file
40     *                                       (nb. dependency passes if file doesn't exist)
41     *
42     * @return bool    true if cache can be used, false otherwise
43     */
44    public function useCache($depends = array())
45    {
46        $this->depends = $depends;
47        $this->_addDependencies();
48
49        if ($this->_event) {
50            return $this->_stats(trigger_event($this->_event, $this, array($this, '_useCache')));
51        } else {
52            return $this->_stats($this->_useCache());
53        }
54    }
55
56    /**
57     * private method containing cache use decision logic
58     *
59     * this function processes the following keys in the depends array
60     *   purge - force a purge on any non empty value
61     *   age   - expire cache if older than age (seconds)
62     *   files - expire cache if any file in this array was updated more recently than the cache
63     *
64     * Note that this function needs to be public as it is used as callback for the event handler
65     *
66     * can be overridden
67     *
68     * @return bool               see useCache()
69     */
70    public function _useCache()
71    {
72
73        if ($this->_nocache) {
74            return false;
75        }                              // caching turned off
76        if (!empty($this->depends['purge'])) {
77            return false;
78        }              // purge requested?
79        if (!($this->_time = @filemtime($this->cache))) {
80            return false;
81        }   // cache exists?
82
83        // cache too old?
84        if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) {
85            return false;
86        }
87
88        if (!empty($this->depends['files'])) {
89            foreach ($this->depends['files'] as $file) {
90                if ($this->_time <= @filemtime($file)) {
91                    return false;
92                }         // cache older than files it depends on?
93            }
94        }
95
96        return true;
97    }
98
99    /**
100     * add dependencies to the depends array
101     *
102     * this method should only add dependencies,
103     * it should not remove any existing dependencies and
104     * it should only overwrite a dependency when the new value is more stringent than the old
105     */
106    protected function _addDependencies()
107    {
108        global $INPUT;
109        if ($INPUT->has('purge')) {
110            $this->depends['purge'] = true;
111        }   // purge requested
112    }
113
114    /**
115     * retrieve the cached data
116     *
117     * @param   bool $clean true to clean line endings, false to leave line endings alone
118     * @return  string          cache contents
119     */
120    public function retrieveCache($clean = true)
121    {
122        return io_readFile($this->cache, $clean);
123    }
124
125    /**
126     * cache $data
127     *
128     * @param   string $data the data to be cached
129     * @return  bool           true on success, false otherwise
130     */
131    public function storeCache($data)
132    {
133        if ($this->_nocache) {
134            return false;
135        }
136
137        return io_savefile($this->cache, $data);
138    }
139
140    /**
141     * remove any cached data associated with this cache instance
142     */
143    public function removeCache()
144    {
145        @unlink($this->cache);
146    }
147
148    /**
149     * Record cache hits statistics.
150     * (Only when debugging allowed, to reduce overhead.)
151     *
152     * @param    bool $success result of this cache use attempt
153     * @return   bool              pass-thru $success value
154     */
155    protected function _stats($success)
156    {
157        global $conf;
158        static $stats = null;
159        static $file;
160
161        if (!$conf['allowdebug']) {
162            return $success;
163        }
164
165        if (is_null($stats)) {
166            $file = $conf['cachedir'] . '/cache_stats.txt';
167            $lines = explode("\n", io_readFile($file));
168
169            foreach ($lines as $line) {
170                $i = strpos($line, ',');
171                $stats[substr($line, 0, $i)] = $line;
172            }
173        }
174
175        if (isset($stats[$this->ext])) {
176            list($ext, $count, $hits) = explode(',', $stats[$this->ext]);
177        } else {
178            $ext = $this->ext;
179            $count = 0;
180            $hits = 0;
181        }
182
183        $count++;
184        if ($success) {
185            $hits++;
186        }
187        $stats[$this->ext] = "$ext,$count,$hits";
188
189        io_saveFile($file, join("\n", $stats));
190
191        return $success;
192    }
193}
194