[DebugBundle] dump() + better Symfony glue

This commit is contained in:
Nicolas Grekas 2014-08-26 21:36:24 +02:00
parent 9dea601234
commit eb98c81754
11 changed files with 599 additions and 66 deletions

View File

@ -87,7 +87,7 @@
],
"files": [
"src/Symfony/Component/Intl/Resources/stubs/functions.php",
"src/Symfony/Bundle/DebugBundle/Resources/functions/debug.php"
"src/Symfony/Bundle/DebugBundle/Resources/functions/dump.php"
]
},
"minimum-stability": "dev",

View File

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Twig\Extension;
use Symfony\Bridge\Twig\TokenParser\DumpTokenParser;
/**
* Provides integration of the dump() function with Twig.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class DumpExtension extends \Twig_Extension
{
public function getTokenParsers()
{
return array(new DumpTokenParser());
}
public function getName()
{
return 'dump';
}
}

View File

@ -0,0 +1,89 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Twig\Node;
/**
* @author Julien Galenski <julien.galenski@gmail.com>
*/
class DumpNode extends \Twig_Node
{
public function __construct(\Twig_NodeInterface $values = null, $lineno, $tag = null)
{
parent::__construct(array('values' => $values), array(), $lineno, $tag);
}
/**
* {@inheritdoc}
*/
public function compile(\Twig_Compiler $compiler)
{
$compiler
->write("if (\$this->env->isDebug()) {\n")
->indent()
;
$values = $this->getNode('values');
if (null === $values) {
// remove embedded templates (macros) from the context
$compiler
->write("\$vars = array();\n")
->write("foreach (\$context as \$key => \$value) {\n")
->indent()
->write("if (!\$value instanceof Twig_Template) {\n")
->indent()
->write("\$vars[\$key] = \$value;\n")
->outdent()
->write("}\n")
->outdent()
->write("}\n")
->addDebugInfo($this)
->write('\Symfony\Component\Debug\Debug::dump($vars);'."\n")
;
} elseif (1 === $values->count()) {
$compiler
->addDebugInfo($this)
->write('\Symfony\Component\Debug\Debug::dump(')
->subcompile($values->getNode(0))
->raw(");\n")
;
} else {
$compiler
->addDebugInfo($this)
->write('\Symfony\Component\Debug\Debug::dump(array(')
->indent()
;
foreach ($values as $node) {
$compiler->addIndentation();
if ($node->hasAttribute('name')) {
$compiler
->string($node->getAttribute('name'))
->raw(' => ')
;
}
$compiler
->subcompile($node)
->raw(",\n")
;
}
$compiler
->outdent()
->raw("));\n")
;
}
$compiler
->outdent()
->write("}\n")
;
}
}

View File

@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Twig\TokenParser;
use Symfony\Bridge\Twig\Node\DumpNode;
/**
* Token Parser for the 'dump' tag.
*
* Dump variables with:
* <pre>
* {% dump %}
* {% dump foo %}
* {% dump foo, bar %}
* </pre>
*
* @author Julien Galenski <julien.galenski@gmail.com>
*/
class DumpTokenParser extends \Twig_TokenParser
{
/**
* {@inheritdoc}
*/
public function parse(\Twig_Token $token)
{
$values = null;
if (!$this->parser->getStream()->test(\Twig_Token::BLOCK_END_TYPE)) {
$values = $this->parser->getExpressionParser()->parseMultitargetExpression();
}
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
return new DumpNode($values, $token->getLine(), $this->getTag());
}
/**
* {@inheritdoc}
*/
public function getTag()
{
return 'dump';
}
}

View File

@ -30,6 +30,7 @@
"symfony/security": "~2.4",
"symfony/stopwatch": "~2.2",
"symfony/console": "~2.2",
"symfony/var-dumper": "~2.6",
"symfony/expression-language": "~2.4"
},
"suggest": {
@ -41,6 +42,7 @@
"symfony/yaml": "For using the YamlExtension",
"symfony/security": "For using the SecurityExtension",
"symfony/stopwatch": "For using the StopwatchExtension",
"symfony/var-dumper": "For using the DumpExtension",
"symfony/expression-language": "For using the ExpressionExtension"
},
"autoload": {

View File

@ -11,6 +11,11 @@
namespace Symfony\Component\Debug;
use Symfony\Component\VarDumper\Cloner\ExtCloner;
use Symfony\Component\VarDumper\Cloner\PhpCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
/**
* Registers all the debug tools.
*
@ -19,6 +24,7 @@ namespace Symfony\Component\Debug;
class Debug
{
private static $enabled = false;
private static $dumpHandler;
/**
* Enables the debug tools.
@ -59,4 +65,42 @@ class Debug
DebugClassLoader::enable();
}
public static function dump($var)
{
if (null === self::$dumpHandler) {
$cloner = extension_loaded('symfony_debug') ? new ExtCloner() : new PhpCloner();
$dumper = 'cli' === PHP_SAPI ? new CliDumper() : new HtmlDumper();
self::$dumpHandler = function ($var) use ($cloner, $dumper) {
$dumper->dump($cloner->cloneVar($var));
};
}
$h = self::$dumpHandler;
if (is_array($h)) {
return $h[0]->{$h[1]}($var);
}
return $h($var);
}
public static function setDumpHandler($callable)
{
if (!is_callable($callable)) {
throw new \InvalidArgumentException('Invalid PHP callback.');
}
$prevHandler = self::$dumpHandler;
if (is_array($callable)) {
if (!is_object($callable[0])) {
self::$dumpHandler = $callable[0].'::'.$callable[1];
}
} else {
self::$dumpHandler = $callable;
}
return $prevHandler;
}
}

View File

@ -20,10 +20,12 @@
"psr/log": "~1.0"
},
"require-dev": {
"symfony/var-dumper": "~2.6",
"symfony/http-kernel": "~2.1",
"symfony/http-foundation": "~2.1"
},
"suggest": {
"symfony/var-dumper": "For using Debug::dump()",
"symfony/http-foundation": "",
"symfony/http-kernel": ""
},

View File

@ -0,0 +1,252 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Dumper\JsonDumper;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use Symfony\Component\VarDumper\Dumper\DataDumperInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class DumpDataCollector extends DataCollector implements DataDumperInterface
{
private $stopwatch;
private $isCollected = true;
private $clonesRoot;
private $clonesCount = 0;
public function __construct(Stopwatch $stopwatch = null)
{
$this->stopwatch = $stopwatch;
$this->clonesRoot = $this;
}
public function __clone()
{
$this->data = array();
$this->clonesRoot->clonesCount++;
}
public function dump(Data $data)
{
if ($this->stopwatch) {
$this->stopwatch->start('dump');
}
if ($this->clonesRoot->isCollected) {
$this->clonesRoot->isCollected = false;
register_shutdown_function(array($this->clonesRoot, 'flushDumps'));
}
$trace = PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS : true;
if (PHP_VERSION_ID >= 50400) {
$trace = debug_backtrace($trace, 6);
} else {
$trace = debug_backtrace($trace);
}
$file = $trace[0]['file'];
$line = $trace[0]['line'];
$name = false;
$fileExcerpt = false;
for ($i = 1; $i < 6; ++$i) {
if (isset($trace[$i]['class'], $trace[$i]['function'])
&& 'dump' === $trace[$i]['function']
&& 'Symfony\Bundle\DebugBundle\DebugBundle' === $trace[$i]['class']
) {
$file = $trace[$i]['file'];
$line = $trace[$i]['line'];
while (++$i < 6) {
if (isset($trace[$i]['function']) && empty($trace[$i]['class'])) {
$file = $trace[$i]['file'];
$line = $trace[$i]['line'];
break;
} elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof \Twig_Template) {
$info = $trace[$i]['object'];
$name = $info->getTemplateName();
$src = $info->getEnvironment()->getLoader()->getSource($name);
$info = $info->getDebugInfo();
if (isset($info[$trace[$i-1]['line']])) {
$file = false;
$line = $info[$trace[$i-1]['line']];
$src = explode("\n", $src);
$fileExcerpt = array();
for ($i = max($line - 3, 1), $max = min($line + 3, count($src)); $i <= $max; ++$i) {
$fileExcerpt[] = '<li'.($i === $line ? ' class="selected"' : '').'><code>'.htmlspecialchars($src[$i - 1]).'</code></li>';
}
$fileExcerpt = '<ol start="'.max($line - 3, 1).'">'.implode("\n", $fileExcerpt).'</ol>';
}
break;
}
}
break;
}
}
if (false === $name) {
$name = strtr($file, '\\', '/');
$name = substr($file, strrpos($file, '/') + 1);
}
$this->clonesRoot->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt');
if ($this->stopwatch) {
$this->stopwatch->stop('dump');
}
}
public function collect(Request $request, Response $response, \Exception $exception = null)
{
}
public function serialize()
{
$ser = serialize($this->clonesRoot->data);
$this->clonesRoot->data = array();
$this->clonesRoot->isCollected = true;
return $ser;
}
public function unserialize($data)
{
parent::unserialize($data);
$this->clonesRoot = $this;
}
public function getDumpsCount()
{
return count($this->clonesRoot->data);
}
public function getDumpsExcerpts()
{
$dumps = array();
foreach ($this->data as $dump) {
$data = $dump['data']->getRawData();
unset($dump['data']);
$data = $data[0][0];
if (isset($data->val)) {
$data = $data->val;
}
if (isset($data->bin)) {
$data = 'b"'.$data->bin.'"';
} elseif (isset($data->str)) {
$data = '"'.$data->str.'"';
} elseif (isset($data->count)) {
$data = 'array('.$data->count.')';
} elseif (isset($data->class)) {
$data = $data->class.'{...}';
} elseif (isset($data->res)) {
$data = 'resource:'.$data->res.'{...}';
} elseif (is_array($data)) {
$data = 'array()';
} elseif (null === $data) {
$data = 'null';
} elseif (false === $data) {
$data = 'false';
} elseif (INF === $data) {
$data = 'INF';
} elseif (-INF === $data) {
$data = '-INF';
} elseif (NAN === $data) {
$data = 'NAN';
} elseif (true === $data) {
$data = 'true';
}
$dump['dataExcerpt'] = $data;
$dumps[] = $dump;
}
return $dumps;
}
public function getDumps($getData = false)
{
if ($getData) {
$dumper = new JsonDumper();
}
$dumps = array();
foreach ($this->clonesRoot->data as $dump) {
$json = '';
if ($getData) {
$dumper->dump($dump['data'], function ($line) use (&$json) {$json .= $line;});
}
$dump['data'] = $json;
$dumps[] = $dump;
}
return $dumps;
}
public function getName()
{
return 'dump';
}
public function flushDumps()
{
if (0 === $this->clonesRoot->clonesCount-- && !$this->clonesRoot->isCollected && $this->clonesRoot->data) {
$this->clonesRoot->clonesCount = 0;
$this->clonesRoot->isCollected = true;
$h = headers_list();
$i = count($h);
array_unshift($h, 'Content-Type: ' . ini_get('default_mimetype'));
while (0 !== stripos($h[$i], 'Content-Type:')) {
--$i;
}
if (stripos($h[$i], 'html')) {
echo '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">';
$dumper = new HtmlDumper();
} else {
$dumper = new CliDumper();
$dumper->setColors(false);
}
foreach ($this->clonesRoot->data as $i => $dump) {
$this->clonesRoot->data[$i] = null;
if ($dumper instanceof HtmlDumper) {
$dump['name'] = htmlspecialchars($dump['name'], ENT_QUOTES, 'UTF-8');
$dump['file'] = htmlspecialchars($dump['file'], ENT_QUOTES, 'UTF-8');
if ('' !== $dump['file']) {
$dump['name'] = "<abbr title=\"{$dump['file']}\">{$dump['name']}</abbr>";
}
echo "\n<br><span class=\"sf-dump-meta\">in {$dump['name']} on line {$dump['line']}:</span>";
} else {
echo "\nin {$dump['name']} on line {$dump['line']}:\n\n";
}
$dumper->dump($dump['data']);
}
$this->clonesRoot->data = array();
}
}
}

View File

@ -0,0 +1,61 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\Debug\Debug;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configures dump() handler.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class DumpListener implements EventSubscriberInterface
{
private $container;
private $dumper;
/**
* @param ContainerInterface $container Service container, for lazy loading.
* @param string $dumper var_dumper dumper service to use.
*/
public function __construct(ContainerInterface $container, $dumper)
{
$this->container = $container;
$this->dumper = $dumper;
}
public function configure()
{
if ($this->container) {
$container = $this->container;
$dumper = $this->dumper;
$this->container = null;
Debug::setDumpHandler(function ($var) use ($container, $dumper) {
$dumper = $container->get($dumper);
$cloner = $container->get('var_dumper.cloner');
$handler = function ($var) use ($dumper, $cloner) {$dumper->dump($cloner->cloneVar($var));};
Debug::setDumpHandler($handler);
$handler($var);
});
}
}
public static function getSubscribedEvents()
{
// Register early to have a working dump() as early as possible
return array(KernelEvents::REQUEST => array('configure', 1024));
}
}

