TwigEnvironment.php 6.49 KB
Newer Older
1 2 3 4
<?php

/**
 * @file
5
 * Contains \Drupal\Core\Template\TwigEnvironment.
6 7 8 9
 */

namespace Drupal\Core\Template;

10
use Drupal\Core\PhpStorage\PhpStorageFactory;
11

12 13 14 15 16 17
/**
 * A class that defines a Twig environment for Drupal.
 *
 * Instances of this class are used to store the configuration and extensions,
 * and are used to load templates from the file system or other locations.
 *
18
 * @see core\vendor\twig\twig\lib\Twig\Environment.php
19 20 21 22 23
 */
class TwigEnvironment extends \Twig_Environment {
  protected $cache_object = NULL;
  protected $storage = NULL;

24 25 26 27 28 29 30
  /**
   * Static cache of template classes.
   *
   * @var array
   */
  protected $templateClasses;

31 32 33
  /**
   * Constructs a TwigEnvironment object and stores cache and storage
   * internally.
34 35
   *
   * @param string $root
36 37 38 39 40
   *   The app root.
   * @param \Twig_LoaderInterface $loader
   *   The Twig loader or loader chain.
   * @param array $options
   *   The options for the Twig environment.
41
   */
42
  public function __construct($root, \Twig_LoaderInterface $loader = NULL, $options = array()) {
43
    // @todo Pass as arguments from the DIC.
44
    $this->cache_object = \Drupal::cache();
45

46 47
    // Ensure that twig.engine is loaded, given that it is needed to render a
    // template because functions like twig_drupal_escape_filter are called.
48
    require_once $root . '/core/themes/engines/twig/twig.engine';
49

50 51
    $this->templateClasses = array();

52 53 54 55 56 57
    $options += array(
      // @todo Ensure garbage collection of expired files.
      'cache' => TRUE,
      'debug' => FALSE,
      'auto_reload' => NULL,
    );
58 59
    // Ensure autoescaping is always on.
    $options['autoescape'] = TRUE;
60

61
    $this->loader = $loader;
62
    parent::__construct($this->loader, $options);
63 64 65 66 67
  }

  /**
   * Checks if the compiled template needs an update.
   */
68 69 70 71 72
  protected function isFresh($cache_filename, $name) {
    $cid = 'twig:' . $cache_filename;
    $obj = $this->cache_object->get($cid);
    $mtime = isset($obj->data) ? $obj->data : FALSE;
    return $mtime === FALSE || $this->isTemplateFresh($name, $mtime);
73 74 75 76 77
  }

  /**
   * Compile the source and write the compiled template to disk.
   */
78 79
  public function updateCompiledTemplate($cache_filename, $name) {
    $source = $this->loader->getSource($name);
80
    $compiled_source = $this->compileSource($source, $name);
81
    $this->storage()->save($cache_filename, $compiled_source);
82 83 84 85 86 87 88 89 90 91 92 93
    // Save the last modification time
    $cid = 'twig:' . $cache_filename;
    $this->cache_object->set($cid, REQUEST_TIME);
  }

  /**
   * Implements Twig_Environment::loadTemplate().
   *
   * We need to overwrite this function to integrate with drupal_php_storage().
   *
   * This is a straight copy from loadTemplate() changed to use
   * drupal_php_storage().
94 95 96 97 98 99 100 101 102 103 104 105 106
   *
   * @param string $name
   *   The template name or the string which should be rendered as template.
   * @param int $index
   *   The index if it is an embedded template.
   *
   * @return \Twig_TemplateInterface
   *   A template instance representing the given template name.
   *
   * @throws \Twig_Error_Loader
   *   When the template cannot be found.
   * @throws \Twig_Error_Syntax
   *   When an error occurred during compilation.
107
   */
108 109
  public function loadTemplate($name, $index = NULL) {
    $cls = $this->getTemplateClass($name, $index);
110 111 112 113 114 115 116 117 118

    if (isset($this->loadedTemplates[$cls])) {
      return $this->loadedTemplates[$cls];
    }

    if (!class_exists($cls, FALSE)) {
      $cache_filename = $this->getCacheFilename($name);

      if ($cache_filename === FALSE) {
119
        $compiled_source = $this->compileSource($this->loader->getSource($name), $name);
120
        eval('?' . '>' . $compiled_source);
121 122
      }
      else {
123 124 125

        // If autoreload is on, check that the template has not been
        // modified since the last compilation.
126
        if ($this->isAutoReload() && !$this->isFresh($cache_filename, $name)) {
127
          $this->updateCompiledTemplate($cache_filename, $name);
128 129
        }

130
        if (!$this->storage()->load($cache_filename)) {
131
          $this->updateCompiledTemplate($cache_filename, $name);
132
          $this->storage()->load($cache_filename);
133 134 135 136 137 138 139 140 141 142 143
        }
      }
    }

    if (!$this->runtimeInitialized) {
        $this->initRuntime();
    }

    return $this->loadedTemplates[$cls] = new $cls($this);
  }

144 145 146 147 148 149 150
  /**
   * Gets the PHP code storage object to use for the compiled Twig files.
   *
   * @return \Drupal\Component\PhpStorage\PhpStorageInterface
   */
  protected function storage() {
    if (!isset($this->storage)) {
151
      $this->storage = PhpStorageFactory::get('twig');
152 153 154 155
    }
    return $this->storage;
  }

156
  /**
157 158 159 160 161 162 163 164 165
   * Gets the template class associated with the given string.
   *
   * @param string $name
   *   The name for which to calculate the template class name.
   * @param int $index
   *   The index if it is an embedded template.
   *
   * @return string
   *   The template class name.
166
   */
167
  public function getTemplateClass($name, $index = NULL) {
168 169 170 171 172 173
    // We override this method to add caching because it gets called multiple
    // times when the same template is used more than once. For example, a page
    // rendering 50 nodes without any node template overrides will use the same
    // node.html.twig for the output of each node and the same compiled class.
    $cache_index = $name . (NULL === $index ? '' : '_' . $index);
    if (!isset($this->templateClasses[$cache_index])) {
174
      $this->templateClasses[$cache_index] = $this->templateClassPrefix . hash('sha256', $this->loader->getCacheKey($name)) . (NULL === $index ? '' : '_' . $index);
175 176 177 178
    }
    return $this->templateClasses[$cache_index];
  }

179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
  /**
   * Renders a twig string directly.
   *
   * Warning: You should use the render element 'inline_template' together with
   * the #template attribute instead of this method directly.
   * On top of that you have to ensure that the template string is not dynamic
   * but just an ordinary static php string, because there may be installations
   * using read-only PHPStorage that want to generate all possible twig
   * templates as part of a build step. So it is important that an automated
   * script can find the templates and extract them. This is only possible if
   * the template is a regular string.
   *
   * @param string $template_string
   *   The template string to render with placeholders.
   * @param array $context
   *   An array of parameters to pass to the template.
   *
   * @return string
   *   The rendered inline template.
   */
  public function renderInline($template_string, array $context = array()) {
200
    return $this->loadTemplate($template_string, NULL)->render($context);
201 202
  }

203
}