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

Last change on this file since 269 was 265, checked in by sven, 12 years ago

Bugfixes:

  • menu.js, template.php, style.css: Loesung fuer abhauenden Menubutton (get-menu) am Ende der Seite (schob sich unter Footer) gefunden, Menue kann jetzt zu jeder Zeit eingeblendet werden.
  • cache.php: Dokumentation
  • navigation.xml: Typos beseitigt
File size: 9.3 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 = $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) 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        function print_cache() {
119                // make sure we already have called is_valid
120                if($this->mtime_cache_file === null)
121                        $this->is_valid(); // calculate mtime
122
123                if(!$this->debug) {
124                        header("Last-Modified: ".gmdate("D, d M Y H:i:s", $this->mtime_cache_file)." GMT");
125                        //header("Etag: $etag");
126                }
127
128                if(@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $this->mtime_cache_file) {
129                        // client already has page cached locally
130                        if($this->debug) {
131                                print 'Would send Client a NOT MODIFIED answer.' . PHP_EOL;
132                        } else {
133                                header("HTTP/1.1 304 Not Modified");
134                                // important - no more output!
135                        }
136                } else {
137                        if($this->debug) {
138                                print 'Would send Client output of ' . $this->cache_file . PHP_EOL;
139                        } else {
140                                readfile($this->cache_file);
141                        }
142                }
143        }
144       
145        /**
146         * Convenience method which will exit the program after calling print_cache().
147         **/
148        function print_cache_and_exit() {
149                $this->print_cache();
150                exit;
151        }
152
153        /**
154         * Convenience method for calling the typical workflow: Test if the cache file
155         * shall be used, and if yes, print it out and exit the program. If this method
156         * returns, you can be sure that you have to create a (new) cache file. So your
157         * typical code will look like:
158         *
159         *  $cache = new t29Cache();
160         *  // initialization stuff $cache->... = ...
161         *  $cache->try_cache_and_exit();
162         *  // so we are still alive - go making content!
163         *  $cache->start_cache(...);
164         *  echo "be happy";
165         *  $cache->write_cache(); // at least if you didn't use any registered shutdown function.
166         *
167         **/
168        function try_cache_and_exit() {
169                if($this->shall_use())
170                        $this->print_cache_and_exit();
171        }
172
173        /**
174         * Start Output Buffering and prepare a register shutdown func,
175         * if wanted. Most likely you will call this method with arguments,
176         * otherwise it just calls ob_start() and that's it.
177         *
178         * $register_shutdown_func can be:
179         *   - Just 'true': Then it will tell t29Cache to register it's
180         *     own write_cache() method as a shutdown function for PHP so
181         *     the cache file is written on script exit.
182         *   - A callable (method or function callable). This will be
183         *     registered at PHP shutdown, *afterwards* our own write_cache
184         *     method will be called. Thus you can inject printing some
185         *     footer code.
186         *   - A filter function. When $shutdown_func_is_filter is set to
187         *     some true value, your given callable $register_shutdown_func
188         *     will be used as a filter, thus being called with the whole
189         *     output buffer and expected to return some modification of that
190         *     stuff. Example:
191         *        $cache->start_cache(function($text) {
192         *          return strtoupper($text); }, true);
193         *     This will convert all page content to uppercase before saving
194         *     the stuff to the cache file.
195         **/
196        function start_cache($register_shutdown_func=null, $shutdown_func_is_filter=false) {
197                if($this->debug)
198                        print "Will start caching with shutdown: " . $register_shutdown_func . PHP_EOL;
199                ob_start();
200
201                if(is_callable($register_shutdown_func)) {
202                        // callback given: Register a shutdown function
203                        // which will call user's callback at first, then
204                        // our own write function
205                        $t = $this; // PHP stupidity
206                        register_shutdown_function(function()
207                          use($register_shutdown_func, $shutdown_func_is_filter, $t) {
208                                if($shutdown_func_is_filter) {
209                                        $content = ob_get_flush();
210                                        if($t->debug)
211                                                // can print output since OutputBuffering is finished
212                                                print 't29Cache: Applying user filter to output' . PHP_EOL;
213                                        $t->write_cache(call_user_func($register_shutdown_func, $content));
214                                } else {
215                                        call_user_func($register_shutdown_func);
216                                        $t->write_cache();
217                                }
218                        });
219                } elseif($register_shutdown_func) {
220                        // only boolean value given: Just register our
221                        // own write function
222                        register_shutdown_function(array($this, 'write_cache'));
223                } else {
224                        // nothing given: Dont call our write function,
225                        // it will be called by hand.
226                }
227        }
228
229        /**
230         * Write Cache file. If the $content string is given, it will
231         * be used as the cache content. Otherwise, a running output buffering
232         * will be assumed (as start_cache fires it) and content will be
233         * extracted with ob_get_flush.
234         **/
235        function write_cache($content=null) {
236                if(!$content)
237                        $content = ob_get_flush();
238
239                if($this->skip) {
240                        $this->print_info('skipped cache and cache saving.');
241                        return; // do not save anything.
242                }
243               
244                if(!file_exists($this->cache_file)) {
245                        if(!self::mkdir_recursive(dirname($this->cache_file)))
246                                $this->print_error('Could not create recursive caching directories');
247                }
248               
249                if(@file_put_contents($this->cache_file, $content))
250                        $this->print_info('Wrote output cache successfully');
251                else
252                        $this->print_error('Could not write page output cache to '.$this->cache_file);
253        }
254       
255       
256        private function print_info($string, $even_if_nonverbose=false) {
257                if($this->verbose || $even_if_nonverbose)
258                        echo "<!-- t29Cache: $string -->\n";
259        }
260       
261        private function print_error($string, $even_if_nonverbose=false) {
262                if($this->verbose || $even_if_nonverbose)
263                        echo "<div class='error t29cache'>t29Cache: $string</div>\n";
264        }
265}
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