[TwigBridge] add Twig dump() function + tests and fixes

This commit is contained in:
Nicolas Grekas 2014-09-17 10:28:09 +02:00
parent 0f8d30fd89
commit 2e167ba351
19 changed files with 373 additions and 137 deletions

View File

@ -85,10 +85,7 @@
"src/Symfony/Component/HttpFoundation/Resources/stubs", "src/Symfony/Component/HttpFoundation/Resources/stubs",
"src/Symfony/Component/Intl/Resources/stubs" "src/Symfony/Component/Intl/Resources/stubs"
], ],
"files": [ "files": [ "src/Symfony/Component/Intl/Resources/stubs/functions.php" ]
"src/Symfony/Component/Intl/Resources/stubs/functions.php",
"src/Symfony/Component/VarDumper/Resources/functions/dump.php"
]
}, },
"minimum-stability": "dev", "minimum-stability": "dev",
"extra": { "extra": {

View File

@ -12,6 +12,8 @@
namespace Symfony\Bridge\Twig\Extension; namespace Symfony\Bridge\Twig\Extension;
use Symfony\Bridge\Twig\TokenParser\DumpTokenParser; use Symfony\Bridge\Twig\TokenParser\DumpTokenParser;
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
/** /**
* Provides integration of the dump() function with Twig. * Provides integration of the dump() function with Twig.
@ -20,6 +22,18 @@ use Symfony\Bridge\Twig\TokenParser\DumpTokenParser;
*/ */
class DumpExtension extends \Twig_Extension class DumpExtension extends \Twig_Extension
{ {
public function __construct(ClonerInterface $cloner = null)
{
$this->cloner = $cloner;
}
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('dump', array($this, 'dump'), array('is_safe' => array('html'), 'needs_context' => true, 'needs_environment' => true)),
);
}
public function getTokenParsers() public function getTokenParsers()
{ {
return array(new DumpTokenParser()); return array(new DumpTokenParser());
@ -29,4 +43,38 @@ class DumpExtension extends \Twig_Extension
{ {
return 'dump'; return 'dump';
} }
public function dump(\Twig_Environment $env, $context)
{
if (!$env->isDebug() || !$this->cloner) {
return;
}
if (2 === func_num_args()) {
$vars = array();
foreach ($context as $key => $value) {
if (!$value instanceof \Twig_Template) {
$vars[$key] = $value;
}
}
$vars = array($vars);
} else {
$vars = func_get_args();
unset($vars[0], $vars[1]);
}
$html = '';
$dumper = new HtmlDumper(function ($line, $depth) use (&$html) {
if (-1 !== $depth) {
$html .= str_repeat(' ', $depth).$line."\n";
}
});
foreach ($vars as $value) {
$dumper->dump($this->cloner->cloneVar($value));
}
return $html;
}
} }

View File

@ -13,20 +13,22 @@ namespace Symfony\Bridge\Twig\Tests\Extension;
use Symfony\Bridge\Twig\Extension\DumpExtension; use Symfony\Bridge\Twig\Extension\DumpExtension;
use Symfony\Component\VarDumper\VarDumper; use Symfony\Component\VarDumper\VarDumper;
use Symfony\Component\VarDumper\Cloner\PhpCloner;
class DumpExtensionTest extends \PHPUnit_Framework_TestCase class DumpExtensionTest extends \PHPUnit_Framework_TestCase
{ {
/** /**
* @dataProvider getDumpParams * @dataProvider getDumpTags
*/ */
public function testDebugDump($template, $debug, $expectedOutput, $expectedDumped) public function testDumpTag($template, $debug, $expectedOutput, $expectedDumped)
{ {
$extension = new DumpExtension(new PhpCloner());
$twig = new \Twig_Environment(new \Twig_Loader_String(), array( $twig = new \Twig_Environment(new \Twig_Loader_String(), array(
'debug' => $debug, 'debug' => $debug,
'cache' => false, 'cache' => false,
'optimizations' => 0, 'optimizations' => 0,
)); ));
$twig->addExtension(new DumpExtension()); $twig->addExtension($extension);
$dumped = null; $dumped = null;
$exception = null; $exception = null;
@ -46,7 +48,7 @@ class DumpExtensionTest extends \PHPUnit_Framework_TestCase
$this->assertSame($expectedDumped, $dumped); $this->assertSame($expectedDumped, $dumped);
} }
public function getDumpParams() public function getDumpTags()
{ {
return array( return array(
array('A{% dump %}B', true, 'AB', array()), array('A{% dump %}B', true, 'AB', array()),
@ -54,4 +56,50 @@ class DumpExtensionTest extends \PHPUnit_Framework_TestCase
array('A{% dump %}B', false, 'AB', null), array('A{% dump %}B', false, 'AB', null),
); );
} }
/**
* @dataProvider getDumpArgs
*/
public function testDump($context, $args, $expectedOutput, $debug = true)
{
$extension = new DumpExtension(new PhpCloner());
$twig = new \Twig_Environment(new \Twig_Loader_String(), array(
'debug' => $debug,
'cache' => false,
'optimizations' => 0,
));
array_unshift($args, $context);
array_unshift($args, $twig);
$dump = call_user_func_array(array($extension, 'dump'), $args);
if ($debug) {
$this->assertStringStartsWith('<script>', $dump);
$dump = preg_replace('/^.*?<pre/', '<pre', $dump);
}
$this->assertEquals($expectedOutput, $dump);
}
public function getDumpArgs()
{
return array(
array(array(), array(), '', false),
array(array(), array(), "<pre id=sf-dump><span class=sf-dump-0>[]\n</span></pre><script>Sfjs.dump.instrument()</script>\n"),
array(
array(),
array(123, 456),
"<pre id=sf-dump><span class=sf-dump-0><span class=sf-dump-num>123</span>\n</span></pre><script>Sfjs.dump.instrument()</script>\n"
."<pre id=sf-dump><span class=sf-dump-0><span class=sf-dump-num>456</span>\n</span></pre><script>Sfjs.dump.instrument()</script>\n",
),
array(
array('foo' => 'bar'),
array(),
"<pre id=sf-dump><span class=sf-dump-0><span class=sf-dump-note>array:1</span> [<span name=sf-dump-child>\n"
." <span class=sf-dump-1>\"<span class=sf-dump-meta>foo</span>\" => \"<span class=sf-dump-str>bar</span>\"\n"
."</span></span>]\n"
."</span></pre><script>Sfjs.dump.instrument()</script>\n",
),
);
}
} }

View File

@ -17,12 +17,9 @@
<argument type="service" id="debug.stopwatch" /> <argument type="service" id="debug.stopwatch" />
</service> </service>
<service id="twig.extension.debug" class="Twig_Extension_Debug" public="false">
<tag name="twig.extension" />
</service>
<service id="twig.extension.dump" class="%twig.extension.dump.class%" public="false"> <service id="twig.extension.dump" class="%twig.extension.dump.class%" public="false">
<tag name="twig.extension" /> <tag name="twig.extension" />
<argument type="service" id="var_dumper.cloner" on-invalid="null" />
</service> </service>
</services> </services>
</container> </container>

View File

@ -169,7 +169,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
$dumper->dump( $dumper->dump(
$dump['data']->getLimitedClone($maxDepthLimit, $maxItemsPerDepth), $dump['data']->getLimitedClone($maxDepthLimit, $maxItemsPerDepth),
function ($line, $depth) use (&$data) { function ($line, $depth) use (&$data) {
if (false !==$depth) { if (-1 !== $depth) {
$data .= str_repeat(' ', $depth).$line."\n"; $data .= str_repeat(' ', $depth).$line."\n";
} }
} }

View File

@ -34,14 +34,17 @@ class DumpDataCollectorTest extends \PHPUnit_Framework_TestCase
$xDump = array( $xDump = array(
array( array(
'data' => "<!DOCTYPE html><style> pre.sf-dump { background-color: #300a24; white-space: pre; line-height: 1.2em; color: #eee8d5; font-family: monospace, sans-serif; padding: 5px; } .sf-dump span { display: inline; }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-dump><span class=sf-dump-0><span class=sf-dump-num>123</span>\n</pre>\n", 'data' => "<pre id=sf-dump><span class=sf-dump-0><span class=sf-dump-num>123</span>\n</span></pre><script>Sfjs.dump.instrument()</script>\n",
'name' => 'DumpDataCollectorTest.php', 'name' => 'DumpDataCollectorTest.php',
'file' => __FILE__, 'file' => __FILE__,
'line' => $line, 'line' => $line,
'fileExcerpt' => false, 'fileExcerpt' => false,
), ),
); );
$this->assertSame($xDump, $collector->getDumps('html')); $dump = $collector->getDumps('html');
$this->assertTrue(isset($dump[0]['data']));
$dump[0]['data'] = preg_replace('/^.*?<pre/', '<pre', $dump[0]['data']);
$this->assertSame($xDump, $dump);
$this->assertStringStartsWith( $this->assertStringStartsWith(
'a:1:{i:0;a:5:{s:4:"data";O:39:"Symfony\Component\VarDumper\Cloner\Data":3:{s:45:"Symfony\Component\VarDumper\Cloner\Datadata";a:1:{i:0;a:1:{i:0;i:123;}}s:49:"Symfony\Component\VarDumper\Cloner\DatamaxDepth";i:-1;s:57:"Symfony\Component\VarDumper\Cloner\DatamaxItemsPerDepth";i:-1;}s:4:"name";s:25:"DumpDataCollectorTest.php";s:4:"file";s:', 'a:1:{i:0;a:5:{s:4:"data";O:39:"Symfony\Component\VarDumper\Cloner\Data":3:{s:45:"Symfony\Component\VarDumper\Cloner\Datadata";a:1:{i:0;a:1:{i:0;i:123;}}s:49:"Symfony\Component\VarDumper\Cloner\DatamaxDepth";i:-1;s:57:"Symfony\Component\VarDumper\Cloner\DatamaxItemsPerDepth";i:-1;}s:4:"name";s:25:"DumpDataCollectorTest.php";s:4:"file";s:',

View File

@ -29,8 +29,9 @@ class ReflectionCaster
public static function castClosure(\Closure $c, array $a, Stub $stub, $isNested) public static function castClosure(\Closure $c, array $a, Stub $stub, $isNested)
{ {
$stub->class = 'Closure'; // HHVM generates unique class names for closures
$a = static::castReflector(new \ReflectionFunction($c), $a, $stub, $isNested); $a = static::castReflector(new \ReflectionFunction($c), $a, $stub, $isNested);
unset($a["\0+\0000"], $a['name']); unset($a["\0+\0000"], $a['name'], $a["\0+\0this"], $a["\0+\0parameter"]);
return $a; return $a;
} }

View File

@ -28,7 +28,7 @@ class ResourceCaster
public static function castDba($dba, array $a, Stub $stub, $isNested) public static function castDba($dba, array $a, Stub $stub, $isNested)
{ {
$list = dba_list(); $list = dba_list();
$a['file'] = $list[substr((string) $dba, 13)]; $a['file'] = $list[(int) $dba];
return $a; return $a;
} }

View File

@ -21,64 +21,64 @@ use Symfony\Component\VarDumper\Exception\ThrowingCasterException;
abstract class AbstractCloner implements ClonerInterface abstract class AbstractCloner implements ClonerInterface
{ {
public static $defaultCasters = array( public static $defaultCasters = array(
'o:Symfony\Component\VarDumper\Caster\CasterStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castStub', 'Symfony\Component\VarDumper\Caster\CasterStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castStub',
'o:Closure' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castClosure', 'Closure' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castClosure',
'o:Reflector' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castReflector', 'Reflector' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castReflector',
'o:Doctrine\Common\Persistence\ObjectManager' => 'Symfony\Component\VarDumper\Caster\StubCaster::castNestedFat', 'Doctrine\Common\Persistence\ObjectManager' => 'Symfony\Component\VarDumper\Caster\StubCaster::castNestedFat',
'o:Doctrine\Common\Proxy\Proxy' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castCommonProxy', 'Doctrine\Common\Proxy\Proxy' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castCommonProxy',
'o:Doctrine\ORM\Proxy\Proxy' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castOrmProxy', 'Doctrine\ORM\Proxy\Proxy' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castOrmProxy',
'o:Doctrine\ORM\PersistentCollection' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castPersistentCollection', 'Doctrine\ORM\PersistentCollection' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castPersistentCollection',
'o:DOMException' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castException', 'DOMException' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castException',
'o:DOMStringList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength', 'DOMStringList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
'o:DOMNameList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength', 'DOMNameList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
'o:DOMImplementation' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castImplementation', 'DOMImplementation' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castImplementation',
'o:DOMImplementationList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength', 'DOMImplementationList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
'o:DOMNode' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNode', 'DOMNode' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNode',
'o:DOMNameSpaceNode' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNameSpaceNode', 'DOMNameSpaceNode' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNameSpaceNode',
'o:DOMDocument' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDocument', 'DOMDocument' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDocument',
'o:DOMNodeList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength', 'DOMNodeList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
'o:DOMNamedNodeMap' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength', 'DOMNamedNodeMap' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
'o:DOMCharacterData' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castCharacterData', 'DOMCharacterData' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castCharacterData',
'o:DOMAttr' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castAttr', 'DOMAttr' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castAttr',
'o:DOMElement' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castElement', 'DOMElement' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castElement',
'o:DOMText' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castText', 'DOMText' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castText',
'o:DOMTypeinfo' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castTypeinfo', 'DOMTypeinfo' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castTypeinfo',
'o:DOMDomError' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDomError', 'DOMDomError' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDomError',
'o:DOMLocator' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLocator', 'DOMLocator' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLocator',
'o:DOMDocumentType' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDocumentType', 'DOMDocumentType' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDocumentType',
'o:DOMNotation' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNotation', 'DOMNotation' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNotation',
'o:DOMEntity' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castEntity', 'DOMEntity' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castEntity',
'o:DOMProcessingInstruction' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castProcessingInstruction', 'DOMProcessingInstruction' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castProcessingInstruction',
'o:DOMXPath' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castXPath', 'DOMXPath' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castXPath',
'o:ErrorException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castErrorException', 'ErrorException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castErrorException',
'o:Exception' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castException', 'Exception' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castException',
'o:Symfony\Component\DependencyInjection\ContainerInterface' 'Symfony\Component\DependencyInjection\ContainerInterface'
=> 'Symfony\Component\VarDumper\Caster\StubCaster::castNestedFat', => 'Symfony\Component\VarDumper\Caster\StubCaster::castNestedFat',
'o:Symfony\Component\VarDumper\Exception\ThrowingCasterException' 'Symfony\Component\VarDumper\Exception\ThrowingCasterException'
=> 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castThrowingCasterException', => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castThrowingCasterException',
'o:PDO' => 'Symfony\Component\VarDumper\Caster\PdoCaster::castPdo', 'PDO' => 'Symfony\Component\VarDumper\Caster\PdoCaster::castPdo',
'o:PDOStatement' => 'Symfony\Component\VarDumper\Caster\PdoCaster::castPdoStatement', 'PDOStatement' => 'Symfony\Component\VarDumper\Caster\PdoCaster::castPdoStatement',
'o:ArrayObject' => 'Symfony\Component\VarDumper\Caster\SplCaster::castArrayObject', 'ArrayObject' => 'Symfony\Component\VarDumper\Caster\SplCaster::castArrayObject',
'o:SplDoublyLinkedList' => 'Symfony\Component\VarDumper\Caster\SplCaster::castDoublyLinkedList', 'SplDoublyLinkedList' => 'Symfony\Component\VarDumper\Caster\SplCaster::castDoublyLinkedList',
'o:SplFixedArray' => 'Symfony\Component\VarDumper\Caster\SplCaster::castFixedArray', 'SplFixedArray' => 'Symfony\Component\VarDumper\Caster\SplCaster::castFixedArray',
'o:SplHeap' => 'Symfony\Component\VarDumper\Caster\SplCaster::castHeap', 'SplHeap' => 'Symfony\Component\VarDumper\Caster\SplCaster::castHeap',
'o:SplObjectStorage' => 'Symfony\Component\VarDumper\Caster\SplCaster::castObjectStorage', 'SplObjectStorage' => 'Symfony\Component\VarDumper\Caster\SplCaster::castObjectStorage',
'o:SplPriorityQueue' => 'Symfony\Component\VarDumper\Caster\SplCaster::castHeap', 'SplPriorityQueue' => 'Symfony\Component\VarDumper\Caster\SplCaster::castHeap',
'r:curl' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castCurl', ':curl' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castCurl',
'r:dba' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba', ':dba' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba',
'r:dba persistent' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba', ':dba persistent' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba',
'r:gd' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castGd', ':gd' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castGd',
'r:mysql link' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castMysqlLink', ':mysql link' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castMysqlLink',
'r:process' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castProcess', ':process' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castProcess',
'r:stream' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStream', ':stream' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStream',
'r:stream-context' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStreamContext', ':stream-context' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStreamContext',
); );
protected $maxItems = 250; protected $maxItems = 250;
@ -107,8 +107,7 @@ abstract class AbstractCloner implements ClonerInterface
* *
* Maps resources or objects types to a callback. * Maps resources or objects types to a callback.
* Types are in the key, with a callable caster for value. * Types are in the key, with a callable caster for value.
* Objects class are to be prefixed with a `o:`, * Resource types are to be prefixed with a `:`,
* resources type are to be prefixed with a `r:`,
* see e.g. static::$defaultCasters. * see e.g. static::$defaultCasters.
* *
* @param callable[] $casters A map of casters. * @param callable[] $casters A map of casters.
@ -193,7 +192,7 @@ abstract class AbstractCloner implements ClonerInterface
$class, $class,
method_exists($class, '__debugInfo'), method_exists($class, '__debugInfo'),
new \ReflectionClass($class), new \ReflectionClass($class),
array_reverse(array($class => $class) + class_parents($class) + class_implements($class) + array('*' => '*')), array_reverse(array($class => $class) + class_parents($class) + class_implements($class)),
); );
$this->classInfo[$class] = $classInfo; $this->classInfo[$class] = $classInfo;
@ -213,7 +212,7 @@ abstract class AbstractCloner implements ClonerInterface
} }
foreach ($classInfo[3] as $p) { foreach ($classInfo[3] as $p) {
if (!empty($this->casters[$p = 'o:'.strtolower($p)])) { if (!empty($this->casters[$p = strtolower($p)])) {
foreach ($this->casters[$p] as $p) { foreach ($this->casters[$p] as $p) {
$a = $this->callCaster($p, $obj, $a, $stub, $isNested); $a = $this->callCaster($p, $obj, $a, $stub, $isNested);
} }
@ -237,8 +236,8 @@ abstract class AbstractCloner implements ClonerInterface
$a = array(); $a = array();
$type = $stub->class; $type = $stub->class;
if (!empty($this->casters['r:'.$type])) { if (!empty($this->casters[':'.$type])) {
foreach ($this->casters['r:'.$type] as $c) { foreach ($this->casters[':'.$type] as $c) {
$a = $this->callCaster($c, $res, $a, $stub, $isNested); $a = $this->callCaster($c, $res, $a, $stub, $isNested);
} }
} }

View File

@ -9,9 +9,7 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Symfony\Component\VarDumper\Dumper; namespace Symfony\Component\VarDumper\Cloner;
use Symfony\Component\VarDumper\Cloner\Stub;
/** /**
* Represents the current state of a dumper while dumping. * Represents the current state of a dumper while dumping.

View File

@ -11,9 +11,6 @@
namespace Symfony\Component\VarDumper\Cloner; namespace Symfony\Component\VarDumper\Cloner;
use Symfony\Component\VarDumper\Dumper\DumperInternalsInterface;
use Symfony\Component\VarDumper\Dumper\Cursor;
/** /**
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*/ */
@ -57,21 +54,21 @@ class Data
} }
/** /**
* Dumps data with a DumperInternalsInterface dumper. * Dumps data with a DumperInterface dumper.
*/ */
public function dump(DumperInternalsInterface $dumper) public function dump(DumperInterface $dumper)
{ {
$refs = array(0); $refs = array(0);
$this->dumpItem($dumper, new Cursor, $refs, $this->data[0][0]); $this->dumpItem($dumper, new Cursor, $refs, $this->data[0][0]);
} }
/** /**
* Breadth-first dumping of items. * Depth-first dumping of items.
* *
* @param DumperInternalsInterface $dumper The dumper being used for dumping. * @param DumperInterface $dumper The dumper being used for dumping.
* @param Cursor $cursor A cursor used for tracking dumper state position. * @param Cursor $cursor A cursor used for tracking dumper state position.
* @param array &$refs A map of all references discovered while dumping. * @param array &$refs A map of all references discovered while dumping.
* @param mixed $item A Stub object or the original value being dumped. * @param mixed $item A Stub object or the original value being dumped.
*/ */
private function dumpItem($dumper, $cursor, &$refs, $item) private function dumpItem($dumper, $cursor, &$refs, $item)
{ {
@ -157,12 +154,12 @@ class Data
/** /**
* Dumps children of hash structures. * Dumps children of hash structures.
* *
* @param DumperInternalsInterface $dumper * @param DumperInterface $dumper
* @param Cursor $parentCursor The cursor of the parent hash. * @param Cursor $parentCursor The cursor of the parent hash.
* @param array &$refs A map of all references discovered while dumping. * @param array &$refs A map of all references discovered while dumping.
* @param array $children The children to dump. * @param array $children The children to dump.
* @param int $hashCut The number of items removed from the original hash. * @param int $hashCut The number of items removed from the original hash.
* @param string $hashType A Cursor::HASH_* const. * @param string $hashType A Cursor::HASH_* const.
* *
* @return int The final number of removed items. * @return int The final number of removed items.
*/ */

View File

@ -9,14 +9,14 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Symfony\Component\VarDumper\Dumper; namespace Symfony\Component\VarDumper\Cloner;
/** /**
* DumperInterface used by Data objects. * DumperInterface used by Data objects.
* *
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*/ */
interface DumperInternalsInterface interface DumperInterface
{ {
/** /**
* Dumps a scalar value. * Dumps a scalar value.

View File

@ -12,13 +12,14 @@
namespace Symfony\Component\VarDumper\Dumper; namespace Symfony\Component\VarDumper\Dumper;
use Symfony\Component\VarDumper\Cloner\Data; use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\DumperInterface;
/** /**
* Abstract mechanism for dumping a Data object. * Abstract mechanism for dumping a Data object.
* *
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*/ */
abstract class AbstractDumper implements DataDumperInterface, DumperInternalsInterface abstract class AbstractDumper implements DataDumperInterface, DumperInterface
{ {
public static $defaultOutputStream = 'php://output'; public static $defaultOutputStream = 'php://output';
@ -87,14 +88,22 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInternalsInt
*/ */
public function dump(Data $data, $lineDumper = null) public function dump(Data $data, $lineDumper = null)
{ {
$this->decimalPoint = (string) 0.5; $exception = null;
$this->decimalPoint = $this->decimalPoint[1];
$dumper = clone $this;
if ($lineDumper) { if ($lineDumper) {
$dumper->setLineDumper($lineDumper); $prevLineDumper = $this->setLineDumper($lineDumper);
}
try {
$data->dump($this);
$this->dumpLine(-1);
} catch (\Exception $exception) {
// Re-thrown below
}
if ($lineDumper) {
$this->setLineDumper($prevLineDumper);
}
if (null !== $exception) {
throw $exception;
} }
$data->dump($dumper);
$dumper->dumpLine(false);
} }
/** /**
@ -116,7 +125,7 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInternalsInt
*/ */
protected function echoLine($line, $depth) protected function echoLine($line, $depth)
{ {
if (false !== $depth) { if (-1 !== $depth) {
fwrite($this->outputStream, str_repeat($this->indentPad, $depth).$line."\n"); fwrite($this->outputStream, str_repeat($this->indentPad, $depth).$line."\n");
} }
} }

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\VarDumper\Dumper; namespace Symfony\Component\VarDumper\Dumper;
use Symfony\Component\VarDumper\Cloner\Data; use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\Cursor;
/** /**
* CliDumper dumps variables for command line output. * CliDumper dumps variables for command line output.

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\VarDumper\Dumper; namespace Symfony\Component\VarDumper\Dumper;
use Symfony\Component\VarDumper\Cloner\Cursor;
/** /**
* HtmlDumper dumps variables as HTML. * HtmlDumper dumps variables as HTML.
* *
@ -21,8 +23,8 @@ class HtmlDumper extends CliDumper
public static $defaultOutputStream = 'php://output'; public static $defaultOutputStream = 'php://output';
protected $dumpHeader; protected $dumpHeader;
protected $dumpPrefix = '<pre class=sf-dump>'; protected $dumpPrefix = '<pre id=sf-dump>';
protected $dumpSuffix = '</pre>'; protected $dumpSuffix = '</pre><script>Sfjs.dump.instrument()</script>';
protected $colors = true; protected $colors = true;
protected $headerIsDumped = false; protected $headerIsDumped = false;
protected $lastDepth = -1; protected $lastDepth = -1;
@ -74,7 +76,7 @@ class HtmlDumper extends CliDumper
* @param string $prefix The prepended HTML string. * @param string $prefix The prepended HTML string.
* @param string $suffix The appended HTML string. * @param string $suffix The appended HTML string.
*/ */
public function setDumpBoudaries($prefix, $suffix) public function setDumpBoundaries($prefix, $suffix)
{ {
$this->dumpPrefix = $prefix; $this->dumpPrefix = $prefix;
$this->dumpSuffix = $suffix; $this->dumpSuffix = $suffix;
@ -87,28 +89,104 @@ class HtmlDumper extends CliDumper
{ {
$this->headerIsDumped = true; $this->headerIsDumped = true;
$p = 'sf-dump'; if (null !== $this->dumpHeader) {
$line = <<<EOHTML return $this->dumpHeader;
<!DOCTYPE html><style> }
pre.sf-dump {
$line = <<<'EOHTML'
<script>
Sfjs = window.Sfjs || {};
Sfjs.dump = Sfjs.dump || {};
Sfjs.dump.childElts = document.getElementsByName('sf-dump-child');
Sfjs.dump.childLen = 0;
Sfjs.dump.instrument = function () {
var elt,
i = this.childLen,
aCompact = '▶</a><span class="sf-dump-compact">',
aExpanded = '▼</a><span class="sf-dump-expanded">';
this.childLen= this.childElts.length;
while (i < this.childLen) {
elt = this.childElts[i];
if ("" == elt.className) {
elt.className = "sf-dump-child";
elt.innerHTML = '<a class=sf-dump-ref onclick="Sfjs.dump.toggle(this)">'+('sf-dump-0' == elt.parentNode.className ? aExpanded : aCompact)+elt.innerHTML+'</span>';
}
++i;
}
};
Sfjs.dump.toggle = function(a) {
var s = a.nextElementSibling;
if ('sf-dump-compact' == s.className) {
a.innerHTML = '▼';
s.className = 'sf-dump-expanded';
} else {
a.innerHTML = '▶';
s.className = 'sf-dump-compact';
}
};
</script>
<style>
#sf-dump {
display: block;
background-color: #300a24; background-color: #300a24;
white-space: pre; white-space: pre;
line-height: 1.2em; line-height: 1.2em;
color: #eee8d5; color: #eee8d5;
font-family: monospace, sans-serif; font: 12px monospace, sans-serif;
padding: 5px; padding: 5px;
} }
.sf-dump span { #sf-dump span {
display: inline; display: inline;
} }
#sf-dump .sf-dump-compact {
display: none;
}
#sf-dump abbr {
text-decoration: none;
border: none;
cursor: help;
}
#sf-dump a {
text-decoration: none;
cursor: pointer;
}
#sf-dump a:hover {
text-decoration: underline;
}
EOHTML; EOHTML;
$line .= "a.$p-ref {{$this->styles['ref']}}";
foreach ($this->styles as $class => $style) { foreach ($this->styles as $class => $style) {
$line .= "span.$p-$class {{$style}}"; $line .= "#sf-dump .sf-dump-$class {{$style}}";
} }
return preg_replace('/\s+/', ' ', $line).'</style>'.$this->dumpHeader; return $this->dumpHeader = preg_replace('/\s+/', ' ', $line).'</style>'.$this->dumpHeader;
}
/**
* {@inheritdoc}
*/
protected function enterHash(Cursor $cursor, $prefix, $hasChild)
{
if ($hasChild) {
$prefix .= '<span name=sf-dump-child>';
}
return parent::enterHash($cursor, $prefix, $hasChild);
}
/**
* {@inheritdoc}
*/
protected function leaveHash(Cursor $cursor, $suffix, $hasChild, $cut)
{
if ($hasChild) {
$suffix = '</span>'.$suffix;
}
return parent::leaveHash($cursor, $suffix, $hasChild, $cut);
} }
/** /**
@ -138,6 +216,10 @@ EOHTML;
$val = str_replace($c, "<span class=sf-dump-cchr>$r</span>", $val); $val = str_replace($c, "<span class=sf-dump-cchr>$r</span>", $val);
} }
} }
} elseif ('note' === $style) {
if (false !== $c = strrpos($val, '\\')) {
$val = sprintf('<abbr title="%s" class=sf-dump-%s>%s</abbr>', $val, $style, substr($val, $c+1));
}
} }
return "<span class=sf-dump-$style>$val</span>"; return "<span class=sf-dump-$style>$val</span>";
@ -148,7 +230,6 @@ EOHTML;
*/ */
protected function dumpLine($depth) protected function dumpLine($depth)
{ {
switch ($this->lastDepth - $depth) { switch ($this->lastDepth - $depth) {
case +1: $this->line = '</span>'.$this->line; break; case +1: $this->line = '</span>'.$this->line; break;
case -1: $this->line = "<span class=sf-dump-$depth>$this->line"; break; case -1: $this->line = "<span class=sf-dump-$depth>$this->line"; break;
@ -161,13 +242,11 @@ EOHTML;
$this->line = $this->getDumpHeader().$this->line; $this->line = $this->getDumpHeader().$this->line;
} }
if (false === $depth) { if (-1 === $depth) {
$this->lastDepth = -1;
$this->line .= $this->dumpSuffix; $this->line .= $this->dumpSuffix;
parent::dumpLine(0); parent::dumpLine(0);
} else {
$this->lastDepth = $depth;
} }
$this->lastDepth = $depth;
parent::dumpLine($depth); parent::dumpLine($depth);
} }

View File

@ -1,10 +1,10 @@
Symfony mechanim for exploring and dumping PHP variables Symfony mechanism for exploring and dumping PHP variables
======================================================== =========================================================
This component provides a mechanism that allows exploring then dumping This component provides a mechanism that allows exploring then dumping
any PHP variable. any PHP variable.
It handles scalar, objects and resources properly, taking hard and soft It handles scalars, objects and resources properly, taking hard and soft
references into account. More than being immune to inifinite recursion references into account. More than being immune to inifinite recursion
problems, it allows dumping where references link to each other. problems, it allows dumping where references link to each other.
It explores recursive structures using a breadth-first algorithm. It explores recursive structures using a breadth-first algorithm.

View File

@ -26,6 +26,13 @@ class CliDumperTest extends \PHPUnit_Framework_TestCase
$dumper = new CliDumper('php://output'); $dumper = new CliDumper('php://output');
$dumper->setColors(false); $dumper->setColors(false);
$cloner = new PhpCloner(); $cloner = new PhpCloner();
$cloner->addCasters(array(
':stream' => function ($res, $a) {
unset($a['uri']);
return $a;
}
));
$data = $cloner->cloneVar($var); $data = $cloner->cloneVar($var);
ob_start(); ob_start();
@ -51,7 +58,7 @@ array:25 [
"[]" => [] "[]" => []
"res" => resource:stream { "res" => resource:stream {
wrapper_type: "plainfile" wrapper_type: "plainfile"
stream_type: "dir" stream_type: "STDIO"
mode: "r" mode: "r"
unread_bytes: 0 unread_bytes: 0
seekable: true seekable: true

View File

@ -0,0 +1,43 @@
<?php
namespace Symfony\Component\VarDumper\Tests\Fixture;
if (!class_exists('Symfony\Component\VarDumper\Tests\Fixture\DumbFoo')) {
class DumbFoo
{
public $foo = 'foo';
}
}
$foo = new DumbFoo();
$foo->bar = 'bar';
$g = fopen(__FILE__, 'r');
$h = fopen(__FILE__, 'r');
fclose($h);
$var = array(
'number' => 1, null,
'const' => 1.1, true, false, NAN, INF, -INF, PHP_INT_MAX,
'str' => "déjà", "\xE9",
'[]' => array(),
'res' => $g,
$h,
'obj' => $foo,
'closure' => function ($a, \PDO &$b = null) {},
'line' => __LINE__ - 1,
'nobj' => array((object) array()),
);
$r = array();
$r[] =& $r;
$var['recurs'] =& $r;
$var[] =& $var[0];
$var['sobj'] = $var['obj'];
$var['snobj'] =& $var['nobj'][0];
$var['snobj2'] = $var['nobj'][0];
$var['file'] = __FILE__;
$var["bin-key-\xE9"] = "";
unset($g, $h, $r);

View File

@ -25,7 +25,16 @@ class HtmlDumperTest extends \PHPUnit_Framework_TestCase
$dumper = new HtmlDumper('php://output'); $dumper = new HtmlDumper('php://output');
$dumper->setColors(false); $dumper->setColors(false);
$dumper->setDumpHeader('<foo></foo>');
$dumper->setDumpBoundaries('<bar>', '</bar>');
$cloner = new PhpCloner(); $cloner = new PhpCloner();
$cloner->addCasters(array(
':stream' => function ($res, $a) {
unset($a['uri']);
return $a;
}
));
$data = $cloner->cloneVar($var); $data = $cloner->cloneVar($var);
ob_start(); ob_start();
@ -37,7 +46,7 @@ class HtmlDumperTest extends \PHPUnit_Framework_TestCase
$this->assertSame( $this->assertSame(
<<<EOTXT <<<EOTXT
<!DOCTYPE html><style> pre.sf-dump { background-color: #300a24; white-space: pre; line-height: 1.2em; color: #eee8d5; font-family: monospace, sans-serif; padding: 5px; } .sf-dump span { display: inline; }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-dump><span class=sf-dump-0><span class=sf-dump-note>array:25</span> [ <foo></foo><bar><span class=sf-dump-0><span class=sf-dump-note>array:25</span> [<span name=sf-dump-child>
<span class=sf-dump-1>"<span class=sf-dump-meta>number</span>" => <span class=sf-dump-num>1</span> <span class=sf-dump-1>"<span class=sf-dump-meta>number</span>" => <span class=sf-dump-num>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>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>const</span>" => <span class=sf-dump-num>1.1</span>
@ -50,9 +59,9 @@ class HtmlDumperTest extends \PHPUnit_Framework_TestCase
"<span class=sf-dump-meta>str</span>" => "<span class=sf-dump-str>déjà</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>7</span> => b"<span class=sf-dump-str>é</span>"
"<span class=sf-dump-meta>[]</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-meta>res</span>" => resource:<span class=sf-dump-note>stream</span> {<span name=sf-dump-child>
<span class=sf-dump-2><span class=sf-dump-meta>wrapper_type</span>: "<span class=sf-dump-str>plainfile</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>stream_type</span>: "<span class=sf-dump-str>STDIO</span>"
<span class=sf-dump-meta>mode</span>: "<span class=sf-dump-str>r</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-num>0</span> <span class=sf-dump-meta>unread_bytes</span>: <span class=sf-dump-num>0</span>
<span class=sf-dump-meta>seekable</span>: <span class=sf-dump-const>true</span> <span class=sf-dump-meta>seekable</span>: <span class=sf-dump-const>true</span>
@ -60,13 +69,13 @@ class HtmlDumperTest extends \PHPUnit_Framework_TestCase
<span class=sf-dump-meta>blocked</span>: <span class=sf-dump-const>true</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>eof</span>: <span class=sf-dump-const>false</span>
<span class=sf-dump-meta>options</span>: [] <span class=sf-dump-meta>options</span>: []
</span>} </span></span>}
<span class=sf-dump-meta>8</span> => resource:<span class=sf-dump-note>Unknown</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-meta>obj</span>" => <span class=sf-dump-note><abbr title="Symfony\Component\VarDumper\Tests\Fixture\DumbFoo" class=sf-dump-note>DumbFoo</abbr></span> {<span name=sf-dump-child> <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-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 class=sf-dump-public>bar</span>": "<span class=sf-dump-str>bar</span>"
</span>} </span></span>}
"<span class=sf-dump-meta>closure</span>" => <span class=sf-dump-note>Closure</span> { "<span class=sf-dump-meta>closure</span>" => <span class=sf-dump-note>Closure</span> {<span name=sf-dump-child>
<span class=sf-dump-2><span class=sf-dump-meta>reflection</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>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-dump-str> @@ {$var['file']} {$var['line']} - {$var['line']}</span>
@ -77,22 +86,22 @@ class HtmlDumperTest extends \PHPUnit_Framework_TestCase
<span class=sf-dump-str> }</span> <span class=sf-dump-str> }</span>
<span class=sf-dump-str>}</span> <span class=sf-dump-str>}</span>
""" """
</span>} </span></span>}
"<span class=sf-dump-meta>line</span>" => <span class=sf-dump-num>{$var['line']}</span> "<span class=sf-dump-meta>line</span>" => <span class=sf-dump-num>{$var['line']}</span>
"<span class=sf-dump-meta>nobj</span>" => <span class=sf-dump-note>array:1</span> [ "<span class=sf-dump-meta>nobj</span>" => <span class=sf-dump-note>array:1</span> [<span name=sf-dump-child>
<span class=sf-dump-2><span class=sf-dump-meta>0</span> => {} <a class=sf-dump-ref name="sf-dump-ref3">#3</a> <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></span>]
"<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-meta>recurs</span>" => <span class=sf-dump-note>array:1</span> [<span name=sf-dump-child> <a class=sf-dump-ref name="sf-dump-ref4">#4</a>
<span class=sf-dump-2><span class=sf-dump-meta>0</span> => <a class=sf-dump-ref href="#sf-dump-ref4">&4</a> <span class=sf-dump-note>array:1</span> [<a class=sf-dump-ref href="#sf-dump-ref4">@4</a>] <span class=sf-dump-2><span class=sf-dump-meta>0</span> => <a class=sf-dump-ref href="#sf-dump-ref4">&4</a> <span class=sf-dump-note>array:1</span> [<a class=sf-dump-ref href="#sf-dump-ref4">@4</a>]
</span>] </span></span>]
<span class=sf-dump-meta>9</span> => <a class=sf-dump-ref href="#sf-dump-ref1">&1</a> <span class=sf-dump-const>null</span> <span class=sf-dump-meta>9</span> => <a class=sf-dump-ref href="#sf-dump-ref1">&1</a> <span class=sf-dump-const>null</span>
"<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>sobj</span>" => <span class=sf-dump-note><abbr title="Symfony\Component\VarDumper\Tests\Fixture\DumbFoo" class=sf-dump-note>DumbFoo</abbr></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> {<a class=sf-dump-ref href="#sf-dump-ref3">@3</a>} "<span class=sf-dump-meta>snobj</span>" => <a class=sf-dump-ref href="#sf-dump-ref3">&3</a> {<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>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>" "<span class=sf-dump-meta>file</span>" => "<span class=sf-dump-str>{$var['file']}</span>"
b"<span class=sf-dump-meta>bin-key-é</span>" => "" b"<span class=sf-dump-meta>bin-key-é</span>" => ""
</span>] </span></span>]
</pre> </span></bar>
EOTXT EOTXT
, ,