source: t29-www/lib/cache.php @ 296

Last change on this file since 296 was 277, checked in by sven, 12 years ago

JavaScript-Preferences-System, mit dem die Einstellungen fuers Menue erhalten bleiben. Refactoring des menu.js-Codes. startup.js um Reihenfolge der Scripte festzulegen. Diverse Bugfixes im PHP und Inhalt.

File size: 9.8 KB
Line 
1<?php
2/**
3 * Main caching and output system.
4 * Modes:
5 *  SKIP  = Skips any caching. By enabling this you just disable
6 *          the caching system which can be great for testing.
7 *          use_cache() will always return false and write_cache()
8 *          will just skip any work.
9 *
10 *  PURGE = Enforce a rewrite of the cache. use_cache() will always
11 *          return false and therefore the cache file will be rewritten
12 *          as if it didn't exist.
13 *
14 *  DEBUG = Debug cache validation calculation and output calculation.
15 *          This will print verbose data just whenever called. The
16 *          cache system can be run around that, but "NOT CHANGED" calls
17 *          will just abort the scenery. Somewhat weird.
18 *
19 *  VERBOSE = Print verbose Messages as HTML Comments (print_info)
20 *            or error messages in div spans. This is useful for any
21 *            HTML output but should be disabled for JS/CSS caching.
22 **/
23
24# Lightweight caching system
25class t29Cache {
26        const webroot_cache_dir = '/shared/cache'; # relative to webroot
27
28        public $skip = false;
29        public $purge = false;
30        public $debug = false;// debug calculation
31        public $verbose = false; // print html comments and errors
32
33        // these must be set after constructing!
34        public $cache_file; // must be set!
35        public $test_files = array(); // must be set!
36
37        private $mtime_cache_file = null; // needed for cache header output
38        private $is_valid = null; // cache output value
39
40        function __construct($debug=false, $verbose=false) {
41                // default values
42                $this->skip = isset($_GET['skip_cache']);
43                $this->purge = isset($_GET['purge_cache']);
44                $this->debug = isset($_GET['debug_cache']) || $debug;
45                $this->verbose = isset($_GET['verbose_cache']) || $verbose || $this->debug;
46        }
47       
48        /**
49         * expecting:
50         * @param $webroot /var/www/foo/bar  (no trailing slash!)
51         * @param $filename /de/bar/baz.htm  (starting with slash!)
52         * @returns absolute filename /var/www/foo/bar/cache/dir/de/bar/baz.htm
53         **/
54        function set_cache_file($webroot, $filename) {
55                $this->cache_file = $webroot . self::webroot_cache_dir . '/' . $filename;
56        }
57       
58        # helper function
59        public static function mkdir_recursive($pathname) {
60                is_dir(dirname($pathname)) || self::mkdir_recursive(dirname($pathname));
61                return is_dir($pathname) || @mkdir($pathname);
62        }
63
64        /**
65         * Calculates and compares the mtimes of the cache file and testing files.
66         * Doesn't obey any debug/skip/purge rules, just gives out if the cache file
67         * is valid or not.
68         * The result is cached in $is_valid, so you can call this (heavy to calc)
69         * method frequently.
70         **/
71        function is_valid() {
72                // no double calculation
73                if($this->is_valid !== null) return $this->is_valid;
74
75                if($this->debug) {
76                        print '<pre>';
77                        print 't29Cache: Validity Checking.'.PHP_EOL;
78                        print 'Cache file: '; var_dump($this->cache_file);
79                        print 'Test files: '; var_dump($this->test_files);
80                }
81
82                $this->mtime_cache_file = @filemtime($this->cache_file);
83                $mtime_test_files = array_map(
84                        function($x){return @filemtime($x);},
85                        $this->test_files);
86                $mtime_test_max = array_reduce($mtime_test_files, 'max');
87                $this->is_valid = $this->mtime_cache_file
88                        && $mtime_test_max < $this->mtime_cache_file;
89                       
90                if($this->debug) {
91                        print 'Cache mtime: '; var_dump($this->mtime_cache_file);
92                        print 'Test files mtimes: '; var_dump($mtime_test_files);
93                        print 'CACHE IS VALID: '; var_dump($this->is_valid);
94                }
95
96                return $this->is_valid;
97        }
98
99        /**
100         * The "front end" to is_valid: Takes skipping and purging rules into
101         * account to decide whether you shall use the cache or not.
102         * @returns boolean value if cache is supposed to be valid or not.
103         **/
104        function shall_use() {
105                $test = $this->is_valid() && !$this->skip && !$this->purge;
106                if($this->debug) {
107                        print 'Shall use Cache: '; var_dump($test);
108                }
109                return $test;
110        }
111       
112        /**
113         * Prints out cache file with according HTTP headers and HTTP caching
114         * (HTTP_IF_MODIFIED_SINCE). You must not print out anything after such a http
115         * header! Therefore consider using the convenience method print_cache_and_exit()
116         * instead of this one or exit on yourself.
117         *
118         * @param $ignore_http_caching Don't check the clients HTTP cache
119         **/
120        function print_cache($ignore_http_caching=false) {
121                // make sure we already have called is_valid
122                if($this->mtime_cache_file === null)
123                        $this->is_valid(); // calculate mtime
124
125                if(!$this->debug) {
126                        header("Last-Modified: ".gmdate("D, d M Y H:i:s", $this->mtime_cache_file)." GMT");
127                        //header("Etag: $etag");
128                }
129
130                if(!$ignore_http_caching && @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $this->mtime_cache_file) {
131                        // client already has page cached locally
132                        if($this->debug) {
133                                print 'Would send Client a NOT MODIFIED answer.' . PHP_EOL;
134                        } else {
135                                header("HTTP/1.1 304 Not Modified");
136                                // important - no more output!
137                        }
138                } else {
139                        if($this->debug) {
140                                print 'Would send Client output of ' . $this->cache_file . PHP_EOL;
141                        } else {
142                                readfile($this->cache_file);
143                        }
144                }
145        }
146       
147        /**
148         * Convenience method which will exit the program after calling print_cache().
149         **/
150        function print_cache_and_exit() {
151                $this->print_cache();
152                exit;
153        }
154
155        /**
156         * Convenience method for calling the typical workflow: Test if the cache file
157         * shall be used, and if yes, print it out and exit the program. If this method
158         * returns, you can be sure that you have to create a (new) cache file. So your
159         * typical code will look like:
160         *
161         *  $cache = new t29Cache();
162         *  // initialization stuff $cache->... = ...
163         *  $cache->try_cache_and_exit();
164         *  // so we are still alive - go making content!
165         *  $cache->start_cache(...);
166         *  echo "be happy";
167         *  $cache->write_cache(); // at least if you didn't use any registered shutdown function.
168         *
169         **/
170        function try_cache_and_exit() {
171                if($this->shall_use())
172                        $this->print_cache_and_exit();
173        }
174
175        /**
176         * Start Output Buffering and prepare a register shutdown func,
177         * if wanted. Most likely you will call this method with arguments,
178         * otherwise it just calls ob_start() and that's it.
179         *
180         * $register_shutdown_func can be:
181         *   - Just 'true': Then it will tell t29Cache to register it's
182         *     own write_cache() method as a shutdown function for PHP so
183         *     the cache file is written on script exit.
184         *   - A callable (method or function callable). This will be
185         *     registered at PHP shutdown, *afterwards* our own write_cache
186         *     method will be called. Thus you can inject printing some
187         *     footer code.
188         *   - A filter function. When $shutdown_func_is_filter is set to
189         *     some true value, your given callable $register_shutdown_func
190         *     will be used as a filter, thus being called with the whole
191         *     output buffer and expected to return some modification of that
192         *     stuff. Example:
193         *        $cache->start_cache(function($text) {
194         *          return strtoupper($text); }, true);
195         *     This will convert all page content to uppercase before saving
196         *     the stuff to the cache file.
197         **/
198        function start_cache($register_shutdown_func=null, $shutdown_func_is_filter=false) {
199                if($this->debug)
200                        print "Will start caching with shutdown: " . $register_shutdown_func . PHP_EOL;
201                ob_start();
202
203                if(is_callable($register_shutdown_func)) {
204                        // callback given: Register a shutdown function
205                        // which will call user's callback at first, then
206                        // our own write function
207                        $t = $this; // PHP stupidity
208                        register_shutdown_function(function()
209                          use($register_shutdown_func, $shutdown_func_is_filter, $t) {
210                                if($shutdown_func_is_filter) {
211                                        $content = ob_get_clean();
212                                        if($t->debug)
213                                                // can print output since OutputBuffering is finished
214                                                print 't29Cache: Applying user filter to output' . PHP_EOL;
215                                        $content = call_user_func($register_shutdown_func, $content);
216                                        print $content;
217                                        $t->write_cache($content);
218                                } else {
219                                        call_user_func($register_shutdown_func);
220                                        $t->write_cache();
221                                }
222                        });
223                } elseif($register_shutdown_func) {
224                        // only boolean value given: Just register our
225                        // own write function
226                        register_shutdown_function(array($this, 'write_cache'));
227                } else {
228                        // nothing given: Dont call our write function,
229                        // it will be called by hand.
230                }
231        }
232
233        /**
234         * Write Cache file. If the $content string is given, it will
235         * be used as the cache content. Otherwise, a running output buffering
236         * will be assumed (as start_cache fires it) and content will be
237         * extracted with ob_get_flush.
238         * @param $content Content to be used as cache content or OB content
239         * @param $clear_ob_cache Use ob_get_clean instead of flushing it. If given,
240         *                        will return $content instead of printing/keeping it.
241         **/
242        function write_cache($content=null, $clear_ob_cache=false) {
243                if(!$content)
244                        $content = ($clear_ob_cache ? ob_get_clean() : ob_get_flush());
245
246                if($this->skip) {
247                        $this->print_info('skipped cache and cache saving.');
248                        return; // do not save anything.
249                }
250               
251                if(!file_exists($this->cache_file)) {
252                        if(!self::mkdir_recursive(dirname($this->cache_file)))
253                                $this->print_error('Could not create recursive caching directories');
254                }
255               
256                if(@file_put_contents($this->cache_file, $content))
257                        $this->print_info('Wrote output cache successfully');
258                else
259                        $this->print_error('Could not write page output cache to '.$this->cache_file);
260
261                if($clear_ob_cache)
262                        return $content;
263        }
264       
265       
266        private function print_info($string, $even_if_nonverbose=false) {
267                if($this->verbose || $even_if_nonverbose)
268                        echo "<!-- t29Cache: $string -->\n";
269        }
270       
271        private function print_error($string, $even_if_nonverbose=false) {
272                if($this->verbose || $even_if_nonverbose)
273                        echo "<div class='error t29cache'>t29Cache: $string</div>\n";
274        }
275}
Note: See TracBrowser for help on using the repository browser.
© 2008 - 2013 technikum29 • Sven Köppel • Some rights reserved
Powered by Trac
Expect where otherwise noted, content on this site is licensed under a Creative Commons 3.0 License