PageRenderTime 41ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

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

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