Issue #3306864 by lauriii, mherchel, alexpott: Integrate Twig with Symfony VarDumper for improved debugging experience

(cherry picked from commit 07994b06fb)
merge-requests/2715/head
Alex Pott 2022-08-31 23:54:21 +01:00
parent bd29f2e041
commit 6425af2663
No known key found for this signature in database
GPG Key ID: BDA67E7EE836E5CE
7 changed files with 133 additions and 4 deletions

View File

@ -1668,12 +1668,18 @@ services:
arguments: ['@renderer', '@url_generator', '@theme.manager', '@date.formatter', '@file_url_generator']
tags:
- { name: twig.extension, priority: 100 }
# @todo Figure out what to do about debugging functions.
# @see https://www.drupal.org/node/1804998
twig.extension.debug:
class: Twig\Extension\DebugExtension
tags:
- { name: twig.extension }
- { name: twig.extension, priority: 50 }
twig.extension.varDumper:
class: Drupal\Core\Template\DebugExtension
tags:
# This extension is loaded after the Twig Debug Extension because for Twig
# Extensions, last extension loaded takes precedent. This allows this
# extension to override the default Twig Debug Extension conditionally
# when Symfony VarDumper is available.
- { name: twig.extension, priority: 25 }
twig.loader:
class: Twig\Loader\ChainLoader
public: false

View File

@ -0,0 +1,68 @@
<?php
namespace Drupal\Core\Template;
use Twig\Environment;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
/**
* A class providing Drupal Twig Debug extension.
*/
final class DebugExtension extends AbstractExtension {
/**
* The Symfony VarDumper class.
*
* Defined as a string because the Symfony VarDumper does not always exist.
*
* @type string
*/
private const SYMFONY_VAR_DUMPER_CLASS = '\Symfony\Component\VarDumper\VarDumper';
/**
* {@inheritdoc}
*/
public function getFunctions(): array {
// Override Twig built in debugger when Symfony VarDumper is available to
// improve developer experience.
// @see \Twig\Extension\DebugExtension
// @see \Symfony\Component\VarDumper\VarDumper
if (class_exists(self::SYMFONY_VAR_DUMPER_CLASS)) {
return [
new TwigFunction('dump', [self::class, 'dump'], ['needs_context' => TRUE, 'needs_environment' => TRUE, 'is_variadic' => TRUE]),
];
}
return [];
}
/**
* Dumps information about variables using Symfony VarDumper.
*
* @param \Twig\Environment $env
* The Twig environment.
* @param array $context
* Variables from the Twig template.
* @param array $variables
* (optional) Variable(s) to dump.
*/
public static function dump(Environment $env, array $context, ...$variables): void {
if (!$env->isDebug()) {
return;
}
if (class_exists(self::SYMFONY_VAR_DUMPER_CLASS)) {
if (func_num_args() === 2) {
call_user_func(self::SYMFONY_VAR_DUMPER_CLASS . '::dump', $context);
}
else {
array_walk($variables, self::SYMFONY_VAR_DUMPER_CLASS . '::dump');
}
}
else {
throw new \LogicException('Could not dump the variable because symfony/var-dumper component is not installed.');
}
}
}

View File

@ -111,4 +111,11 @@ class TwigThemeTestController {
return ['#theme' => 'twig_theme_test_embed_tag'];
}
/**
* Renders for testing drupal_dump function.
*/
public function dump() {
return ['#theme' => 'twig_theme_test_dump'];
}
}

View File

@ -0,0 +1,6 @@
{% set foo = '💩' %}
{% set bar = '🐣' %}
{% set baz = '☄️' %}
{{ dump(foo) }}
{{ dump() }}
{{ dump(foo, baz) }}

View File

@ -77,6 +77,10 @@ function twig_theme_test_theme($existing, $type, $theme, $path) {
'variables' => [],
'template' => 'twig_theme_test.embed_tag',
];
$items['twig_theme_test_dump'] = [
'variables' => [],
'template' => 'twig_theme_test.dump',
];
return $items;
}

View File

@ -76,3 +76,10 @@ twig_theme_test_embed_tag:
_controller: '\Drupal\twig_theme_test\TwigThemeTestController::embedTagRender'
requirements:
_access: 'TRUE'
twig_theme_test_dump:
path: '/twig-theme-test/dump'
defaults:
_controller: '\Drupal\twig_theme_test\TwigThemeTestController::dump'
requirements:
_access: 'TRUE'

View File

@ -17,7 +17,7 @@ class TwigExtensionTest extends BrowserTestBase {
*
* @var array
*/
protected static $modules = ['theme_test', 'twig_extension_test'];
protected static $modules = ['theme_test', 'twig_extension_test', 'twig_theme_test'];
/**
* {@inheritdoc}
@ -92,4 +92,35 @@ class TwigExtensionTest extends BrowserTestBase {
$this->assertSame(0, $extension->renderVar(0.0), 'TwigExtension::renderVar() renders zero correctly when provided as a double.');
}
/**
* Tests the dump function.
*/
public function testDump() {
// Test Twig Debug disabled.
$this->drupalGet('/twig-theme-test/dump');
$this->assertSession()->elementsCount('css', '.sf-dump', 0);
// Test Twig Debug enabled.
$parameters = $this->container->getParameter('twig.config');
$parameters['debug'] = TRUE;
$this->setContainerParameter('twig.config', $parameters);
$this->resetAll();
$this->drupalGet('/twig-theme-test/dump');
$dumps = $this->getSession()->getPage()->findAll('css', '.sf-dump');
$this->assertEquals(4, count($dumps));
// Test dumping single variable.
$this->assertStringContainsString('💩', $dumps[0]->getText());
$this->assertStringNotContainsString('🐣', $dumps[0]->getText());
// Test dumping context.
$this->assertStringContainsString('"bar" => "🐣"', $dumps[1]->getText());
// Test dump as a variadic.
$this->assertStringContainsString('💩', $dumps[2]->getText());
$this->assertStringContainsString('☄️', $dumps[3]->getText());
}
}