Loading core/assets/scaffold/files/default.services.yml +8 −0 Original line number Diff line number Diff line Loading @@ -132,6 +132,14 @@ parameters: # # @default [] tags: [] # Renderer cache debug: # # Allows cache debugging output for each rendered element. # # Enabling render cache debugging is not recommended in production # environments. # @default false debug: false # Cacheability debugging: # # Responses with cacheability metadata (CacheableResponseInterface instances) Loading core/core.services.yml +1 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ parameters: max-age: 0 contexts: ['session', 'user'] tags: [] debug: false factory.keyvalue: default: keyvalue.database http.response.debug_cacheability_headers: false Loading core/lib/Drupal/Core/Render/Renderer.php +79 −0 Original line number Diff line number Diff line Loading @@ -120,6 +120,9 @@ public function __construct(ControllerResolverInterface $controller_resolver, Th $this->elementInfo = $element_info; $this->placeholderGenerator = $placeholder_generator; $this->renderCache = $render_cache; if (!isset($renderer_config['debug'])) { $renderer_config['debug'] = FALSE; } $this->rendererConfig = $renderer_config; $this->requestStack = $request_stack; Loading Loading @@ -215,6 +218,10 @@ protected function doRender(&$elements, $is_root_call = FALSE) { return ''; } if ($this->rendererConfig['debug'] === TRUE) { $render_start = microtime(TRUE); } if (!isset($elements['#access']) && isset($elements['#access_callback'])) { $elements['#access'] = $this->doCallback('#access_callback', $elements['#access_callback'], [$elements]); } Loading Loading @@ -276,6 +283,10 @@ protected function doRender(&$elements, $is_root_call = FALSE) { if (is_string($elements['#markup'])) { $elements['#markup'] = Markup::create($elements['#markup']); } // Add debug output to the renderable array on cache hit. if ($this->rendererConfig['debug'] === TRUE) { $elements = $this->addDebugOutput($elements, TRUE); } // The render cache item contains all the bubbleable rendering metadata // for the subtree. $context->update($elements); Loading Loading @@ -513,6 +524,11 @@ protected function doRender(&$elements, $is_root_call = FALSE) { throw new \LogicException('Cache keys may not be changed after initial setup. Use the contexts property instead to bubble additional metadata.'); } $this->renderCache->set($elements, $pre_bubbling_elements); // Add debug output to the renderable array on cache miss. if ($this->rendererConfig['debug'] === TRUE) { $render_stop = microtime(TRUE); $elements = $this->addDebugOutput($elements, FALSE, $pre_bubbling_elements, $render_stop - $render_start); } // Update the render context; the render cache implementation may update // the element, and it may have different bubbleable metadata now. // @see \Drupal\Core\Render\PlaceholderingRenderCache::set() Loading Loading @@ -772,4 +788,67 @@ protected function doCallback($callback_type, $callback, array $args) { return $this->doTrustedCallback($callback, $args, $message, TrustedCallbackInterface::THROW_EXCEPTION, RenderCallbackInterface::class); } /** * Add cache debug information to the render array. * * @param array $elements * The renderable array that must be wrapped with the cache debug output. * @param bool $is_cache_hit * A flag indicating that the cache is hit or miss. * @param array $pre_bubbling_elements * The renderable array for pre-bubbling elements. * @param float $render_time * The rendering time. * * @return array * The renderable array. */ protected function addDebugOutput(array $elements, bool $is_cache_hit, array $pre_bubbling_elements = [], float $render_time = 0) { if (empty($elements['#markup'])) { return $elements; } $debug_items = [ 'CACHE' => &$elements, 'PRE-BUBBLING CACHE' => &$pre_bubbling_elements, ]; $prefix = "<!-- START RENDERER -->"; $prefix .= "\n<!-- CACHE-HIT: " . ($is_cache_hit ? 'Yes' : 'No') . " -->"; foreach ($debug_items as $name_prefix => $debug_item) { if (!empty($debug_item['#cache']['tags'])) { $prefix .= "\n<!-- " . $name_prefix . " TAGS:"; foreach ($debug_item['#cache']['tags'] as $tag) { $prefix .= "\n * " . $tag; } $prefix .= "\n-->"; } if (!empty($debug_item['#cache']['contexts'])) { $prefix .= "\n<!-- " . $name_prefix . " CONTEXTS:"; foreach ($debug_item['#cache']['contexts'] as $context) { $prefix .= "\n * " . $context; } $prefix .= "\n-->"; } if (!empty($debug_item['#cache']['keys'])) { $prefix .= "\n<!-- " . $name_prefix . " KEYS:"; foreach ($debug_item['#cache']['keys'] as $key) { $prefix .= "\n * " . $key; } $prefix .= "\n-->"; } if (!empty($debug_item['#cache']['max-age'])) { $prefix .= "\n<!-- " . $name_prefix . " MAX-AGE: " . $debug_item['#cache']['max-age'] . " -->"; } } if (!empty($render_time)) { $prefix .= "\n<!-- RENDERING TIME: " . number_format($render_time, 9) . " -->"; } $suffix = "<!-- END RENDERER -->"; $elements['#markup'] = Markup::create("$prefix\n" . $elements['#markup'] . "\n$suffix"); return $elements; } } core/tests/Drupal/Tests/Core/Render/RendererDebugTest.php 0 → 100644 +87 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Tests\Core\Render; use function preg_replace; /** * @coversDefaultClass \Drupal\Core\Render\Renderer * @group Render */ class RendererDebugTest extends RendererTestBase { /** * {@inheritdoc} */ protected function setUp(): void { $this->rendererConfig['debug'] = TRUE; parent::setUp(); } /** * Test render debug output. */ public function testDebugOutput() { $this->setUpRequest(); $this->setupMemoryCache(); $element = [ '#cache' => [ 'keys' => ['render_cache_test_key'], 'tags' => ['render_cache_test_tag', 'render_cache_test_tag1'], 'max-age' => 10, ], '#markup' => 'Test 1', ]; $markup = $this->renderer->renderRoot($element); $expected = <<<EOF <!-- START RENDERER --> <!-- CACHE-HIT: No --> <!-- CACHE TAGS: * render_cache_test_tag * render_cache_test_tag1 --> <!-- CACHE CONTEXTS: * languages:language_interface * theme --> <!-- CACHE KEYS: * render_cache_test_key --> <!-- CACHE MAX-AGE: 10 --> <!-- PRE-BUBBLING CACHE TAGS: * render_cache_test_tag * render_cache_test_tag1 --> <!-- PRE-BUBBLING CACHE CONTEXTS: * languages:language_interface * theme --> <!-- PRE-BUBBLING CACHE KEYS: * render_cache_test_key --> <!-- PRE-BUBBLING CACHE MAX-AGE: 10 --> <!-- RENDERING TIME: 0.123456789 --> Test 1 <!-- END RENDERER --> EOF; $this->assertSame($expected, preg_replace('/RENDERING TIME: \d{1}.\d{9}/', 'RENDERING TIME: 0.123456789', $markup->__toString())); $element = [ '#cache' => [ 'keys' => ['render_cache_test_key'], 'tags' => ['render_cache_test_tag', 'render_cache_test_tag1'], 'max-age' => 10, ], '#markup' => 'Test 1', ]; $markup = $this->renderer->renderRoot($element); $this->assertStringContainsString('CACHE-HIT: Yes', $markup->__toString()); } } core/tests/Drupal/Tests/Core/Render/RendererTestBase.php +1 −0 Original line number Diff line number Diff line Loading @@ -109,6 +109,7 @@ abstract class RendererTestBase extends UnitTestCase { 'contexts' => ['session', 'user'], 'tags' => ['current-temperature'], ], 'debug' => FALSE, ]; /** Loading Loading
core/assets/scaffold/files/default.services.yml +8 −0 Original line number Diff line number Diff line Loading @@ -132,6 +132,14 @@ parameters: # # @default [] tags: [] # Renderer cache debug: # # Allows cache debugging output for each rendered element. # # Enabling render cache debugging is not recommended in production # environments. # @default false debug: false # Cacheability debugging: # # Responses with cacheability metadata (CacheableResponseInterface instances) Loading
core/core.services.yml +1 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ parameters: max-age: 0 contexts: ['session', 'user'] tags: [] debug: false factory.keyvalue: default: keyvalue.database http.response.debug_cacheability_headers: false Loading
core/lib/Drupal/Core/Render/Renderer.php +79 −0 Original line number Diff line number Diff line Loading @@ -120,6 +120,9 @@ public function __construct(ControllerResolverInterface $controller_resolver, Th $this->elementInfo = $element_info; $this->placeholderGenerator = $placeholder_generator; $this->renderCache = $render_cache; if (!isset($renderer_config['debug'])) { $renderer_config['debug'] = FALSE; } $this->rendererConfig = $renderer_config; $this->requestStack = $request_stack; Loading Loading @@ -215,6 +218,10 @@ protected function doRender(&$elements, $is_root_call = FALSE) { return ''; } if ($this->rendererConfig['debug'] === TRUE) { $render_start = microtime(TRUE); } if (!isset($elements['#access']) && isset($elements['#access_callback'])) { $elements['#access'] = $this->doCallback('#access_callback', $elements['#access_callback'], [$elements]); } Loading Loading @@ -276,6 +283,10 @@ protected function doRender(&$elements, $is_root_call = FALSE) { if (is_string($elements['#markup'])) { $elements['#markup'] = Markup::create($elements['#markup']); } // Add debug output to the renderable array on cache hit. if ($this->rendererConfig['debug'] === TRUE) { $elements = $this->addDebugOutput($elements, TRUE); } // The render cache item contains all the bubbleable rendering metadata // for the subtree. $context->update($elements); Loading Loading @@ -513,6 +524,11 @@ protected function doRender(&$elements, $is_root_call = FALSE) { throw new \LogicException('Cache keys may not be changed after initial setup. Use the contexts property instead to bubble additional metadata.'); } $this->renderCache->set($elements, $pre_bubbling_elements); // Add debug output to the renderable array on cache miss. if ($this->rendererConfig['debug'] === TRUE) { $render_stop = microtime(TRUE); $elements = $this->addDebugOutput($elements, FALSE, $pre_bubbling_elements, $render_stop - $render_start); } // Update the render context; the render cache implementation may update // the element, and it may have different bubbleable metadata now. // @see \Drupal\Core\Render\PlaceholderingRenderCache::set() Loading Loading @@ -772,4 +788,67 @@ protected function doCallback($callback_type, $callback, array $args) { return $this->doTrustedCallback($callback, $args, $message, TrustedCallbackInterface::THROW_EXCEPTION, RenderCallbackInterface::class); } /** * Add cache debug information to the render array. * * @param array $elements * The renderable array that must be wrapped with the cache debug output. * @param bool $is_cache_hit * A flag indicating that the cache is hit or miss. * @param array $pre_bubbling_elements * The renderable array for pre-bubbling elements. * @param float $render_time * The rendering time. * * @return array * The renderable array. */ protected function addDebugOutput(array $elements, bool $is_cache_hit, array $pre_bubbling_elements = [], float $render_time = 0) { if (empty($elements['#markup'])) { return $elements; } $debug_items = [ 'CACHE' => &$elements, 'PRE-BUBBLING CACHE' => &$pre_bubbling_elements, ]; $prefix = "<!-- START RENDERER -->"; $prefix .= "\n<!-- CACHE-HIT: " . ($is_cache_hit ? 'Yes' : 'No') . " -->"; foreach ($debug_items as $name_prefix => $debug_item) { if (!empty($debug_item['#cache']['tags'])) { $prefix .= "\n<!-- " . $name_prefix . " TAGS:"; foreach ($debug_item['#cache']['tags'] as $tag) { $prefix .= "\n * " . $tag; } $prefix .= "\n-->"; } if (!empty($debug_item['#cache']['contexts'])) { $prefix .= "\n<!-- " . $name_prefix . " CONTEXTS:"; foreach ($debug_item['#cache']['contexts'] as $context) { $prefix .= "\n * " . $context; } $prefix .= "\n-->"; } if (!empty($debug_item['#cache']['keys'])) { $prefix .= "\n<!-- " . $name_prefix . " KEYS:"; foreach ($debug_item['#cache']['keys'] as $key) { $prefix .= "\n * " . $key; } $prefix .= "\n-->"; } if (!empty($debug_item['#cache']['max-age'])) { $prefix .= "\n<!-- " . $name_prefix . " MAX-AGE: " . $debug_item['#cache']['max-age'] . " -->"; } } if (!empty($render_time)) { $prefix .= "\n<!-- RENDERING TIME: " . number_format($render_time, 9) . " -->"; } $suffix = "<!-- END RENDERER -->"; $elements['#markup'] = Markup::create("$prefix\n" . $elements['#markup'] . "\n$suffix"); return $elements; } }
core/tests/Drupal/Tests/Core/Render/RendererDebugTest.php 0 → 100644 +87 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Tests\Core\Render; use function preg_replace; /** * @coversDefaultClass \Drupal\Core\Render\Renderer * @group Render */ class RendererDebugTest extends RendererTestBase { /** * {@inheritdoc} */ protected function setUp(): void { $this->rendererConfig['debug'] = TRUE; parent::setUp(); } /** * Test render debug output. */ public function testDebugOutput() { $this->setUpRequest(); $this->setupMemoryCache(); $element = [ '#cache' => [ 'keys' => ['render_cache_test_key'], 'tags' => ['render_cache_test_tag', 'render_cache_test_tag1'], 'max-age' => 10, ], '#markup' => 'Test 1', ]; $markup = $this->renderer->renderRoot($element); $expected = <<<EOF <!-- START RENDERER --> <!-- CACHE-HIT: No --> <!-- CACHE TAGS: * render_cache_test_tag * render_cache_test_tag1 --> <!-- CACHE CONTEXTS: * languages:language_interface * theme --> <!-- CACHE KEYS: * render_cache_test_key --> <!-- CACHE MAX-AGE: 10 --> <!-- PRE-BUBBLING CACHE TAGS: * render_cache_test_tag * render_cache_test_tag1 --> <!-- PRE-BUBBLING CACHE CONTEXTS: * languages:language_interface * theme --> <!-- PRE-BUBBLING CACHE KEYS: * render_cache_test_key --> <!-- PRE-BUBBLING CACHE MAX-AGE: 10 --> <!-- RENDERING TIME: 0.123456789 --> Test 1 <!-- END RENDERER --> EOF; $this->assertSame($expected, preg_replace('/RENDERING TIME: \d{1}.\d{9}/', 'RENDERING TIME: 0.123456789', $markup->__toString())); $element = [ '#cache' => [ 'keys' => ['render_cache_test_key'], 'tags' => ['render_cache_test_tag', 'render_cache_test_tag1'], 'max-age' => 10, ], '#markup' => 'Test 1', ]; $markup = $this->renderer->renderRoot($element); $this->assertStringContainsString('CACHE-HIT: Yes', $markup->__toString()); } }
core/tests/Drupal/Tests/Core/Render/RendererTestBase.php +1 −0 Original line number Diff line number Diff line Loading @@ -109,6 +109,7 @@ abstract class RendererTestBase extends UnitTestCase { 'contexts' => ['session', 'user'], 'tags' => ['current-temperature'], ], 'debug' => FALSE, ]; /** Loading