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

Last change on this file since 357 was 357, checked in by sven, 6 years ago

t29Host-Webroot-System entwickelt.

Damit können Installationen der technikum29-Website ab nun auch in Unterverzeichnissen
(bislang allerdings nur unterhalb des DocumentRoots) installiert werden, was einem
größere Flexibilität beim lokalen Aufsetzen der Seite liefert

File size: 10.5 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         * TODO FIXME Doku outdated for this method --
181         *
182         * $register_shutdown_func can be:
183         *   - Just 'true': Then it will tell t29Cache to register it's
184         *     own write_cache() method as a shutdown function for PHP so
185         *     the cache file is written on script exit.
186         *   - A callable (method or function callable). This will be
187         *     registered at PHP shutdown, *afterwards* our own write_cache
188         *     method will be called. Thus you can inject printing some
189         *     footer code.
190         *   - A filter function. When $shutdown_func_is_filter is set to
191         *     some true value, your given callable $register_shutdown_func
192         *     will be used as a filter, thus being called with the whole
193         *     output buffer and expected to return some modification of that
194         *     stuff. Example:
195         *        $cache->start_cache(function($text) {
196         *          return strtoupper($text); }, true);
197         *     This will convert all page content to uppercase before saving
198         *     the stuff to the cache file.
199         **/
200        //function start_cache($register_shutdown_func=null, $shutdown_func_is_filter=false) {
201        function start_cache(array $args) {
202                $defaults = array(
203                        'shutdown_func' => null,
204                        'filter_func'   => null,
205                        'write_cache'   => true,
206                );
207                $args = array_merge($defaults, $args);
208               
209                if($this->debug)
210                        print "Will start caching with shutdown: " . $args['shutdown_func'] . PHP_EOL;
211                       
212                // check if output file is writable; for logging and logging output
213                // purpose.
214                //if(!is_writable($this->cache_file))
215                //      print "Cache file not writable: ".$this->cache_file;
216                //      print "\n";
217                //exit;
218
219                ob_start();
220
221                if($args['shutdown_func'] || $args['filter_func']) {
222                        // callback/filter given: Register a shutdown function
223                        // which will call user's callback at first, then
224                        // our own write function. and which handles filters
225                        $t = $this; // PHP stupidity
226                        register_shutdown_function(function()  use($args, $t) {
227                                if($args['filter_func']) {
228                                        // also collect the shutdown func prints in the $content
229                                        if($args['shutdown_func'])
230                                                call_user_func($args['shutdown_func']);
231
232                                        $content = ob_get_clean();
233                                        if($t->debug)
234                                                // can print output since OutputBuffering is finished
235                                                print 't29Cache: Applying user filter to output' . PHP_EOL;
236                                        $content = call_user_func($args['filter_func'], $content);
237                                        print $content;
238                                               
239                                        if($args['write_cache'])
240                                                $t->write_cache($content);
241                                        return;
242                                } else if($args['shutdown_func'])
243                                        call_user_func($args['shutdown_func']);
244                                if($args['write_cache'])
245                                        $t->write_cache();
246                        });
247                } elseif($args['write_cache']) {
248                        // Just register our own write function
249                        register_shutdown_function(array($this, 'write_cache'));
250                } else {
251                        // nothing given: Dont call our write function,
252                        // it must therefore be called by hand.
253                }
254        }
255
256        /**
257         * Write Cache file. If the $content string is given, it will
258         * be used as the cache content. Otherwise, a running output buffering
259         * will be assumed (as start_cache fires it) and content will be
260         * extracted with ob_get_flush.
261         * @param $content Content to be used as cache content or OB content
262         * @param $clear_ob_cache Use ob_get_clean instead of flushing it. If given,
263         *                        will return $content instead of printing/keeping it.
264         **/
265        function write_cache($content=null, $clear_ob_cache=false) {
266                if(!$content)
267                        $content = ($clear_ob_cache ? ob_get_clean() : ob_get_flush());
268
269                if($this->skip) {
270                        $this->print_info('skipped cache and cache saving.');
271                        return; // do not save anything.
272                }
273               
274                if(!file_exists($this->cache_file)) {
275                        if(!self::mkdir_recursive(dirname($this->cache_file)))
276                                $this->print_error('Could not create recursive caching directories');
277                }
278               
279                if(@file_put_contents($this->cache_file, $content))
280                        $this->print_info('Wrote output cache successfully');
281                else
282                        $this->print_error('Could not write page output cache to '.$this->cache_file);
283
284                if($clear_ob_cache)
285                        return $content;
286        }
287       
288       
289        private function print_info($string, $even_if_nonverbose=false) {
290                if($this->verbose || $even_if_nonverbose)
291                        echo "<!-- t29Cache: $string -->\n";
292        }
293       
294        private function print_error($string, $even_if_nonverbose=false) {
295                require_once dirname(__FILE__).'/logging.php';
296                $log = t29Log::get();
297               
298                if($this->verbose || $even_if_nonverbose)
299                        $log->WARN("t29Cache: ".$string, t29Log::IMMEDIATELY_PRINT);
300        }
301}
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