1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11:
12:
13:
14:
15: 16: 17: 18: 19:
20: abstract class Template extends Object implements ITemplate
21: {
22:
23: public $warnOnUndefined = TRUE;
24:
25:
26: public $onPrepareFilters = array();
27:
28:
29: private $params = array();
30:
31:
32: private $filters = array();
33:
34:
35: private $helpers = array();
36:
37:
38: private $helperLoaders = array();
39:
40:
41:
42: 43: 44: 45: 46:
47: public function registerFilter($callback)
48: {
49: $callback = callback($callback);
50: if (in_array($callback, $this->filters)) {
51: throw new InvalidStateException("Filter '$callback' was registered twice.");
52: }
53: $this->filters[] = $callback;
54: }
55:
56:
57:
58: 59: 60: 61:
62: final public function getFilters()
63: {
64: return $this->filters;
65: }
66:
67:
68:
69:
70:
71:
72:
73: 74: 75: 76: 77:
78: public function render()
79: {
80: }
81:
82:
83:
84: 85: 86: 87: 88:
89: public function save($file)
90: {
91: if (file_put_contents($file, $this->__toString(TRUE)) === FALSE) {
92: throw new IOException("Unable to save file '$file'.");
93: }
94: }
95:
96:
97:
98: 99: 100: 101: 102:
103: public function __toString()
104: {
105: ob_start();
106: try {
107: $this->render();
108: return ob_get_clean();
109:
110: } catch (Exception $e) {
111: ob_end_clean();
112: if (func_num_args() && func_get_arg(0)) {
113: throw $e;
114: } else {
115: Debug::toStringException($e);
116: }
117: }
118: }
119:
120:
121:
122: 123: 124: 125: 126: 127:
128: protected function compile($content, $label = NULL)
129: {
130: if (!$this->filters) {
131: $this->onPrepareFilters($this);
132: }
133:
134: try {
135: foreach ($this->filters as $filter) {
136: $content = self::extractPhp($content, $blocks);
137: $content = $filter->invoke($content);
138: $content = strtr($content, $blocks); 139: }
140: } catch (Exception $e) {
141: throw new InvalidStateException("Filter $filter: " . $e->getMessage() . ($label ? " (in $label)" : ''), 0, $e);
142: }
143:
144: if ($label) {
145: $content = "<?php\n// $label\n//\n?>$content";
146: }
147:
148: return self::optimizePhp($content);
149: }
150:
151:
152:
153:
154:
155:
156:
157: 158: 159: 160: 161: 162:
163: public function registerHelper($name, $callback)
164: {
165: $this->helpers[strtolower($name)] = callback($callback);
166: }
167:
168:
169:
170: 171: 172: 173: 174:
175: public function registerHelperLoader($callback)
176: {
177: $this->helperLoaders[] = callback($callback);
178: }
179:
180:
181:
182: 183: 184: 185:
186: final public function getHelpers()
187: {
188: return $this->helpers;
189: }
190:
191:
192:
193: 194: 195: 196: 197: 198:
199: public function __call($name, $args)
200: {
201: $lname = strtolower($name);
202: if (!isset($this->helpers[$lname])) {
203: foreach ($this->helperLoaders as $loader) {
204: $helper = $loader->invoke($lname);
205: if ($helper) {
206: $this->registerHelper($lname, $helper);
207: return $this->helpers[$lname]->invokeArgs($args);
208: }
209: }
210: return parent::__call($name, $args);
211: }
212:
213: return $this->helpers[$lname]->invokeArgs($args);
214: }
215:
216:
217:
218: 219: 220: 221: 222:
223: public function setTranslator(ITranslator $translator = NULL)
224: {
225: $this->registerHelper('translate', $translator === NULL ? NULL : array($translator, 'translate'));
226: return $this;
227: }
228:
229:
230:
231:
232:
233:
234:
235: 236: 237: 238: 239: 240:
241: public function add($name, $value)
242: {
243: if (array_key_exists($name, $this->params)) {
244: throw new InvalidStateException("The variable '$name' already exists.");
245: }
246:
247: $this->params[$name] = $value;
248: }
249:
250:
251:
252: 253: 254: 255: 256:
257: public function setParams(array $params)
258: {
259: $this->params = $params;
260: return $this;
261: }
262:
263:
264:
265: 266: 267: 268:
269: public function getParams()
270: {
271: return $this->params;
272: }
273:
274:
275:
276: 277: 278: 279: 280: 281:
282: public function __set($name, $value)
283: {
284: $this->params[$name] = $value;
285: }
286:
287:
288:
289: 290: 291: 292: 293:
294: public function &__get($name)
295: {
296: if ($this->warnOnUndefined && !array_key_exists($name, $this->params)) {
297: trigger_error("The variable '$name' does not exist in template.", E_USER_NOTICE);
298: }
299:
300: return $this->params[$name];
301: }
302:
303:
304:
305: 306: 307: 308: 309:
310: public function __isset($name)
311: {
312: return isset($this->params[$name]);
313: }
314:
315:
316:
317: 318: 319: 320: 321:
322: public function __unset($name)
323: {
324: unset($this->params[$name]);
325: }
326:
327:
328:
329:
330:
331:
332:
333: 334: 335: 336: 337: 338:
339: private static function extractPhp($source, & $blocks)
340: {
341: $res = '';
342: $blocks = array();
343: $tokens = token_get_all($source);
344: foreach ($tokens as $n => $token) {
345: if (is_array($token)) {
346: if ($token[0] === T_INLINE_HTML) {
347: $res .= $token[1];
348: continue;
349:
350: } elseif ($token[0] === T_OPEN_TAG && $token[1] === '<?' && isset($tokens[$n+1][1]) && $tokens[$n+1][1] === 'xml') {
351: $php = & $res;
352: $token[1] = '<<?php ?>?';
353:
354: } elseif ($token[0] === T_OPEN_TAG || $token[0] === T_OPEN_TAG_WITH_ECHO) {
355: $res .= $id = "\x01@php:p" . count($blocks) . "@\x02";
356: $php = & $blocks[$id];
357: }
358: $php .= $token[1];
359:
360: } else {
361: $php .= $token;
362: }
363: }
364: return $res;
365: }
366:
367:
368:
369: 370: 371: 372: 373:
374: public static function optimizePhp($source)
375: {
376: $res = $php = '';
377: $lastChar = ';';
378: $tokens = new ArrayIterator(token_get_all($source));
379: foreach ($tokens as $key => $token) {
380: if (is_array($token)) {
381: if ($token[0] === T_INLINE_HTML) {
382: $lastChar = '';
383: $res .= $token[1];
384:
385: } elseif ($token[0] === T_CLOSE_TAG) {
386: $next = isset($tokens[$key + 1]) ? $tokens[$key + 1] : NULL;
387: if (substr($res, -1) !== '<' && preg_match('#^<\?php\s*$#', $php)) {
388: $php = ''; 389:
390: } elseif (is_array($next) && $next[0] === T_OPEN_TAG) { 391: if ($lastChar !== ';' && $lastChar !== '{' && $lastChar !== '}' && $lastChar !== ':' && $lastChar !== '/' ) $php .= $lastChar = ';';
392: if (substr($next[1], -1) === "\n") $php .= "\n";
393: $tokens->next();
394:
395: } elseif ($next) {
396: $res .= preg_replace('#;?(\s)*$#', '$1', $php) . $token[1]; 397: $php = '';
398:
399: } else { 400: if ($lastChar !== '}' && $lastChar !== ';') $php .= ';';
401: }
402:
403: } elseif ($token[0] === T_ELSE || $token[0] === T_ELSEIF) {
404: if ($tokens[$key + 1] === ':' && $lastChar === '}') $php .= ';'; 405: $lastChar = '';
406: $php .= $token[1];
407:
408: } else {
409: if (!in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, T_OPEN_TAG))) $lastChar = '';
410: $php .= $token[1];
411: }
412: } else {
413: $php .= $lastChar = $token;
414: }
415: }
416: return $res . $php;
417: }
418:
419: }
420: