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

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

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