PageRenderTime 30ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/Nette/Templating/Template.php

http://github.com/nette/nette
PHP | 456 lines | 203 code | 114 blank | 139 comment | 23 complexity | 2e0aab7c0a18f5ccd6da61bb61e76691 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * This file is part of the Nette Framework (http://nette.org)
  4. *
  5. * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  6. *
  7. * For the full copyright and license information, please view
  8. * the file license.txt that was distributed with this source code.
  9. */
  10. namespace Nette\Templating;
  11. use Nette,
  12. Nette\Caching;
  13. /**
  14. * Template.
  15. *
  16. * @author David Grudl
  17. */
  18. class Template extends Nette\Object implements ITemplate
  19. {
  20. /** @var array of function(Template $sender); Occurs before a template is compiled - implement to customize the filters */
  21. public $onPrepareFilters = array();
  22. /** @var string */
  23. private $source;
  24. /** @var array */
  25. private $params = array();
  26. /** @var array compile-time filters */
  27. private $filters = array();
  28. /** @var array run-time helpers */
  29. private $helpers = array();
  30. /** @var array */
  31. private $helperLoaders = array();
  32. /** @var Nette\Caching\IStorage */
  33. private $cacheStorage;
  34. /**
  35. * Sets template source code.
  36. * @param string
  37. * @return Template provides a fluent interface
  38. */
  39. public function setSource($source)
  40. {
  41. $this->source = $source;
  42. return $this;
  43. }
  44. /**
  45. * Returns template source code.
  46. * @return source
  47. */
  48. public function getSource()
  49. {
  50. return $this->source;
  51. }
  52. /********************* rendering ****************d*g**/
  53. /**
  54. * Renders template to output.
  55. * @return void
  56. */
  57. public function render()
  58. {
  59. $cache = new Caching\Cache($storage = $this->getCacheStorage(), 'Nette.Template');
  60. $cached = $compiled = $cache->load($this->source);
  61. if ($compiled === NULL) {
  62. $compiled = $this->compile();
  63. $cache->save($this->source, $compiled, array(Caching\Cache::CONSTS => 'Nette\Framework::REVISION'));
  64. $cached = $cache->load($this->source);
  65. }
  66. if ($cached !== NULL && $storage instanceof Caching\Storages\PhpFileStorage) {
  67. Nette\Utils\LimitedScope::load($cached['file'], $this->getParameters());
  68. } else {
  69. Nette\Utils\LimitedScope::evaluate($compiled, $this->getParameters());
  70. }
  71. }
  72. /**
  73. * Renders template to file.
  74. * @param string
  75. * @return void
  76. */
  77. public function save($file)
  78. {
  79. if (file_put_contents($file, $this->__toString(TRUE)) === FALSE) {
  80. throw new Nette\IOException("Unable to save file '$file'.");
  81. }
  82. }
  83. /**
  84. * Renders template to string.
  85. * @param bool can throw exceptions? (hidden parameter)
  86. * @return string
  87. */
  88. public function __toString()
  89. {
  90. $args = func_get_args();
  91. ob_start();
  92. try {
  93. $this->render();
  94. return ob_get_clean();
  95. } catch (\Exception $e) {
  96. ob_end_clean();
  97. if ($args && $args[0]) {
  98. throw $e;
  99. } else {
  100. Nette\Diagnostics\Debugger::toStringException($e);
  101. }
  102. }
  103. }
  104. /**
  105. * Applies filters on template content.
  106. * @return string
  107. */
  108. public function compile()
  109. {
  110. if (!$this->filters) {
  111. $this->onPrepareFilters($this);
  112. }
  113. $code = $this->getSource();
  114. foreach ($this->filters as $filter) {
  115. $code = self::extractPhp($code, $blocks);
  116. $code = $filter/*5.2*->invoke*/($code);
  117. $code = strtr($code, $blocks); // put PHP code back
  118. }
  119. return Helpers::optimizePhp($code);
  120. }
  121. /********************* template filters & helpers ****************d*g**/
  122. /**
  123. * Registers callback as template compile-time filter.
  124. * @param callback
  125. * @return Template provides a fluent interface
  126. */
  127. public function registerFilter($callback)
  128. {
  129. $callback = callback($callback);
  130. if (in_array($callback, $this->filters)) {
  131. throw new Nette\InvalidStateException("Filter '$callback' was registered twice.");
  132. }
  133. $this->filters[] = $callback;
  134. return $this;
  135. }
  136. /**
  137. * Returns all registered compile-time filters.
  138. * @return array
  139. */
  140. final public function getFilters()
  141. {
  142. return $this->filters;
  143. }
  144. /**
  145. * Registers callback as template run-time helper.
  146. * @param string
  147. * @param callback
  148. * @return Template provides a fluent interface
  149. */
  150. public function registerHelper($name, $callback)
  151. {
  152. $this->helpers[strtolower($name)] = callback($callback);
  153. return $this;
  154. }
  155. /**
  156. * Registers callback as template run-time helpers loader.
  157. * @param callback
  158. * @return Template provides a fluent interface
  159. */
  160. public function registerHelperLoader($callback)
  161. {
  162. $this->helperLoaders[] = callback($callback);
  163. return $this;
  164. }
  165. /**
  166. * Returns all registered run-time helpers.
  167. * @return array
  168. */
  169. final public function getHelpers()
  170. {
  171. return $this->helpers;
  172. }
  173. /**
  174. * Call a template run-time helper. Do not call directly.
  175. * @param string helper name
  176. * @param array arguments
  177. * @return mixed
  178. */
  179. public function __call($name, $args)
  180. {
  181. $lname = strtolower($name);
  182. if (!isset($this->helpers[$lname])) {
  183. foreach ($this->helperLoaders as $loader) {
  184. $helper = $loader/*5.2*->invoke*/($lname);
  185. if ($helper) {
  186. $this->registerHelper($lname, $helper);
  187. return $this->helpers[$lname]->invokeArgs($args);
  188. }
  189. }
  190. return parent::__call($name, $args);
  191. }
  192. return $this->helpers[$lname]->invokeArgs($args);
  193. }
  194. /**
  195. * Sets translate adapter.
  196. * @param Nette\Localization\ITranslator
  197. * @return Template provides a fluent interface
  198. */
  199. public function setTranslator(Nette\Localization\ITranslator $translator = NULL)
  200. {
  201. $this->registerHelper('translate', $translator === NULL ? NULL : array($translator, 'translate'));
  202. return $this;
  203. }
  204. /********************* template parameters ****************d*g**/
  205. /**
  206. * Adds new template parameter.
  207. * @param string name
  208. * @param mixed value
  209. * @return Template provides a fluent interface
  210. */
  211. public function add($name, $value)
  212. {
  213. if (array_key_exists($name, $this->params)) {
  214. throw new Nette\InvalidStateException("The variable '$name' already exists.");
  215. }
  216. $this->params[$name] = $value;
  217. return $this;
  218. }
  219. /**
  220. * Sets all parameters.
  221. * @param array
  222. * @return Template provides a fluent interface
  223. */
  224. public function setParameters(array $params)
  225. {
  226. $this->params = $params + $this->params;
  227. return $this;
  228. }
  229. /**
  230. * Returns array of all parameters.
  231. * @return array
  232. */
  233. public function getParameters()
  234. {
  235. $this->params['template'] = $this;
  236. return $this->params;
  237. }
  238. /** @deprecated */
  239. function setParams(array $params)
  240. {
  241. trigger_error(__METHOD__ . '() is deprecated; use setParameters() instead.', E_USER_WARNING);
  242. return $this->setParameters($params);
  243. }
  244. /** @deprecated */
  245. function getParams()
  246. {
  247. trigger_error(__METHOD__ . '() is deprecated; use getParameters() instead.', E_USER_WARNING);
  248. return $this->getParameters();
  249. }
  250. /**
  251. * Sets a template parameter. Do not call directly.
  252. * @param string name
  253. * @param mixed value
  254. * @return void
  255. */
  256. public function __set($name, $value)
  257. {
  258. $this->params[$name] = $value;
  259. }
  260. /**
  261. * Returns a template parameter. Do not call directly.
  262. * @param string name
  263. * @return mixed value
  264. */
  265. public function &__get($name)
  266. {
  267. if (!array_key_exists($name, $this->params)) {
  268. trigger_error("The variable '$name' does not exist in template.", E_USER_NOTICE);
  269. }
  270. return $this->params[$name];
  271. }
  272. /**
  273. * Determines whether parameter is defined. Do not call directly.
  274. * @param string name
  275. * @return bool
  276. */
  277. public function __isset($name)
  278. {
  279. return isset($this->params[$name]);
  280. }
  281. /**
  282. * Removes a template parameter. Do not call directly.
  283. * @param string name
  284. * @return void
  285. */
  286. public function __unset($name)
  287. {
  288. unset($this->params[$name]);
  289. }
  290. /********************* caching ****************d*g**/
  291. /**
  292. * Set cache storage.
  293. * @param Nette\Caching\Cache
  294. * @return Template provides a fluent interface
  295. */
  296. public function setCacheStorage(Caching\IStorage $storage)
  297. {
  298. $this->cacheStorage = $storage;
  299. return $this;
  300. }
  301. /**
  302. * @return Nette\Caching\IStorage
  303. */
  304. public function getCacheStorage()
  305. {
  306. if ($this->cacheStorage === NULL) {
  307. return new Caching\Storages\DevNullStorage;
  308. }
  309. return $this->cacheStorage;
  310. }
  311. /********************* tools ****************d*g**/
  312. /**
  313. * Extracts all blocks of PHP code.
  314. * @param string
  315. * @param array
  316. * @return string
  317. */
  318. private static function extractPhp($source, & $blocks)
  319. {
  320. $res = '';
  321. $blocks = array();
  322. $tokens = token_get_all($source);
  323. foreach ($tokens as $n => $token) {
  324. if (is_array($token)) {
  325. if ($token[0] === T_INLINE_HTML || $token[0] === T_CLOSE_TAG) {
  326. $res .= $token[1];
  327. continue;
  328. } elseif ($token[0] === T_OPEN_TAG && $token[1] === '<?' && isset($tokens[$n+1][1]) && $tokens[$n+1][1] === 'xml') {
  329. $php = & $res;
  330. $token[1] = '<<?php ?>?';
  331. } elseif ($token[0] === T_OPEN_TAG || $token[0] === T_OPEN_TAG_WITH_ECHO) {
  332. $res .= $id = "<?php \x01@php:p" . count($blocks) . "@\x02";
  333. $php = & $blocks[$id];
  334. }
  335. $php .= $token[1];
  336. } else {
  337. $php .= $token;
  338. }
  339. }
  340. return $res;
  341. }
  342. }