function TwigThemeEngine::renderTemplate

Renders a template.

Parameters

string $template_file: The name of the template to render, without the file extension.

array $variables: A keyed array of variables that will appear in the output.

Return value

string|\Drupal\Component\Render\MarkupInterface The output generated by the template, plus any debug information.

Overrides ThemeEngineInterface::renderTemplate

File

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

Class

TwigThemeEngine
Twig theme engine.

Namespace

Drupal\Core\Template

Code

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);
}

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