qtools_profiler-8.x-1.x-dev/modules/qtools_cache_profiler/src/Renderer.php

modules/qtools_cache_profiler/src/Renderer.php
<?php

namespace Drupal\qtools_cache_profiler;

use Drupal\Component\Serialization\Json;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Render\Markup;
use Drupal\Core\Render\Renderer as CoreRenderer;
use Drupal\qtools_common\QToolsCacheHelper;

/**
 * Decorates core's Renderer to provide the necessary metadata to renderviz' JS.
 */
class Renderer extends CoreRenderer {

  const LOG_START = 'START';
  const LOG_END = 'END';

  /**
   * Wrapper to easy bedug.
   */
  protected function log($type, $cacheId = NULL, $data = NULL) {
    if (TRUE) {
      return;
    }

    // Logger function.
    $log = function_exists('dpm') ? 'dpm' : 'print_r';

    // Output payload before separator if we closing render.
    if ($type == static::LOG_END && !empty($data)) {
      $log($data);
    }

    // Print action and cid.
    $prefix = '=============== RENDER ' . $type;
    $log($prefix . ': ' . $cacheId);

    // Output payload after separator if we opening render.
    if ($type == static::LOG_START && !empty($data)) {
      $log($data);
    }
  }

  /**
   * Check if given content require wraping to correcly place metadata.
   */
  protected function requireWrap($elements, $cleaned_content) {
    $trimmed_content = trim($elements['#markup']);

    // To preserve all cache info we need a target tag, so we have to wrap
    // output but we can't wrap some special tags or drupal will break.
    $unwrappable_tags = [
      '<script',
      '<link',
      '<style',
      '<meta',
      '<title',
      '<body',
      '<html',
      '<head',
      '<!DOCTYPE',
    ];
    foreach ($unwrappable_tags as $tag) {
      if (strpos($cleaned_content, $tag) === 0) {
        return FALSE;
      }
    }

    // If template is empty we would see previous render info.
    $wrap = strpos($trimmed_content, '<!--RENDERER_START-->') === 0;

    // If elements are about to be lazy built we wrap them.
    if (!$wrap && !empty($elements['#lazy_builder'])) {
      $wrap = TRUE;
    }

    // If there are more than one child element we need to wrap.
    if (!$wrap) {
      $dom = new \DOMDocument();

      libxml_use_internal_errors(TRUE);
      $dom->loadHTML($cleaned_content);

      // Structure will be wrapped in <html><body> so we go 2 level deep.
      $childNodes = $dom->childNodes[1]->childNodes[0]->childNodes;
      if ($childNodes->length > 1) {
        $wrap = TRUE;
      }
      libxml_use_internal_errors(FALSE);
    }

    return $wrap;
  }

