1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Latte;
9:
10:
11: 12: 13:
14: class Engine
15: {
16: use Strict;
17:
18: const VERSION = '2.4.2';
19:
20:
21: const CONTENT_HTML = 'html',
22: CONTENT_XHTML = 'xhtml',
23: CONTENT_XML = 'xml',
24: CONTENT_JS = 'js',
25: CONTENT_CSS = 'css',
26: CONTENT_ICAL = 'ical',
27: CONTENT_TEXT = 'text';
28:
29:
30: public $onCompile = [];
31:
32:
33: private $parser;
34:
35:
36: private $compiler;
37:
38:
39: private $loader;
40:
41:
42: private $filters;
43:
44:
45: private $providers = [];
46:
47:
48: private $contentType = self::CONTENT_HTML;
49:
50:
51: private $tempDirectory;
52:
53:
54: private $autoRefresh = TRUE;
55:
56:
57:
58: public function __construct()
59: {
60: $this->filters = new Runtime\FilterExecutor;
61: }
62:
63:
64: 65: 66: 67:
68: public function render($name, array $params = [], $block = NULL)
69: {
70: $this->createTemplate($name, $params + ['_renderblock' => $block])
71: ->render();
72: }
73:
74:
75: 76: 77: 78:
79: public function renderToString($name, array $params = [], $block = NULL)
80: {
81: $template = $this->createTemplate($name, $params + ['_renderblock' => $block]);
82: return $template->capture([$template, 'render']);
83: }
84:
85:
86: 87: 88: 89:
90: public function createTemplate($name, array $params = [])
91: {
92: $class = $this->getTemplateClass($name);
93: if (!class_exists($class, FALSE)) {
94: $this->loadTemplate($name);
95: }
96: return new $class($this, $params, $this->filters, $this->providers, $name);
97: }
98:
99:
100: 101: 102: 103:
104: public function compile($name)
105: {
106: foreach ($this->onCompile ?: [] as $cb) {
107: call_user_func(Helpers::checkCallback($cb), $this);
108: }
109: $this->onCompile = [];
110:
111: $source = $this->getLoader()->getContent($name);
112:
113: try {
114: $tokens = $this->getParser()->setContentType($this->contentType)
115: ->parse($source);
116:
117: $code = $this->getCompiler()->setContentType($this->contentType)
118: ->compile($tokens, $this->getTemplateClass($name));
119:
120: } catch (\Exception $e) {
121: if (!$e instanceof CompileException) {
122: $e = new CompileException("Thrown exception '{$e->getMessage()}'", NULL, $e);
123: }
124: $line = isset($tokens) ? $this->getCompiler()->getLine() : $this->getParser()->getLine();
125: throw $e->setSource($source, $line, $name);
126: }
127:
128: if (!preg_match('#\n|\?#', $name)) {
129: $code = "<?php\n// source: $name\n?>" . $code;
130: }
131: $code = PhpHelpers::reformatCode($code);
132: return $code;
133: }
134:
135:
136: 137: 138: 139: 140: 141:
142: public function warmupCache($name)
143: {
144: if (!$this->tempDirectory) {
145: throw new \LogicException('Path to temporary directory is not set.');
146: }
147:
148: $class = $this->getTemplateClass($name);
149: if (!class_exists($class, FALSE)) {
150: $this->loadTemplate($name);
151: }
152: }
153:
154:
155: 156: 157:
158: private function loadTemplate($name)
159: {
160: if (!$this->tempDirectory) {
161: $code = $this->compile($name);
162: if (@eval('?>' . $code) === FALSE) {
163: throw (new CompileException('Error in template: ' . error_get_last()['message']))
164: ->setSource($code, error_get_last()['line'], "$name (compiled)");
165: }
166: return;
167: }
168:
169: $file = $this->getCacheFile($name);
170:
171: if (!$this->isExpired($file, $name) && (@include $file) !== FALSE) {
172: return;
173: }
174:
175: if (!is_dir($this->tempDirectory)) {
176: @mkdir($this->tempDirectory);
177: }
178:
179: $handle = fopen("$file.lock", 'c+');
180: if (!$handle || !flock($handle, LOCK_EX)) {
181: throw new \RuntimeException("Unable to acquire exclusive lock '$file.lock'.");
182: }
183:
184: if (!is_file($file) || $this->isExpired($file, $name)) {
185: $code = $this->compile($name);
186: if (file_put_contents("$file.tmp", $code) !== strlen($code) || !rename("$file.tmp", $file)) {
187: @unlink("$file.tmp");
188: throw new \RuntimeException("Unable to create '$file'.");
189: } elseif (function_exists('opcache_invalidate')) {
190: @opcache_invalidate($file, TRUE);
191: }
192: }
193:
194: if ((include $file) === FALSE) {
195: throw new \RuntimeException("Unable to load '$file'.");
196: }
197:
198: flock($handle, LOCK_UN);
199: fclose($handle);
200: @unlink("$file.lock");
201: }
202:
203:
204: 205: 206: 207: 208:
209: private function isExpired($file, $name)
210: {
211: return $this->autoRefresh && $this->getLoader()->isExpired($name, (int) @filemtime($file));
212: }
213:
214:
215: 216: 217:
218: public function getCacheFile($name)
219: {
220: $hash = substr($this->getTemplateClass($name), 8);
221: $base = preg_match('#([/\\\\][\w@.-]{3,35}){1,3}\z#', $name, $m)
222: ? preg_replace('#[^\w@.-]+#', '-', substr($m[0], 1)) . '--'
223: : '';
224: return "$this->tempDirectory/$base$hash.php";
225: }
226:
227:
228: 229: 230:
231: public function getTemplateClass($name)
232: {
233: $key = $this->getLoader()->getUniqueId($name) . "\00" . self::VERSION;
234: return 'Template' . substr(md5($key), 0, 10);
235: }
236:
237:
238: 239: 240: 241: 242: 243:
244: public function addFilter($name, $callback)
245: {
246: $this->filters->add($name, $callback);
247: return $this;
248: }
249:
250:
251: 252: 253: 254:
255: public function getFilters()
256: {
257: return $this->filters->getAll();
258: }
259:
260:
261: 262: 263: 264: 265: 266:
267: public function invokeFilter($name, array $args)
268: {
269: return call_user_func_array($this->filters->$name, $args);
270: }
271:
272:
273: 274: 275: 276:
277: public function addMacro($name, IMacro $macro)
278: {
279: $this->getCompiler()->addMacro($name, $macro);
280: return $this;
281: }
282:
283:
284: 285: 286: 287:
288: public function addProvider($name, $value)
289: {
290: $this->providers[$name] = $value;
291: return $this;
292: }
293:
294:
295: 296: 297: 298:
299: public function getProviders()
300: {
301: return $this->providers;
302: }
303:
304:
305: 306: 307:
308: public function setContentType($type)
309: {
310: $this->contentType = $type;
311: return $this;
312: }
313:
314:
315: 316: 317: 318:
319: public function setTempDirectory($path)
320: {
321: $this->tempDirectory = $path;
322: return $this;
323: }
324:
325:
326: 327: 328: 329:
330: public function setAutoRefresh($on = TRUE)
331: {
332: $this->autoRefresh = (bool) $on;
333: return $this;
334: }
335:
336:
337: 338: 339:
340: public function getParser()
341: {
342: if (!$this->parser) {
343: $this->parser = new Parser;
344: }
345: return $this->parser;
346: }
347:
348:
349: 350: 351:
352: public function getCompiler()
353: {
354: if (!$this->compiler) {
355: $this->compiler = new Compiler;
356: Macros\CoreMacros::install($this->compiler);
357: Macros\BlockMacros::install($this->compiler);
358: }
359: return $this->compiler;
360: }
361:
362:
363: 364: 365:
366: public function setLoader(ILoader $loader)
367: {
368: $this->loader = $loader;
369: return $this;
370: }
371:
372:
373: 374: 375:
376: public function getLoader()
377: {
378: if (!$this->loader) {
379: $this->loader = new Loaders\FileLoader;
380: }
381: return $this->loader;
382: }
383:
384: }
385: