PageRenderTime 46ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Symfony/Component/Templating/PhpEngine.php

https://github.com/lalol/symfony
PHP | 504 lines | 227 code | 59 blank | 218 comment | 15 complexity | 41a3de5b854147c4b5e6db2713086f7d MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Templating;
  11. use Symfony\Component\Templating\Storage\Storage;
  12. use Symfony\Component\Templating\Storage\FileStorage;
  13. use Symfony\Component\Templating\Storage\StringStorage;
  14. use Symfony\Component\Templating\Helper\HelperInterface;
  15. use Symfony\Component\Templating\Loader\LoaderInterface;
  16. /**
  17. * PhpEngine is an engine able to render PHP templates.
  18. *
  19. * @author Fabien Potencier <fabien@symfony.com>
  20. */
  21. class PhpEngine implements EngineInterface, \ArrayAccess
  22. {
  23. protected $loader;
  24. protected $current;
  25. protected $helpers;
  26. protected $parents;
  27. protected $stack;
  28. protected $charset;
  29. protected $cache;
  30. protected $escapers;
  31. protected $globals;
  32. protected $parser;
  33. /**
  34. * Constructor.
  35. *
  36. * @param TemplateNameParserInterface $parser A TemplateNameParserInterface instance
  37. * @param LoaderInterface $loader A loader instance
  38. * @param array $helpers An array of helper instances
  39. */
  40. public function __construct(TemplateNameParserInterface $parser, LoaderInterface $loader, array $helpers = array())
  41. {
  42. $this->parser = $parser;
  43. $this->loader = $loader;
  44. $this->parents = array();
  45. $this->stack = array();
  46. $this->charset = 'UTF-8';
  47. $this->cache = array();
  48. $this->globals = array();
  49. $this->setHelpers($helpers);
  50. $this->initializeEscapers();
  51. foreach ($this->escapers as $context => $escaper) {
  52. $this->setEscaper($context, $escaper);
  53. }
  54. }
  55. /**
  56. * Renders a template.
  57. *
  58. * @param mixed $name A template name or a TemplateReferenceInterface instance
  59. * @param array $parameters An array of parameters to pass to the template
  60. *
  61. * @return string The evaluated template as a string
  62. *
  63. * @throws \InvalidArgumentException if the template does not exist
  64. * @throws \RuntimeException if the template cannot be rendered
  65. */
  66. public function render($name, array $parameters = array())
  67. {
  68. $storage = $this->load($name);
  69. $key = md5(serialize($storage));
  70. $this->current = $key;
  71. $this->parents[$key] = null;
  72. // attach the global variables
  73. $parameters = array_replace($this->getGlobals(), $parameters);
  74. // render
  75. if (false === $content = $this->evaluate($storage, $parameters)) {
  76. throw new \RuntimeException(sprintf('The template "%s" cannot be rendered.', $this->parser->parse($name)));
  77. }
  78. // decorator
  79. if ($this->parents[$key]) {
  80. $slots = $this->get('slots');
  81. $this->stack[] = $slots->get('_content');
  82. $slots->set('_content', $content);
  83. $content = $this->render($this->parents[$key], $parameters);
  84. $slots->set('_content', array_pop($this->stack));
  85. }
  86. return $content;
  87. }
  88. /**
  89. * Returns true if the template exists.
  90. *
  91. * @param mixed $name A template name or a TemplateReferenceInterface instance
  92. *
  93. * @return Boolean true if the template exists, false otherwise
  94. */
  95. public function exists($name)
  96. {
  97. try {
  98. $this->load($name);
  99. } catch (\InvalidArgumentException $e) {
  100. return false;
  101. }
  102. return true;
  103. }
  104. /**
  105. * Returns true if this class is able to render the given template.
  106. *
  107. * @param mixed $name A template name or a TemplateReferenceInterface instance
  108. *
  109. * @return Boolean true if this class supports the given resource, false otherwise
  110. */
  111. public function supports($name)
  112. {
  113. $template = $this->parser->parse($name);
  114. return 'php' === $template->get('engine');
  115. }
  116. /**
  117. * Evaluates a template.
  118. *
  119. * @param Storage $template The template to render
  120. * @param array $parameters An array of parameters to pass to the template
  121. *
  122. * @return string|false The evaluated template, or false if the engine is unable to render the template
  123. */
  124. protected function evaluate(Storage $template, array $parameters = array())
  125. {
  126. $__template__ = $template;
  127. if ($__template__ instanceof FileStorage) {
  128. extract($parameters);
  129. $view = $this;
  130. ob_start();
  131. require $__template__;
  132. return ob_get_clean();
  133. } elseif ($__template__ instanceof StringStorage) {
  134. extract($parameters);
  135. $view = $this;
  136. ob_start();
  137. eval('; ?>'.$__template__.'<?php ;');
  138. return ob_get_clean();
  139. }
  140. return false;
  141. }
  142. /**
  143. * Gets a helper value.
  144. *
  145. * @param string $name The helper name
  146. *
  147. * @return mixed The helper value
  148. *
  149. * @throws \InvalidArgumentException if the helper is not defined
  150. */
  151. public function offsetGet($name)
  152. {
  153. return $this->get($name);
  154. }
  155. /**
  156. * Returns true if the helper is defined.
  157. *
  158. * @param string $name The helper name
  159. *
  160. * @return Boolean true if the helper is defined, false otherwise
  161. */
  162. public function offsetExists($name)
  163. {
  164. return isset($this->helpers[$name]);
  165. }
  166. /**
  167. * Sets a helper.
  168. *
  169. * @param HelperInterface $name The helper instance
  170. * @param string $value An alias
  171. */
  172. public function offsetSet($name, $value)
  173. {
  174. $this->set($name, $value);
  175. }
  176. /**
  177. * Removes a helper.
  178. *
  179. * @param string $name The helper name
  180. */
  181. public function offsetUnset($name)
  182. {
  183. throw new \LogicException(sprintf('You can\'t unset a helper (%s).', $name));
  184. }
  185. /**
  186. * @param Helper[] $helpers An array of helper
  187. */
  188. public function addHelpers(array $helpers)
  189. {
  190. foreach ($helpers as $alias => $helper) {
  191. $this->set($helper, is_int($alias) ? null : $alias);
  192. }
  193. }
  194. /**
  195. * Sets the helpers.
  196. *
  197. * @params Helper[] $helpers An array of helper
  198. */
  199. public function setHelpers(array $helpers)
  200. {
  201. $this->helpers = array();
  202. $this->addHelpers($helpers);
  203. }
  204. /**
  205. * Sets a helper.
  206. *
  207. * @param HelperInterface $helper The helper instance
  208. * @param string $alias An alias
  209. */
  210. public function set(HelperInterface $helper, $alias = null)
  211. {
  212. $this->helpers[$helper->getName()] = $helper;
  213. if (null !== $alias) {
  214. $this->helpers[$alias] = $helper;
  215. }
  216. $helper->setCharset($this->charset);
  217. }
  218. /**
  219. * Returns true if the helper if defined.
  220. *
  221. * @param string $name The helper name
  222. *
  223. * @return Boolean true if the helper is defined, false otherwise
  224. */
  225. public function has($name)
  226. {
  227. return isset($this->helpers[$name]);
  228. }
  229. /**
  230. * Gets a helper value.
  231. *
  232. * @param string $name The helper name
  233. *
  234. * @return HelperInterface The helper instance
  235. *
  236. * @throws \InvalidArgumentException if the helper is not defined
  237. */
  238. public function get($name)
  239. {
  240. if (!isset($this->helpers[$name])) {
  241. throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
  242. }
  243. return $this->helpers[$name];
  244. }
  245. /**
  246. * Decorates the current template with another one.
  247. *
  248. * @param string $template The decorator logical name
  249. */
  250. public function extend($template)
  251. {
  252. $this->parents[$this->current] = $template;
  253. }
  254. /**
  255. * Escapes a string by using the current charset.
  256. *
  257. * @param mixed $value A variable to escape
  258. * @param string $context The context name
  259. *
  260. * @return string The escaped value
  261. */
  262. public function escape($value, $context = 'html')
  263. {
  264. return call_user_func($this->getEscaper($context), $value);
  265. }
  266. /**
  267. * Sets the charset to use.
  268. *
  269. * @param string $charset The charset
  270. */
  271. public function setCharset($charset)
  272. {
  273. $this->charset = $charset;
  274. }
  275. /**
  276. * Gets the current charset.
  277. *
  278. * @return string The current charset
  279. */
  280. public function getCharset()
  281. {
  282. return $this->charset;
  283. }
  284. /**
  285. * Adds an escaper for the given context.
  286. *
  287. * @param string $context The escaper context (html, js, ...)
  288. * @param mixed $escaper A PHP callable
  289. */
  290. public function setEscaper($context, $escaper)
  291. {
  292. $this->escapers[$context] = $escaper;
  293. }
  294. /**
  295. * Gets an escaper for a given context.
  296. *
  297. * @param string $context The context name
  298. *
  299. * @return mixed $escaper A PHP callable
  300. */
  301. public function getEscaper($context)
  302. {
  303. if (!isset($this->escapers[$context])) {
  304. throw new \InvalidArgumentException(sprintf('No registered escaper for context "%s".', $context));
  305. }
  306. return $this->escapers[$context];
  307. }
  308. /**
  309. * @param string $name
  310. * @param mixed $value
  311. */
  312. public function addGlobal($name, $value)
  313. {
  314. $this->globals[$name] = $value;
  315. }
  316. /**
  317. * Returns the assigned globals.
  318. *
  319. * @return array
  320. */
  321. public function getGlobals()
  322. {
  323. return $this->globals;
  324. }
  325. /**
  326. * Initializes the built-in escapers.
  327. *
  328. * Each function specifies a way for applying a transformation to a string
  329. * passed to it. The purpose is for the string to be "escaped" so it is
  330. * suitable for the format it is being displayed in.
  331. *
  332. * For example, the string: "It's required that you enter a username & password.\n"
  333. * If this were to be displayed as HTML it would be sensible to turn the
  334. * ampersand into '&amp;' and the apostrophe into '&aps;'. However if it were
  335. * going to be used as a string in JavaScript to be displayed in an alert box
  336. * it would be right to leave the string as-is, but c-escape the apostrophe and
  337. * the new line.
  338. *
  339. * For each function there is a define to avoid problems with strings being
  340. * incorrectly specified.
  341. */
  342. protected function initializeEscapers()
  343. {
  344. $that = $this;
  345. $this->escapers = array(
  346. 'html' =>
  347. /**
  348. * Runs the PHP function htmlspecialchars on the value passed.
  349. *
  350. * @param string $value the value to escape
  351. *
  352. * @return string the escaped value
  353. */
  354. function ($value) use ($that)
  355. {
  356. // Numbers and Boolean values get turned into strings which can cause problems
  357. // with type comparisons (e.g. === or is_int() etc).
  358. return is_string($value) ? htmlspecialchars($value, ENT_QUOTES, $that->getCharset(), false) : $value;
  359. },
  360. 'js' =>
  361. /**
  362. * A function that escape all non-alphanumeric characters
  363. * into their \xHH or \uHHHH representations
  364. *
  365. * @param string $value the value to escape
  366. * @return string the escaped value
  367. */
  368. function ($value) use ($that)
  369. {
  370. if ('UTF-8' != $that->getCharset()) {
  371. $value = $that->convertEncoding($value, 'UTF-8', $that->getCharset());
  372. }
  373. $callback = function ($matches) use ($that)
  374. {
  375. $char = $matches[0];
  376. // \xHH
  377. if (!isset($char[1])) {
  378. return '\\x'.substr('00'.bin2hex($char), -2);
  379. }
  380. // \uHHHH
  381. $char = $that->convertEncoding($char, 'UTF-16BE', 'UTF-8');
  382. return '\\u'.substr('0000'.bin2hex($char), -4);
  383. };
  384. if (null === $value = preg_replace_callback('#[^\p{L}\p{N} ]#u', $callback, $value)) {
  385. throw new \InvalidArgumentException('The string to escape is not a valid UTF-8 string.');
  386. }
  387. if ('UTF-8' != $that->getCharset()) {
  388. $value = $that->convertEncoding($value, $that->getCharset(), 'UTF-8');
  389. }
  390. return $value;
  391. },
  392. );
  393. }
  394. /**
  395. * Convert a string from one encoding to another.
  396. *
  397. * @param string $string The string to convert
  398. * @param string $to The input encoding
  399. * @param string $from The output encoding
  400. *
  401. * @return string The string with the new encoding
  402. *
  403. * @throws \RuntimeException if no suitable encoding function is found (iconv or mbstring)
  404. */
  405. public function convertEncoding($string, $to, $from)
  406. {
  407. if (function_exists('iconv')) {
  408. return iconv($from, $to, $string);
  409. } elseif (function_exists('mb_convert_encoding')) {
  410. return mb_convert_encoding($string, $to, $from);
  411. }
  412. throw new \RuntimeException('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).');
  413. }
  414. /**
  415. * Gets the loader associated with this engine.
  416. *
  417. * @return LoaderInterface A LoaderInterface instance
  418. */
  419. public function getLoader()
  420. {
  421. return $this->loader;
  422. }
  423. /**
  424. * Loads the given template.
  425. *
  426. * @param mixed $name A template name or a TemplateReferenceInterface instance
  427. *
  428. * @return Storage A Storage instance
  429. *
  430. * @throws \InvalidArgumentException if the template cannot be found
  431. */
  432. protected function load($name)
  433. {
  434. $template = $this->parser->parse($name);
  435. $key = $template->getLogicalName();
  436. if (isset($this->cache[$key])) {
  437. return $this->cache[$key];
  438. }
  439. $storage = $this->loader->load($template);
  440. if (false === $storage) {
  441. throw new \InvalidArgumentException(sprintf('The template "%s" does not exist.', $template));
  442. }
  443. return $this->cache[$key] = $storage;
  444. }
  445. }