  /**
   * {@inheritdoc}
   */
  protected function doRender(&$elements, $is_root_call = FALSE) {
    $placeholder = !empty($elements['#create_placeholder']);

    $original_elements = [];
    $original_elements['#cache'] = isset($elements['#cache']) ? $elements['#cache'] : [];

    // Create cache ID and store it alongside with rendered metadata.
    $cacheId = QToolsCacheHelper::buildCidElements($elements);
    if (!empty($cacheId)) {
      $this->log(static::LOG_START, $cacheId, $elements);
      $keys = $elements['#cache']['keys'];
    };

    // Do normal rendering.
    $result = parent::doRender($elements, $is_root_call);

    // When there is no output, there is also nothing to visualize.
    if ($result === '') {
      $this->log(static::LOG_END, $cacheId);
      return '';
    }

    // The HTML spec says HTML comments are markup, thus it's not allowed to use
    // HTML comments inside HTML element attributes. For example:
    // <a <!--title="need to be comment out"-->>a link</a>
    // is as wrong as
    // <a <span></span>>a link</a>
    // So we only wrap $result in a HTML comment with renderviz metadata when
    // $result actually contains HTML markup. So, 'This is text.' will cause an
    // early return, but 'This is text and <a href="…">a link</a>.' will not.
    // The presence of HTML indicates it's valid to have HTML, and hwen it
    // valid to have HTML, HTML comments are allowed too.
    // Since strip tags always remove comments we need to make sure
    // they will not affect our compassing.
    $cleaned_result = trim(preg_replace('/<!--(.*)-->/Uis', '', $result));

    // Check if this chunk is HTML markup.
    if ($cleaned_result == strip_tags($cleaned_result)) {
      $this->log(static::LOG_END, $cacheId, $result);
      // Returned without debug output.
      return $result;
    }

    // Apply the same default cacheability logic that Renderer::doRender()
    // applies.
    $pre_bubbling_elements = $original_elements;
    $pre_bubbling_elements['#cache']['tags'] = isset($original_elements['#cache']['tags']) ? $original_elements['#cache']['tags'] : [];
    $pre_bubbling_elements['#cache']['max-age'] = isset($original_elements['#cache']['max-age']) ? $original_elements['#cache']['max-age'] : Cache::PERMANENT;
    // @todo Add these always? That's more accurate for visualization purposes;
    //   it is only for performance optimization purposes that the wrapped
    //   function doesn't do that.
    if ($is_root_call || isset($elements['#cache']['keys'])) {
      $required_cache_contexts = $this->rendererConfig['required_cache_contexts'];
      if (isset($pre_bubbling_elements['#cache']['contexts'])) {
        $pre_bubbling_elements['#cache']['contexts'] = Cache::mergeContexts($pre_bubbling_elements['#cache']['contexts'], $required_cache_contexts);
      }
      else {
        $pre_bubbling_elements['#cache']['contexts'] = $required_cache_contexts;
      }
    }

    // Prepare data for output.
    $result_cache = $elements['#cache'];
    $pre_bubbling_cache = $pre_bubbling_elements['#cache'];

    // If this render could have been cached we need to get its final cacheId
    // as it will be different from previous due to bubbling.
    if (!empty($cacheId)) {
      $cacheId2 = QToolsCacheHelper::buildCidElements($elements, $keys);
      $result_cache['qtools-cache-profiler-cacheable'] = 1;
    }
    elseif (!empty($elements['#lazy_builder_built'])) {
      // Lazy built blocks defy normal rule in a way that they are always
      // built on every page load, and must act as ones that
      // potentially cacheable.
      $cacheId2 = QToolsCacheHelper::buildCidElements($elements);
      $result_cache['qtools-cache-profiler-cacheable'] = 1;
      $result_cache['qtools-cache-profiler-lazy'] = 1;
    }
    elseif (strpos($cleaned_result, '<!DOCTYPE') === 0) {
      $result_cache['qtools-cache-profiler-cacheable'] = 1;
      $result_cache['contexts'][] = 'route';
      $result_cache['contexts'][] = 'request_format';
      $result_cache['contexts'][] = 'url.query_args:_wrapper_format';
      $cacheId2 = QToolsCacheHelper::buildCidElements(['#cache' => $result_cache], ['response']);
    }

    // Add real cid to the output.
    if (!empty($cacheId2)) {
      $result_cache['qtools-cache-profiler-cid'] = $cacheId2;
    }

    // Add debug output.
    // @todo: This currently prints the final and pre-bubbling elements.
    $interesting_keys = ['keys', 'contexts', 'tags', 'max-age'];
    if (array_intersect(array_keys($result_cache), $interesting_keys) || array_intersect(array_keys($pre_bubbling_cache), $interesting_keys)) {
      $prefix = '<!--RENDERER_START-->' . '<!--' . Json::encode($result_cache) . '-->' . '<!--' . Json::encode($pre_bubbling_cache) . '-->';
      $suffix = '<!--RENDERER_END-->';

      // If this render support caching add cid for easy visualise.
      if (!empty($cacheId2)) {
        $prefix .= '<!--RENDERER_CID ' . $cacheId2 . ' -->';
      }

      // Some templates does not have any additional tags, therefor
      // there is nothing to attach our data to, as we add wrapper if allowed
      // and this data is critical.
      // Placeholder elements always wrapped as their cache info is yet unknown.
      if (!empty($placeholder) || (!empty($cacheId2) && $this->requireWrap($elements, $cleaned_result))) {
        $elements['#markup'] = '<div>' . $elements['#markup'] . '</div>';
      }
      $elements['#markup'] = Markup::create($prefix . $elements['#markup'] . $suffix);
    }

    // Debug results.
    if (!empty($cacheId2)) {
      $this->log(static::LOG_END, $cacheId2, $elements);
    };
    return $elements['#markup'];
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc