3 namespace Drupal\Core\Template;
5 use Drupal\Core\Cache\CacheBackendInterface;
6 use Drupal\Core\PhpStorage\PhpStorageFactory;
7 use Drupal\Core\Render\Markup;
8 use Drupal\Core\State\StateInterface;
11 * A class that defines a Twig environment for Drupal.
13 * Instances of this class are used to store the configuration and extensions,
14 * and are used to load templates from the file system or other locations.
16 * @see core\vendor\twig\twig\lib\Twig\Environment.php
18 class TwigEnvironment extends \Twig_Environment {
21 * Key name of the Twig cache prefix metadata key-value pair in State.
23 const CACHE_PREFIX_METADATA_KEY = 'twig_extension_hash_prefix';
28 * @var \Drupal\Core\State\StateInterface
33 * Static cache of template classes.
37 protected $templateClasses;
39 protected $twigCachePrefix = '';
42 * Constructs a TwigEnvironment object and stores cache and storage
47 * @param \Drupal\Core\Cache\CacheBackendInterface $cache
49 * @param string $twig_extension_hash
50 * The Twig extension hash.
51 * @param \Drupal\Core\State\StateInterface $state
53 * @param \Twig_LoaderInterface $loader
54 * The Twig loader or loader chain.
55 * @param array $options
56 * The options for the Twig environment.
58 public function __construct($root, CacheBackendInterface $cache, $twig_extension_hash, StateInterface $state, \Twig_LoaderInterface $loader = NULL, $options = []) {
59 $this->state = $state;
61 // Ensure that twig.engine is loaded, given that it is needed to render a
62 // template because functions like TwigExtension::escapeFilter() are called.
63 require_once $root . '/core/themes/engines/twig/twig.engine';
65 $this->templateClasses = [];
68 // @todo Ensure garbage collection of expired files.
71 'auto_reload' => NULL,
73 // Ensure autoescaping is always on.
74 $options['autoescape'] = 'html';
76 $policy = new TwigSandboxPolicy();
77 $sandbox = new \Twig_Extension_Sandbox($policy, TRUE);
78 $this->addExtension($sandbox);
80 if ($options['cache'] === TRUE) {
81 $current = $state->get(static::CACHE_PREFIX_METADATA_KEY, ['twig_extension_hash' => '']);
82 if ($current['twig_extension_hash'] !== $twig_extension_hash || empty($current['twig_cache_prefix'])) {
84 'twig_extension_hash' => $twig_extension_hash,
85 // Generate a new prefix which invalidates any existing cached files.
86 'twig_cache_prefix' => uniqid(),
89 $state->set(static::CACHE_PREFIX_METADATA_KEY, $current);
91 $this->twigCachePrefix = $current['twig_cache_prefix'];
93 $options['cache'] = new TwigPhpStorageCache($cache, $this->twigCachePrefix);
96 $this->loader = $loader;
97 parent::__construct($this->loader, $options);
101 * Invalidates all compiled Twig templates.
103 * @see \drupal_flush_all_caches
105 public function invalidate() {
106 PhpStorageFactory::get('twig')->deleteAll();
107 $this->templateClasses = [];
108 $this->loadedTemplates = [];
109 $this->state->delete(static::CACHE_PREFIX_METADATA_KEY);
113 * Get the cache prefixed used by \Drupal\Core\Template\TwigPhpStorageCache
116 * The file cache prefix, or empty string if the cache is disabled.
118 public function getTwigCachePrefix() {
119 return $this->twigCachePrefix;
123 * Gets the template class associated with the given string.
125 * @param string $name
126 * The name for which to calculate the template class name.
128 * The index if it is an embedded template.
131 * The template class name.
133 public function getTemplateClass($name, $index = NULL) {
134 // We override this method to add caching because it gets called multiple
135 // times when the same template is used more than once. For example, a page
136 // rendering 50 nodes without any node template overrides will use the same
137 // node.html.twig for the output of each node and the same compiled class.
138 $cache_index = $name . (NULL === $index ? '' : '_' . $index);
139 if (!isset($this->templateClasses[$cache_index])) {
140 $this->templateClasses[$cache_index] = $this->templateClassPrefix . hash('sha256', $this->loader->getCacheKey($name)) . (NULL === $index ? '' : '_' . $index);
142 return $this->templateClasses[$cache_index];
146 * Renders a twig string directly.
148 * Warning: You should use the render element 'inline_template' together with
149 * the #template attribute instead of this method directly.
150 * On top of that you have to ensure that the template string is not dynamic
151 * but just an ordinary static php string, because there may be installations
152 * using read-only PHPStorage that want to generate all possible twig
153 * templates as part of a build step. So it is important that an automated
154 * script can find the templates and extract them. This is only possible if
155 * the template is a regular string.
157 * @param string $template_string
158 * The template string to render with placeholders.
159 * @param array $context
160 * An array of parameters to pass to the template.
162 * @return \Drupal\Component\Render\MarkupInterface|string
163 * The rendered inline template as a Markup object.
165 * @see \Drupal\Core\Template\Loader\StringLoader::exists()
167 public function renderInline($template_string, array $context = []) {
168 // Prefix all inline templates with a special comment.
169 $template_string = '{# inline_template_start #}' . $template_string;
170 return Markup::create($this->loadTemplate($template_string, NULL)->render($context));