TwigEnvironment.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. <?php
  2. namespace Drupal\Core\Template;
  3. use Drupal\Core\Cache\CacheBackendInterface;
  4. use Drupal\Core\PhpStorage\PhpStorageFactory;
  5. use Drupal\Core\Render\Markup;
  6. use Drupal\Core\State\StateInterface;
  7. /**
  8. * A class that defines a Twig environment for Drupal.
  9. *
  10. * Instances of this class are used to store the configuration and extensions,
  11. * and are used to load templates from the file system or other locations.
  12. *
  13. * @see core\vendor\twig\twig\lib\Twig\Environment.php
  14. */
  15. class TwigEnvironment extends \Twig_Environment {
  16. /**
  17. * Key name of the Twig cache prefix metadata key-value pair in State.
  18. */
  19. const CACHE_PREFIX_METADATA_KEY = 'twig_extension_hash_prefix';
  20. /**
  21. * The state service.
  22. *
  23. * @var \Drupal\Core\State\StateInterface
  24. */
  25. protected $state;
  26. /**
  27. * Static cache of template classes.
  28. *
  29. * @var array
  30. */
  31. protected $templateClasses;
  32. /**
  33. * The template cache filename prefix.
  34. *
  35. * @var string
  36. */
  37. protected $twigCachePrefix = '';
  38. /**
  39. * Constructs a TwigEnvironment object and stores cache and storage
  40. * internally.
  41. *
  42. * @param string $root
  43. * The app root.
  44. * @param \Drupal\Core\Cache\CacheBackendInterface $cache
  45. * The cache bin.
  46. * @param string $twig_extension_hash
  47. * The Twig extension hash.
  48. * @param \Drupal\Core\State\StateInterface $state
  49. * The state service.
  50. * @param \Twig_LoaderInterface $loader
  51. * The Twig loader or loader chain.
  52. * @param array $options
  53. * The options for the Twig environment.
  54. */
  55. public function __construct($root, CacheBackendInterface $cache, $twig_extension_hash, StateInterface $state, \Twig_LoaderInterface $loader = NULL, array $options = []) {
  56. $this->state = $state;
  57. // Ensure that twig.engine is loaded, given that it is needed to render a
  58. // template because functions like TwigExtension::escapeFilter() are called.
  59. // @todo remove in Drupal 9.0.0 https://www.drupal.org/node/3011393.
  60. require_once $root . '/core/themes/engines/twig/twig.engine';
  61. $this->templateClasses = [];
  62. $options += [
  63. // @todo Ensure garbage collection of expired files.
  64. 'cache' => TRUE,
  65. 'debug' => FALSE,
  66. 'auto_reload' => NULL,
  67. ];
  68. // Ensure autoescaping is always on.
  69. $options['autoescape'] = 'html';
  70. if ($options['cache'] === TRUE) {
  71. $current = $state->get(static::CACHE_PREFIX_METADATA_KEY, ['twig_extension_hash' => '']);
  72. if ($current['twig_extension_hash'] !== $twig_extension_hash || empty($current['twig_cache_prefix'])) {
  73. $current = [
  74. 'twig_extension_hash' => $twig_extension_hash,
  75. // Generate a new prefix which invalidates any existing cached files.
  76. 'twig_cache_prefix' => uniqid(),
  77. ];
  78. $state->set(static::CACHE_PREFIX_METADATA_KEY, $current);
  79. }
  80. $this->twigCachePrefix = $current['twig_cache_prefix'];
  81. $options['cache'] = new TwigPhpStorageCache($cache, $this->twigCachePrefix);
  82. }
  83. $this->setLoader($loader);
  84. parent::__construct($this->getLoader(), $options);
  85. $policy = new TwigSandboxPolicy();
  86. $sandbox = new \Twig_Extension_Sandbox($policy, TRUE);
  87. $this->addExtension($sandbox);
  88. }
  89. /**
  90. * Invalidates all compiled Twig templates.
  91. *
  92. * @see \drupal_flush_all_caches
  93. */
  94. public function invalidate() {
  95. PhpStorageFactory::get('twig')->deleteAll();
  96. $this->templateClasses = [];
  97. $this->state->delete(static::CACHE_PREFIX_METADATA_KEY);
  98. }
  99. /**
  100. * Get the cache prefixed used by \Drupal\Core\Template\TwigPhpStorageCache
  101. *
  102. * @return string
  103. * The file cache prefix, or empty string if the cache is disabled.
  104. */
  105. public function getTwigCachePrefix() {
  106. return $this->twigCachePrefix;
  107. }
  108. /**
  109. * Gets the template class associated with the given string.
  110. *
  111. * @param string $name
  112. * The name for which to calculate the template class name.
  113. * @param int $index
  114. * The index if it is an embedded template.
  115. *
  116. * @return string
  117. * The template class name.
  118. */
  119. public function getTemplateClass($name, $index = NULL) {
  120. // We override this method to add caching because it gets called multiple
  121. // times when the same template is used more than once. For example, a page
  122. // rendering 50 nodes without any node template overrides will use the same
  123. // node.html.twig for the output of each node and the same compiled class.
  124. $cache_index = $name . (NULL === $index ? '' : '_' . $index);
  125. if (!isset($this->templateClasses[$cache_index])) {
  126. $this->templateClasses[$cache_index] = parent::getTemplateClass($name, $index);
  127. }
  128. return $this->templateClasses[$cache_index];
  129. }
  130. /**
  131. * Renders a twig string directly.
  132. *
  133. * Warning: You should use the render element 'inline_template' together with
  134. * the #template attribute instead of this method directly.
  135. * On top of that you have to ensure that the template string is not dynamic
  136. * but just an ordinary static php string, because there may be installations
  137. * using read-only PHPStorage that want to generate all possible twig
  138. * templates as part of a build step. So it is important that an automated
  139. * script can find the templates and extract them. This is only possible if
  140. * the template is a regular string.
  141. *
  142. * @param string $template_string
  143. * The template string to render with placeholders.
  144. * @param array $context
  145. * An array of parameters to pass to the template.
  146. *
  147. * @return \Drupal\Component\Render\MarkupInterface|string
  148. * The rendered inline template as a Markup object.
  149. *
  150. * @see \Drupal\Core\Template\Loader\StringLoader::exists()
  151. */
  152. public function renderInline($template_string, array $context = []) {
  153. // Prefix all inline templates with a special comment.
  154. $template_string = '{# inline_template_start #}' . $template_string;
  155. return Markup::create($this->createTemplate($template_string)->render($context));
  156. }
  157. }