class TwigThemeEngine

Twig theme engine.

Hierarchy

Expanded class hierarchy of TwigThemeEngine

1 file declares its use of TwigThemeEngine
FilterKernelTest.php in core/modules/filter/tests/src/Kernel/FilterKernelTest.php

File

core/lib/Drupal/Core/Template/TwigThemeEngine.php, line 14

Namespace

Drupal\Core\Template
View source
class TwigThemeEngine implements ThemeEngineInterface {
  protected const EXTENSION = '.html.twig';
  public function __construct(protected TwigEnvironment $twig) {
  }
  
  /**
   * {@inheritdoc}
   */
  public function theme(array $existing, string $type, string $theme, string $path) : array {
    return drupal_find_theme_templates($existing, self::EXTENSION, $path);
  }
  
  /**
   * {@inheritdoc}
   */
  public function renderTemplate(string $template_file, array $variables) : string|MarkupInterface {
    $template_file .= self::EXTENSION;
    try {
      $rendered_markup = $this->twig
        ->load($template_file)
        ->render($variables);
    } catch (RuntimeError $e) {
      // In case there is a previous exception, re-throw the previous exception,
      // so that the original exception is shown, rather than
      // \Twig\Template::displayWithErrorHandling()'s exception.
      $previous_exception = $e->getPrevious();
      if ($previous_exception) {
        throw $previous_exception;
      }
      throw $e;
    }
    if ($this->twig
      ->isDebug()) {
      $output = [
        'debug_prefix' => '',
        'debug_info' => '',
        'rendered_markup' => $rendered_markup,
        'debug_suffix' => '',
      ];
      $output['debug_prefix'] .= "\n\n<!-- THEME DEBUG -->";
      $output['debug_prefix'] .= "\n<!-- THEME HOOK: '" . Html::escape($variables['theme_hook_original']) . "' -->";
      // If there are theme suggestions, reverse the array so more specific
      // suggestions are shown first.
      if (!empty($variables['theme_hook_suggestions'])) {
        $variables['theme_hook_suggestions'] = array_reverse($variables['theme_hook_suggestions']);
      }
      // Add debug output for directly called suggestions like
      // '#theme' => 'comment__node__article'.
      if (str_contains($variables['theme_hook_original'], '__')) {
        $derived_suggestions[] = $hook = $variables['theme_hook_original'];
        while ($pos = strrpos($hook, '__')) {
          $hook = substr($hook, 0, $pos);
          $derived_suggestions[] = $hook;
        }
        // Get the value of the base hook (last derived suggestion) and append
        // it to the end of all theme suggestions.
        $base_hook = array_pop($derived_suggestions);
        $variables['theme_hook_suggestions'] = array_merge($derived_suggestions, $variables['theme_hook_suggestions']);
        $variables['theme_hook_suggestions'][] = $base_hook;
      }
      if (!empty($variables['theme_hook_suggestions'])) {
        $current_template = basename($template_file);
        $suggestions = $variables['theme_hook_suggestions'];
        // Only add the original theme hook if it wasn't a directly called
        // suggestion.
        if (!str_contains($variables['theme_hook_original'], '__')) {
          $suggestions[] = $variables['theme_hook_original'];
        }
        $invalid_suggestions = [];
        $base_hook = $base_hook ?? $variables['theme_hook_original'];
        foreach ($suggestions as $key => &$suggestion) {
          // Valid suggestions are $base_hook, $base_hook__*, and contain no
          // hyphens.
          if ($suggestion !== $base_hook && !str_starts_with($suggestion, $base_hook . '__') || str_contains($suggestion, '-')) {
            $invalid_suggestions[] = $suggestion;
            unset($suggestions[$key]);
            continue;
          }
          $template = strtr($suggestion, '_', '-') . self::EXTENSION;
          $prefix = $template == $current_template ? '✅' : '▪️';
          // Add deprecated hint.
          if (isset($variables['theme_hook_suggestions__DEPRECATED'][$suggestion])) {
            $template .= ' (deprecated)';
          }
          $suggestion = $prefix . ' ' . $template;
        }
        $output['debug_info'] .= "\n<!-- FILE NAME SUGGESTIONS:\n   " . Html::escape(implode("\n   ", $suggestions)) . "\n-->";
        if (!empty($invalid_suggestions)) {
          $output['debug_info'] .= "\n<!-- INVALID FILE NAME SUGGESTIONS:";
          $output['debug_info'] .= "\n   See https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Render!theme.api.php/function/hook_theme_suggestions_alter";
          $output['debug_info'] .= "\n   " . Html::escape(implode("\n   ", $invalid_suggestions));
          $output['debug_info'] .= "\n-->";
        }
      }
      // Check if the template_file belongs to a custom theme.
      $template_override_status_output = "BEGIN OUTPUT";
      $template_override_suffix_output = "END OUTPUT";
      if (str_starts_with($template_file, $variables['directory'])) {
        $template_override_status_output = "💡 BEGIN CUSTOM TEMPLATE OUTPUT";
        $template_override_suffix_output = "END CUSTOM TEMPLATE OUTPUT";
      }
      $output['debug_info'] .= "\n<!-- " . $template_override_status_output . " from '" . Html::escape($template_file) . "' -->\n";
      $output['debug_suffix'] .= "\n<!-- " . $template_override_suffix_output . " from '" . Html::escape($template_file) . "' -->\n\n";
      // This output has already been rendered and is therefore considered safe.
      return Markup::create(implode('', $output));
    }
    return Markup::create($rendered_markup);
  }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title
TwigThemeEngine::EXTENSION protected constant
TwigThemeEngine::renderTemplate public function Renders a template. Overrides ThemeEngineInterface::renderTemplate
TwigThemeEngine::theme public function Registers templates that belong to the theme engine. Overrides ThemeEngineInterface::theme
TwigThemeEngine::__construct public function

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.