1
0
mirror of https://github.com/maximebf/php-debugbar.git synced 2025-01-16 13:00:42 +01:00

New debugbar twig extensions (#632)

This commit is contained in:
erikn69 2024-03-20 13:43:14 -05:00 committed by GitHub
parent 3168cb9adf
commit 3adb1e45f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 460 additions and 11 deletions

View File

@ -1,2 +1,7 @@
Hello {{ name }}
{% include "foobar.html" %}
{% measure 'Measure Debugs' %}
{{ debug('Hello ' ~ name) }}
{{ dump({'name' : name}) }}
{% endmeasure %}

View File

@ -8,8 +8,19 @@ $debugbarRenderer->setBaseUrl('../../../src/DebugBar/Resources');
$loader = new Twig\Loader\FilesystemLoader('.');
$twig = new Twig\Environment($loader);
$profile = new Twig\Profiler\Profile();
// enable template measure on timeline
$twig->addExtension(new DebugBar\Bridge\Twig\TimeableTwigExtensionProfiler($profile, $debugbar['time']));
// enable {% measure 'foo' %} {% endmeasure %} tags for time measure on templates
$twig->addExtension(new DebugBar\Bridge\Twig\MeasureTwigExtension($debugbar['time']));
$twig->enableDebug();
// enable {{ dump('foo') }} function on templates
$twig->addExtension(new DebugBar\Bridge\Twig\DumpTwigExtension());
// enable {{ debug('foo') }} function on templates
$twig->addExtension(new DebugBar\Bridge\Twig\DebugTwigExtension($debugbar['messages']));
$debugbar->addCollector(new DebugBar\Bridge\NamespacedTwigProfileCollector($profile, $twig));
render_demo_page(function() use ($twig) {

View File

@ -118,17 +118,6 @@ $env->addExtension(new Twig_Extension_Profiler($profile));
$debugbar->addCollector(new DebugBar\Bridge\TwigProfileCollector($profile));
```
You can optionally use `DebugBar\Bridge\Twig\TimeableTwigExtensionProfiler` in place of
`Twig_Extension_Profiler` so render operation can be measured.
```php
$loader = new Twig_Loader_Filesystem('.');
$env = new Twig_Environment($loader);
$profile = new Twig_Profiler_Profile();
$env->addExtension(new DebugBar\Bridge\Twig\TimeableTwigExtensionProfiler($profile, $debugbar['time']));
$debugbar->addCollector(new DebugBar\Bridge\TwigProfileCollector($profile));
```
### Version 2 and 3
This collector uses the class `Twig\Extension\ProfilerExtension` to collect info about rendered
@ -149,3 +138,49 @@ $env->addExtension(new ProfilerExtension($profile));
$debugbar->addCollector(new NamespacedTwigProfileCollector($profile));
```
### Optional debugbar twig extensions
You can optionally use `DebugBar\Bridge\Twig\TimeableTwigExtensionProfiler` in place of
`Twig\Profiler\Profile` so render operation can be measured.
```php
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Twig\Profiler\Profile;
$loader = new FilesystemLoader('.');
$env = new Environment($loader);
$profile = new Profile();
$env->addExtension(new DebugBar\Bridge\Twig\TimeableTwigExtensionProfiler($profile, $debugbar['time']));
$debugbar->addCollector(new DebugBar\Bridge\TwigProfileCollector($profile));
```
Other optional extensions add functions and tags for debugbar integration into templates.
```php
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Twig\Profiler\Profile;
$loader = new FilesystemLoader('.');
$env = new Environment($loader);
$profile = new Profile();
// enable {% measure 'foo' %} {% endmeasure %} tags for time measure on templates
// this extension adds timeline items to TimeDataCollector
$twig->addExtension(new DebugBar\Bridge\Twig\MeasureTwigExtension($debugbar['time']));
$twig->enableDebug(); // if Twig\Environment debug is disabled, dump/debug are ignored
// enable {{ dump('foo') }} function on templates
// this extension allows dumping data using debugbar DataFormatter
$twig->addExtension(new DebugBar\Bridge\Twig\DumpTwigExtension());
// enable {{ debug('foo') }} function on templates
// this extension allows debugging in MessageCollector
$twig->addExtension(new DebugBar\Bridge\Twig\DebugTwigExtension($debugbar['messages']));
$debugbar->addCollector(new DebugBar\Bridge\TwigProfileCollector($profile));
```

View File

@ -0,0 +1,94 @@
<?php
namespace DebugBar\Bridge\Twig;
use Twig\Environment;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
/**
* Debug messages to debugbar in your Twig templates.
*
* @package DebugBar\Bridge\Twig
*/
class DebugTwigExtension extends AbstractExtension
{
/**
* @var \DebugBar\DataCollector\MessagesCollector|null
*/
protected $messagesCollector;
/**
* @var string
*/
protected $functionName;
/**
*
* @param \DebugBar\DataCollector\MessagesCollector|null $app
* @param string $functionName
*/
public function __construct($messagesCollector, $functionName = 'debug')
{
$this->messagesCollector = $messagesCollector;
$this->functionName = $functionName;
}
/**
* {@inheritDoc}
*/
public function getName()
{
return static::class;
}
/**
* {@inheritDoc}
*/
public function getFunctions()
{
return [
new TwigFunction(
$this->functionName,
[$this, 'debug'],
['needs_context' => true, 'needs_environment' => true]
),
];
}
/**
* Based on Twig_Extension_Debug / twig_var_dump
*
* @param Environment $env
* @param $context
*/
public function debug(Environment $env, $context)
{
if (!$env->isDebug() || !$this->messagesCollector) {
return;
}
$count = func_num_args();
if (2 === $count) {
$data = [];
foreach ($context as $key => $value) {
if (is_object($value)) {
if (method_exists($value, 'toArray')) {
$data[$key] = $value->toArray();
} else {
$data[$key] = "Object (" . get_class($value) . ")";
}
} else {
$data[$key] = $value;
}
}
$this->messagesCollector->addMessage($data, 'debug');
} else {
for ($i = 2; $i < $count; $i++) {
$this->messagesCollector->addMessage(func_get_arg($i), 'debug');
}
}
return;
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace DebugBar\Bridge\Twig;
use DebugBar\DataFormatter\HasDataFormatter;
use Twig\Environment;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
/**
* Dump variables using debugbar DataFormatter
*
* @package DebugBar\Bridge\Twig
*/
class DumpTwigExtension extends AbstractExtension
{
use HasDataFormatter;
/**
* @var string
*/
protected $functionName;
/**
* Create a new auth extension.
*
* @param string $functionName
*/
public function __construct($functionName = 'dump')
{
$this->functionName = $functionName;
}
/**
* {@inheritDoc}
*/
public function getName()
{
return static::class;
}
/**
* {@inheritDoc}
*/
public function getFunctions()
{
return [
new TwigFunction(
$this->functionName,
[$this, 'dump'],
['is_safe' => ['html'], 'needs_context' => true, 'needs_environment' => true]
),
];
}
/**
* Based on Twig_Extension_Debug / twig_var_dump
*
* @param Environment $env
* @param $context
*
* @return string
*/
public function dump(Environment $env, $context)
{
if (!$env->isDebug()) {
return;
}
$output = '';
$count = func_num_args();
if (2 === $count) {
$data = [];
foreach ($context as $key => $value) {
if (is_object($value)) {
if (method_exists($value, 'toArray')) {
$data[$key] = $value->toArray();
} else {
$data[$key] = "Object (" . get_class($value) . ")";
}
} else {
$data[$key] = $value;
}
}
$output .= $this->formatVar($data);
} else {
for ($i = 2; $i < $count; $i++) {
$output .= $this->formatVar(func_get_arg($i));
}
}
if ($this->isHtmlVarDumperUsed()) {
return $output;
}
return '<pre>' . $output . '</pre>';
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace DebugBar\Bridge\Twig;
use Twig\Extension\AbstractExtension;
/**
* Access debugbar timeline measure in your Twig templates.
* Based on Symfony\Bridge\Twig\Extension\StopwatchExtension
*
* @package DebugBar\Bridge\Twig
*/
class MeasureTwigExtension extends AbstractExtension
{
/**
* @var \DebugBar\DataCollector\TimeDataCollector|null
*/
protected $timeCollector;
/**
* @var string
*/
protected $tagName;
/**
* Create a new auth extension.
*
* @param \DebugBar\DataCollector\TimeDataCollector|null $debugbar
* @param string $tagName
*/
public function __construct($timeCollector, $tagName = 'measure')
{
$this->timeCollector = $timeCollector;
$this->tagName = $tagName;
}
/**
* {@inheritDoc}
*/
public function getName()
{
return static::class;
}
/**
* @return \Twig\TokenParser\TokenParserInterface[]
*/
public function getTokenParsers()
{
return [
/*
* {% measure foo %}
* Some stuff which will be recorded on the timeline
* {% endmeasure %}
*/
new MeasureTwigTokenParser(!is_null($this->timeCollector), $this->tagName, $this->getName()),
];
}
public function startMeasure(...$arg)
{
if (!$this->timeCollector) {
return;
}
$this->timeCollector->startMeasure(...$arg);
}
public function stopMeasure(...$arg)
{
if (!$this->timeCollector) {
return;
}
$this->timeCollector->stopMeasure(...$arg);
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace DebugBar\Bridge\Twig;
use DebugBar\Bridge\Twig\MeasureTwigExtension;
use Twig\Compiler;
use Twig\Node\Expression\AssignNameExpression;
use Twig\Node\Node;
/**
* Represents a measure node.
* Based on Symfony\Bridge\Twig\Node\StopwatchNode
*/
class MeasureTwigNode extends Node
{
/**
* @var string
*/
protected $extName;
public function __construct(
Node $name,
$body,
AssignNameExpression $var,
$lineno = 0,
$tag = null,
$extName = null
) {
parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno, $tag);
$this->extName = $extName ?: MeasureTwigExtension::class;
}
public function compile(Compiler $compiler)
{
$compiler
->addDebugInfo($this)
->write('')
->subcompile($this->getNode('var'))
->raw(' = ')
->subcompile($this->getNode('name'))
->write(";\n")
->write("\$this->env->getExtension('".$this->extName."')->startMeasure(")
->subcompile($this->getNode('var'))
->raw(");\n")
->subcompile($this->getNode('body'))
->write("\$this->env->getExtension('".$this->extName."')->stopMeasure(")
->subcompile($this->getNode('var'))
->raw(");\n");
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace DebugBar\Bridge\Twig;
use Twig\Node\Expression\AssignNameExpression;
use Twig\Token;
use Twig\TokenParser\AbstractTokenParser;
/**
* Token Parser for the measure tag.
* Based on Symfony\Bridge\Twig\TokenParser\StopwatchTokenParser;
*/
class MeasureTwigTokenParser extends AbstractTokenParser
{
/**
* @var string
*/
private $extName;
/**
* @var string
*/
private $tagName;
/**
* @var bool
*/
private $enabled;
/**
*
* @param string $tagName
*/
public function __construct($enabled, $tagName, $extName = null)
{
$this->enabled = $enabled;
$this->tagName = $tagName;
$this->extName = $extName;
}
public function parse(Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
// {% measure 'bar' %}
$name = $this->parser->getExpressionParser()->parseExpression();
$stream->expect(Token::BLOCK_END_TYPE);
// {% endmeasure %}
$body = $this->parser->subparse([$this, 'decideMeasureEnd'], true);
$stream->expect(Token::BLOCK_END_TYPE);
if ($this->enabled) {
return new MeasureTwigNode(
$name,
$body,
new AssignNameExpression($this->parser->getVarName(), $token->getLine()),
$lineno,
$this->getTag(),
$this->extName
);
}
return $body;
}
public function getTag()
{
return $this->tagName;
}
public function decideMeasureEnd(Token $token)
{
return $token->test('end'.$this->getTag());
}
}