- Reverted echo concatenation instead of multiple parameters (It was correct before...
[haanga.git] / lib / Haanga.php
blobb20c6906f3d3c8d5fa5ba5f7d5c5509f6d76bc16
1 <?php
2 /*
3 +---------------------------------------------------------------------------------+
4 | Copyright (c) 2010 César Rodas and Menéame Comunicacions S.L. |
5 +---------------------------------------------------------------------------------+
6 | Redistribution and use in source and binary forms, with or without |
7 | modification, are permitted provided that the following conditions are met: |
8 | 1. Redistributions of source code must retain the above copyright |
9 | notice, this list of conditions and the following disclaimer. |
10 | |
11 | 2. Redistributions in binary form must reproduce the above copyright |
12 | notice, this list of conditions and the following disclaimer in the |
13 | documentation and/or other materials provided with the distribution. |
14 | |
15 | 3. All advertising materials mentioning features or use of this software |
16 | must display the following acknowledgement: |
17 | This product includes software developed by César D. Rodas. |
18 | |
19 | 4. Neither the name of the César D. Rodas nor the |
20 | names of its contributors may be used to endorse or promote products |
21 | derived from this software without specific prior written permission. |
22 | |
23 | THIS SOFTWARE IS PROVIDED BY CÉSAR D. RODAS ''AS IS'' AND ANY |
24 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
25 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
26 | DISCLAIMED. IN NO EVENT SHALL CÉSAR D. RODAS BE LIABLE FOR ANY |
27 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
28 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
29 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
30 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
31 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
32 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE |
33 +---------------------------------------------------------------------------------+
34 | Authors: César Rodas <crodas@php.net> |
35 +---------------------------------------------------------------------------------+
38 define('HAANGA_VERSION', '1.0.3');
41 /**
42 * Haanga Runtime class
44 * Simple class to call templates efficiently. This class aims
45 * to reduce the compilation of a template as less a possible. Also
46 * it will not load in memory the compiler, except when there is not
47 * cache (compiled template) or it is out-dated.
50 class Haanga
52 protected static $cache_dir;
53 protected static $templates_dir='.';
54 protected static $debug;
55 protected static $bootstrap = NULL;
56 protected static $check_ttl;
57 protected static $check_get;
58 protected static $check_set;
59 protected static $use_autoload = TRUE;
60 protected static $hash_filename = TRUE;
61 protected static $compiler = array();
63 public static $has_compiled;
65 private function __construct()
67 /* The class can't be instanced */
70 final public static function AutoLoad($class)
72 static $loaded = array();
73 static $path;
75 if (!isset($loaded[$class]) && substr($class, 0, 6) === 'Haanga' && !class_exists($class, false)) {
76 if ($path === NULL) {
77 $path = dirname(__FILE__);
79 $file = $path.DIRECTORY_SEPARATOR.str_replace('_', DIRECTORY_SEPARATOR, $class).'.php';
80 if (is_file($file)) {
81 require $file;
83 $loaded[$class] = TRUE;
84 return;
87 return FALSE;
90 public static function getTemplateDir()
92 return self::$templates_dir;
95 // configure(Array $opts) {{{
96 /**
97 * Configuration to load Haanga
99 * Options:
101 * - (string) cache_dir
102 * - (string) tempalte_dir
103 * - (callback) on_compile
104 * - (boolean) debug
105 * - (int) check_ttl
106 * - (callback) check_get
107 * - (callback) check_set
108 * - (boolean) autoload
109 * - (boolean) use_hash_filename
111 * @return void
113 final public static function configure(Array $opts)
115 foreach ($opts as $option => $value) {
116 switch (strtolower($option)) {
117 case 'cache_dir':
118 self::$cache_dir = $value;
119 break;
120 case 'template_dir':
121 self::$templates_dir = $value;
122 break;
123 case 'bootstrap':
124 if (is_callable($value)) {
125 self::$bootstrap = $value;
127 break;
128 case 'debug':
129 self::enableDebug((bool)$value);
130 break;
131 case 'check_ttl':
132 self::$check_ttl = (int)$value;
133 break;
134 case 'check_get':
135 if (is_callable($value)) {
136 self::$check_get = $value;
138 break;
139 case 'check_set':
140 if (is_callable($value)) {
141 self::$check_set = $value;
143 break;
144 case 'autoload':
145 self::$use_autoload = (bool)$value;
146 break;
147 case 'use_hash_filename':
148 self::$hash_filename = (bool)$value;
149 break;
150 case 'compiler':
151 if (is_array($value)) {
152 self::$compiler = $value;
154 break;
155 default:
156 continue;
160 // }}}
162 // checkCacheDir(string $dir) {{{
164 * Check the directory where the compiled templates
165 * are stored.
167 * @param string $dir
169 * @return void
171 public static function checkCacheDir()
173 $dir = self::$cache_dir;
174 if (!is_dir($dir)) {
175 $old = umask(0);
176 if (!mkdir($dir, 0777, TRUE)) {
177 throw new Haanga_Exception("{$dir} is not a valid directory");
179 umask($old);
181 if (!is_writable($dir)) {
182 throw new Haanga_Exception("{$dir} can't be written");
185 // }}}
187 // enableDebug($bool) {{{
188 public static function enableDebug($bool)
190 self::$debug = $bool;
192 // }}}
194 // safe_load(string $file, array $vars, bool $return, array $blocks) {{{
195 public static function Safe_Load($file, $vars = array(), $return=FALSE, $blocks=array())
197 try {
199 $tpl = self::$templates_dir.'/'.$file;
200 if (file_exists($tpl)) {
201 /* call load if the tpl file exists */
202 return self::Load($file, $vars, $return, $blocks);
204 } Catch (Exception $e) {
206 /* some error but we don't care at all */
207 return "";
209 // }}}
211 // load(string $file, array $vars, bool $return, array $blocks) {{{
213 * Load
215 * Load template. If the template is already compiled, just the compiled
216 * PHP file will be included an used. If the template is new, or it
217 * had changed, the Haanga compiler is loaded in memory, and the template
218 * is compiled.
221 * @param string $file
222 * @param array $vars
223 * @param bool $return
224 * @param array $blocks
226 * @return string|NULL
228 public static function Load($file, $vars = array(), $return=FALSE, $blocks=array())
230 static $compiler;
231 if (empty(self::$cache_dir)) {
232 throw new Haanga_Exception("Cache dir or template dir is missing");
235 self::$has_compiled = FALSE;
237 $tpl = self::$templates_dir.'/'.$file;
238 $fnc = sha1($tpl);
239 $callback = "haanga_".$fnc;
241 if (is_callable($callback)) {
242 return $callback($vars, $return, $blocks);
245 $php = self::$hash_filename ? $fnc : $file;
246 $php = self::$cache_dir.'/'.$php.'.php';
248 $check = TRUE;
250 if (self::$check_ttl && self::$check_get && self::$check_set) {
251 /* */
252 if (call_user_func(self::$check_get, $callback)) {
253 /* disable checking for the next $check_ttl seconds */
254 $check = FALSE;
255 } else {
256 $result = call_user_func(self::$check_set, $callback, TRUE, self::$check_ttl);
260 if (!is_file($php) || ($check && filemtime($tpl) > filemtime($php))) {
262 if (!is_file($tpl)) {
263 /* There is no template nor compiled file */
264 throw new Exception("View {$file} doesn't exists");
267 if (!is_dir(dirname($php))) {
268 $old = umask(0);
269 mkdir(dirname($php), 0777, TRUE);
270 umask($old);
273 $fp = fopen($php, "a+");
274 /* try to block PHP file */
275 if (!flock($fp, LOCK_EX | LOCK_NB)) {
276 /* couldn't block, another process is already compiling */
277 fclose($fp);
278 if (is_file($php)) {
280 ** if there is an old version of the cache
281 ** load it
283 require $php;
284 return $callback($vars, $return, $blocks);
287 ** no luck, probably the template is new
288 ** the compilation will be done, but we won't
289 ** save
291 unset($fp);
294 /* recompile */
295 if (!$compiler) {
296 self::checkCacheDir();
298 /* Load needed files (to avoid autoload as much as possible) */
299 $dir = dirname(__FILE__);
300 require_once "{$dir}/Haanga/AST.php";
301 require_once "{$dir}/Haanga/Compiler.php";
302 require_once "{$dir}/Haanga/Compiler/Runtime.php";
303 require_once "{$dir}/Haanga/Compiler/Parser.php";
304 require_once "{$dir}/Haanga/Compiler/Lexer.php";
305 require_once "{$dir}/Haanga/Generator/PHP.php";
306 require_once "{$dir}/Haanga/Extension.php";
307 require_once "{$dir}/Haanga/Extension/Filter.php";
308 require_once "{$dir}/Haanga/Extension/Tag.php";
310 /* load compiler (done just once) */
311 if (self::$use_autoload) {
312 spl_autoload_register(array(__CLASS__, 'AutoLoad'));
315 $compiler = new Haanga_Compiler_Runtime;
317 if (self::$bootstrap) {
318 /* call bootstrap hook, just the first time */
319 call_user_func(self::$bootstrap);
322 if (count(self::$compiler) != 0) {
323 foreach (self::$compiler as $opt => $value) {
324 Haanga_Compiler::setOption($opt, $value);
329 $compiler->reset();
331 if (self::$debug) {
332 $compiler->setDebug($php.".dump");
335 $code = $compiler->compile_file($tpl, FALSE, $vars);
337 if (isset($fp)) {
338 ftruncate($fp, 0); // truncate file
339 fwrite($fp, "<?php".$code);
340 flock($fp, LOCK_UN); // release the lock
341 fclose($fp);
342 } else {
343 /* local eval */
344 eval($code);
346 self::$has_compiled = TRUE;
349 if (!is_callable($callback)) {
350 require $php;
353 if (!isset($HAANGA_VERSION) || $HAANGA_VERSION != HAANGA_VERSION) {
354 touch($php, 300, 300);
355 chmod($php, 0777);
358 return $callback($vars, $return, $blocks);
360 // }}}
365 * Local variables:
366 * tab-width: 4
367 * c-basic-offset: 4
368 * End:
369 * vim600: sw=4 ts=4 fdm=marker
370 * vim<600: sw=4 ts=4