View File

@ -21,7 +21,7 @@ class HtmlDumper extends CliDumper
public static $defaultOutputStream = 'php://output';
protected $dumpHeader;
protected $dumpPrefix = '<pre class=sf-var-debug style=white-space:pre>';
protected $dumpPrefix = '<pre class=sf-dump style=white-space:pre>';
protected $dumpSuffix = '</pre>';
protected $colors = true;
protected $headerIsDumped = false;
@ -88,7 +88,7 @@ class HtmlDumper extends CliDumper
$this->headerIsDumped = true;
$line = $this->line;
$p = 'sf-var-debug';
$p = 'sf-dump';
$this->line = '<!DOCTYPE html><style>';
parent::dumpLine(0);
$this->line .= "a.$p-ref {{$this->styles['ref']}}";
@ -119,9 +119,9 @@ class HtmlDumper extends CliDumper
if ('ref' === $style) {
$ref = substr($val, 1);
if ('#' === $val[0]) {
return "<a class=sf-var-debug-ref name=\"sf-var-debug-ref$ref\">$val</a>";
return "<a class=sf-dump-ref name=\"sf-dump-ref$ref\">$val</a>";
} else {
return "<a class=sf-var-debug-ref href=\"#sf-var-debug-ref$ref\">$val</a>";
return "<a class=sf-dump-ref href=\"#sf-dump-ref$ref\">$val</a>";
}
}
@ -131,12 +131,12 @@ class HtmlDumper extends CliDumper
foreach (static::$controlChars as $c) {
if (false !== strpos($val, $c)) {
$r = "\x7F" === $c ? '?' : chr(64 + ord($c));
$val = str_replace($c, "<span class=sf-var-debug-cchr>$r</span>", $val);
$val = str_replace($c, "<span class=sf-dump-cchr>$r</span>", $val);
}
}
}
return "<span class=sf-var-debug-$style>$val</span>";
return "<span class=sf-dump-$style>$val</span>";
}
/**
@ -150,7 +150,7 @@ class HtmlDumper extends CliDumper
switch ($this->lastDepth - $depth) {
case +1: $this->line = '</span>'.$this->line; break;
case -1: $this->line = "<span class=sf-var-debug-$depth>$this->line"; break;
case -1: $this->line = "<span class=sf-dump-$depth>$this->line"; break;
}
if (-1 === $this->lastDepth) {

View File

@ -38,73 +38,73 @@ class HtmlDumperTest extends \PHPUnit_Framework_TestCase
$this->assertSame(
<<<EOTXT
<!DOCTYPE html><style>
a.sf-var-debug-ref {color:#444444}
span.sf-var-debug-num {font-weight:bold;color:#0087FF}
span.sf-var-debug-const {font-weight:bold;color:#0087FF}
span.sf-var-debug-str {font-weight:bold;color:#00D7FF}
span.sf-var-debug-cchr {font-style: italic}
span.sf-var-debug-note {color:#D7AF00}
span.sf-var-debug-ref {color:#444444}
span.sf-var-debug-public {color:#008700}
span.sf-var-debug-protected {color:#D75F00}
span.sf-var-debug-private {color:#D70000}
span.sf-var-debug-meta {color:#005FFF}
a.sf-dump-ref {color:#444444}
span.sf-dump-num {font-weight:bold;color:#0087FF}
span.sf-dump-const {font-weight:bold;color:#0087FF}
span.sf-dump-str {font-weight:bold;color:#00D7FF}
span.sf-dump-cchr {font-style: italic}
span.sf-dump-note {color:#D7AF00}
span.sf-dump-ref {color:#444444}
span.sf-dump-public {color:#008700}
span.sf-dump-protected {color:#D75F00}
span.sf-dump-private {color:#D70000}
span.sf-dump-meta {color:#005FFF}
</style>
<pre class=sf-var-debug style=white-space:pre><span class=sf-var-debug-0><span class=sf-var-debug-note>array:25</span> [
<span class=sf-var-debug-1>"<span class=sf-var-debug-meta>number</span>" => <span class=sf-var-debug-const>1</span>
<span class=sf-var-debug-meta>0</span> => <span class=sf-var-debug-const>null</span> <a class=sf-var-debug-ref name="sf-var-debug-ref1">#1</a>
"<span class=sf-var-debug-meta>const</span>" => <span class=sf-var-debug-num>1.1</span>
<span class=sf-var-debug-meta>1</span> => <span class=sf-var-debug-const>true</span>
<span class=sf-var-debug-meta>2</span> => <span class=sf-var-debug-const>false</span>
<span class=sf-var-debug-meta>3</span> => <span class=sf-var-debug-num>NAN</span>
<span class=sf-var-debug-meta>4</span> => <span class=sf-var-debug-num>INF</span>
<span class=sf-var-debug-meta>5</span> => <span class=sf-var-debug-num>-INF</span>
<span class=sf-var-debug-meta>6</span> => <span class=sf-var-debug-const>9223372036854775807</span>
"<span class=sf-var-debug-meta>str</span>" => "<span class=sf-var-debug-str>déjà</span>"
<span class=sf-var-debug-meta>7</span> => b"<span class=sf-var-debug-str>é</span>"
"<span class=sf-var-debug-meta>[]</span>" => []
"<span class=sf-var-debug-meta>res</span>" => resource:<span class=sf-var-debug-note>stream</span> {
<span class=sf-var-debug-2><span class=sf-var-debug-meta>wrapper_type</span>: "<span class=sf-var-debug-str>plainfile</span>"
<span class=sf-var-debug-meta>stream_type</span>: "<span class=sf-var-debug-str>dir</span>"
<span class=sf-var-debug-meta>mode</span>: "<span class=sf-var-debug-str>r</span>"
<span class=sf-var-debug-meta>unread_bytes</span>: <span class=sf-var-debug-const>0</span>
<span class=sf-var-debug-meta>seekable</span>: <span class=sf-var-debug-const>true</span>
<span class=sf-var-debug-meta>timed_out</span>: <span class=sf-var-debug-const>false</span>
<span class=sf-var-debug-meta>blocked</span>: <span class=sf-var-debug-const>true</span>
<span class=sf-var-debug-meta>eof</span>: <span class=sf-var-debug-const>false</span>
<span class=sf-var-debug-meta>options</span>: []
<pre class=sf-dump style=white-space:pre><span class=sf-dump-0><span class=sf-dump-note>array:25</span> [
<span class=sf-dump-1>"<span class=sf-dump-meta>number</span>" => <span class=sf-dump-const>1</span>
<span class=sf-dump-meta>0</span> => <span class=sf-dump-const>null</span> <a class=sf-dump-ref name="sf-dump-ref1">#1</a>
"<span class=sf-dump-meta>const</span>" => <span class=sf-dump-num>1.1</span>
<span class=sf-dump-meta>1</span> => <span class=sf-dump-const>true</span>
<span class=sf-dump-meta>2</span> => <span class=sf-dump-const>false</span>
<span class=sf-dump-meta>3</span> => <span class=sf-dump-num>NAN</span>
<span class=sf-dump-meta>4</span> => <span class=sf-dump-num>INF</span>
<span class=sf-dump-meta>5</span> => <span class=sf-dump-num>-INF</span>
<span class=sf-dump-meta>6</span> => <span class=sf-dump-const>9223372036854775807</span>
"<span class=sf-dump-meta>str</span>" => "<span class=sf-dump-str>déjà</span>"
<span class=sf-dump-meta>7</span> => b"<span class=sf-dump-str>é</span>"
"<span class=sf-dump-meta>[]</span>" => []
"<span class=sf-dump-meta>res</span>" => resource:<span class=sf-dump-note>stream</span> {
<span class=sf-dump-2><span class=sf-dump-meta>wrapper_type</span>: "<span class=sf-dump-str>plainfile</span>"
<span class=sf-dump-meta>stream_type</span>: "<span class=sf-dump-str>dir</span>"
<span class=sf-dump-meta>mode</span>: "<span class=sf-dump-str>r</span>"
<span class=sf-dump-meta>unread_bytes</span>: <span class=sf-dump-const>0</span>
<span class=sf-dump-meta>seekable</span>: <span class=sf-dump-const>true</span>
<span class=sf-dump-meta>timed_out</span>: <span class=sf-dump-const>false</span>
<span class=sf-dump-meta>blocked</span>: <span class=sf-dump-const>true</span>
<span class=sf-dump-meta>eof</span>: <span class=sf-dump-const>false</span>
<span class=sf-dump-meta>options</span>: []
</span>}
<span class=sf-var-debug-meta>8</span> => resource:<span class=sf-var-debug-note>Unknown</span> {}
"<span class=sf-var-debug-meta>obj</span>" => <span class=sf-var-debug-note>Symfony\Component\VarDumper\Tests\Fixture\DumbFoo</span> { <a class=sf-var-debug-ref name="sf-var-debug-ref2">#2</a>
<span class=sf-var-debug-2><span class=sf-var-debug-public>foo</span>: "<span class=sf-var-debug-str>foo</span>"
"<span class=sf-var-debug-public>bar</span>": "<span class=sf-var-debug-str>bar</span>"
<span class=sf-dump-meta>8</span> => resource:<span class=sf-dump-note>Unknown</span> {}
"<span class=sf-dump-meta>obj</span>" => <span class=sf-dump-note>Symfony\Component\VarDumper\Tests\Fixture\DumbFoo</span> { <a class=sf-dump-ref name="sf-dump-ref2">#2</a>
<span class=sf-dump-2><span class=sf-dump-public>foo</span>: "<span class=sf-dump-str>foo</span>"
"<span class=sf-dump-public>bar</span>": "<span class=sf-dump-str>bar</span>"
</span>}
"<span class=sf-var-debug-meta>closure</span>" => <span class=sf-var-debug-note>Closure</span> {
<span class=sf-var-debug-2><span class=sf-var-debug-meta>reflection</span>: """
<span class=sf-var-debug-str>Closure [ &lt;user&gt; {$closureLabel} Symfony\Component\VarDumper\Tests\Fixture\{closure} ] {</span>
<span class=sf-var-debug-str> @@ {$var['file']} {$var['line']} - {$var['line']}</span>
"<span class=sf-dump-meta>closure</span>" => <span class=sf-dump-note>Closure</span> {
<span class=sf-dump-2><span class=sf-dump-meta>reflection</span>: """
<span class=sf-dump-str>Closure [ &lt;user&gt; {$closureLabel} Symfony\Component\VarDumper\Tests\Fixture\{closure} ] {</span>
<span class=sf-dump-str> @@ {$var['file']} {$var['line']} - {$var['line']}</span>
<span class=sf-var-debug-str> - Parameters [2] {</span>
<span class=sf-var-debug-str> Parameter #0 [ &lt;required&gt; \$a ]</span>
<span class=sf-var-debug-str> Parameter #1 [ &lt;optional&gt; PDO or NULL &amp;\$b = NULL ]</span>
<span class=sf-var-debug-str> }</span>
<span class=sf-var-debug-str>}</span>
<span class=sf-dump-str> - Parameters [2] {</span>
<span class=sf-dump-str> Parameter #0 [ &lt;required&gt; \$a ]</span>
<span class=sf-dump-str> Parameter #1 [ &lt;optional&gt; PDO or NULL &amp;\$b = NULL ]</span>
<span class=sf-dump-str> }</span>
<span class=sf-dump-str>}</span>
"""
</span>}
"<span class=sf-var-debug-meta>line</span>" => <span class=sf-var-debug-const>{$var['line']}</span>
"<span class=sf-var-debug-meta>nobj</span>" => <span class=sf-var-debug-note>array:1</span> [
<span class=sf-var-debug-2><span class=sf-var-debug-meta>0</span> => {} <a class=sf-var-debug-ref name="sf-var-debug-ref3">#3</a>
"<span class=sf-dump-meta>line</span>" => <span class=sf-dump-const>{$var['line']}</span>
"<span class=sf-dump-meta>nobj</span>" => <span class=sf-dump-note>array:1</span> [
<span class=sf-dump-2><span class=sf-dump-meta>0</span> => {} <a class=sf-dump-ref name="sf-dump-ref3">#3</a>
</span>]
"<span class=sf-var-debug-meta>recurs</span>" => <span class=sf-var-debug-note>array:1</span> [ <a class=sf-var-debug-ref name="sf-var-debug-ref4">#4</a>
<span class=sf-var-debug-2><span class=sf-var-debug-meta>0</span> => <span class=sf-var-debug-note>array:1</span> [<a class=sf-var-debug-ref href="#sf-var-debug-ref4">&4</a>]
"<span class=sf-dump-meta>recurs</span>" => <span class=sf-dump-note>array:1</span> [ <a class=sf-dump-ref name="sf-dump-ref4">#4</a>
<span class=sf-dump-2><span class=sf-dump-meta>0</span> => <span class=sf-dump-note>array:1</span> [<a class=sf-dump-ref href="#sf-dump-ref4">&4</a>]
</span>]
<span class=sf-var-debug-meta>9</span> => <span class=sf-var-debug-const>null</span> <a class=sf-var-debug-ref href="#sf-var-debug-ref1">&1</a>
"<span class=sf-var-debug-meta>sobj</span>" => <span class=sf-var-debug-note>Symfony\Component\VarDumper\Tests\Fixture\DumbFoo</span> {<a class=sf-var-debug-ref href="#sf-var-debug-ref2">@2</a>}
"<span class=sf-var-debug-meta>snobj</span>" => {<a class=sf-var-debug-ref href="#sf-var-debug-ref3">&3</a>}
"<span class=sf-var-debug-meta>snobj2</span>" => {<a class=sf-var-debug-ref href="#sf-var-debug-ref3">@3</a>}
"<span class=sf-var-debug-meta>file</span>" => "<span class=sf-var-debug-str>{$var['file']}</span>"
b"<span class=sf-var-debug-meta>bin-key-é</span>" => ""
<span class=sf-dump-meta>9</span> => <span class=sf-dump-const>null</span> <a class=sf-dump-ref href="#sf-dump-ref1">&1</a>
"<span class=sf-dump-meta>sobj</span>" => <span class=sf-dump-note>Symfony\Component\VarDumper\Tests\Fixture\DumbFoo</span> {<a class=sf-dump-ref href="#sf-dump-ref2">@2</a>}
"<span class=sf-dump-meta>snobj</span>" => {<a class=sf-dump-ref href="#sf-dump-ref3">&3</a>}
"<span class=sf-dump-meta>snobj2</span>" => {<a class=sf-dump-ref href="#sf-dump-ref3">@3</a>}
"<span class=sf-dump-meta>file</span>" => "<span class=sf-dump-str>{$var['file']}</span>"
b"<span class=sf-dump-meta>bin-key-é</span>" => ""
</span>]
</pre>