PageRenderTime 27ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

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

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