feature #16325 [VarDumper] Casters for Generator, ReflectionGenerator and ReflectionType (nicolas-grekas)
This PR was merged into the 2.8 branch.
Discussion
----------
[VarDumper] Casters for Generator, ReflectionGenerator and ReflectionType
| Q | A
| ------------- | ---
| Bug fix? | yes
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | -
| License | MIT
| Doc PR | -
I had this on a local branch since a few days:
- on PHP7, using ReflectionGenerator allows dumping interesting state info about generators (see test case for example output)
- caster for ReflectionType added
- source code excerpts are now left-trimmed (see adjusted test cases)
Commits
-------
d6c2d75
[VarDumper] Casters for Generator, ReflectionGenerator and ReflectionType
This commit is contained in:
commit
973fbb310e
@ -21,6 +21,7 @@ use Symfony\Component\VarDumper\Cloner\Stub;
|
||||
*/
|
||||
class ExceptionCaster
|
||||
{
|
||||
public static $srcContext = 1;
|
||||
public static $traceArgs = true;
|
||||
public static $errorTypes = array(
|
||||
E_DEPRECATED => 'E_DEPRECATED',
|
||||
@ -71,7 +72,7 @@ class ExceptionCaster
|
||||
'file' => $b[$prefix.'file'],
|
||||
'line' => $b[$prefix.'line'],
|
||||
));
|
||||
$a[$xPrefix.'trace'] = new TraceStub($b[$xPrefix.'trace'], 1, false, 0, -1 - count($a[$xPrefix.'trace']->value));
|
||||
$a[$xPrefix.'trace'] = new TraceStub($b[$xPrefix.'trace'], false, 0, -1 - count($a[$xPrefix.'trace']->value));
|
||||
}
|
||||
|
||||
unset($a[$xPrefix.'previous'], $a[$prefix.'code'], $a[$prefix.'file'], $a[$prefix.'line']);
|
||||
@ -90,7 +91,7 @@ class ExceptionCaster
|
||||
|
||||
$a = array();
|
||||
$j = count($frames);
|
||||
if (0 > $i = $trace->offset) {
|
||||
if (0 > $i = $trace->sliceOffset) {
|
||||
$i = max(0, $j + $i);
|
||||
}
|
||||
if (!isset($trace->value[$i])) {
|
||||
@ -98,7 +99,7 @@ class ExceptionCaster
|
||||
}
|
||||
$lastCall = isset($frames[$i]['function']) ? ' ==> '.(isset($frames[$i]['class']) ? $frames[0]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : '';
|
||||
|
||||
for ($j -= $i++; isset($frames[$i]); ++$i, --$j) {
|
||||
for ($j += $trace->numberingOffset - $i++; isset($frames[$i]); ++$i, --$j) {
|
||||
$call = isset($frames[$i]['function']) ? (isset($frames[$i]['class']) ? $frames[$i]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : '???';
|
||||
|
||||
$a[Caster::PREFIX_VIRTUAL.$j.'. '.$call.$lastCall] = new FrameStub(
|
||||
@ -108,7 +109,6 @@ class ExceptionCaster
|
||||
'type' => isset($frames[$i]['type']) ? $frames[$i]['type'] : null,
|
||||
'function' => isset($frames[$i]['function']) ? $frames[$i]['function'] : null,
|
||||
) + $frames[$i - 1],
|
||||
$trace->srcContext,
|
||||
$trace->keepArgs,
|
||||
true
|
||||
);
|
||||
@ -122,12 +122,11 @@ class ExceptionCaster
|
||||
'type' => null,
|
||||
'function' => '{main}',
|
||||
) + $frames[$i - 1],
|
||||
$trace->srcContext,
|
||||
$trace->keepArgs,
|
||||
true
|
||||
);
|
||||
if (null !== $trace->length) {
|
||||
$a = array_slice($a, 0, $trace->length, true);
|
||||
if (null !== $trace->sliceLength) {
|
||||
$a = array_slice($a, 0, $trace->sliceLength, true);
|
||||
}
|
||||
|
||||
return $a;
|
||||
@ -146,8 +145,8 @@ class ExceptionCaster
|
||||
$f['file'] = substr($f['file'], 0, -strlen($match[0]));
|
||||
$f['line'] = (int) $match[1];
|
||||
}
|
||||
if (file_exists($f['file']) && 0 <= $frame->srcContext) {
|
||||
$src[$f['file'].':'.$f['line']] = self::extractSource(explode("\n", file_get_contents($f['file'])), $f['line'], $frame->srcContext);
|
||||
if (file_exists($f['file']) && 0 <= self::$srcContext) {
|
||||
$src[$f['file'].':'.$f['line']] = self::extractSource(explode("\n", file_get_contents($f['file'])), $f['line'], self::$srcContext);
|
||||
|
||||
if (!empty($f['class']) && is_subclass_of($f['class'], 'Twig_Template') && method_exists($f['class'], 'getDebugInfo')) {
|
||||
$template = isset($f['object']) ? $f['object'] : new $f['class'](new \Twig_Environment(new \Twig_Loader_Filesystem()));
|
||||
@ -157,7 +156,7 @@ class ExceptionCaster
|
||||
$templateSrc = explode("\n", method_exists($template, 'getSource') ? $template->getSource() : $template->getEnvironment()->getLoader()->getSource($templateName));
|
||||
$templateInfo = $template->getDebugInfo();
|
||||
if (isset($templateInfo[$f['line']])) {
|
||||
$src[$templateName.':'.$templateInfo[$f['line']]] = self::extractSource($templateSrc, $templateInfo[$f['line']], $frame->srcContext);
|
||||
$src[$templateName.':'.$templateInfo[$f['line']]] = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext);
|
||||
}
|
||||
} catch (\Twig_Error_Loader $e) {
|
||||
}
|
||||
@ -247,15 +246,29 @@ class ExceptionCaster
|
||||
|
||||
private static function extractSource(array $srcArray, $line, $srcContext)
|
||||
{
|
||||
$src = '';
|
||||
$src = array();
|
||||
|
||||
for ($i = $line - 1 - $srcContext; $i <= $line - 1 + $srcContext; ++$i) {
|
||||
$src .= (isset($srcArray[$i]) ? $srcArray[$i] : '')."\n";
|
||||
}
|
||||
if (!$srcContext) {
|
||||
$src = trim($src);
|
||||
$src[] = (isset($srcArray[$i]) ? $srcArray[$i] : '')."\n";
|
||||
}
|
||||
|
||||
return $src;
|
||||
$ltrim = 0;
|
||||
while (' ' === $src[0][$ltrim] || "\t" === $src[0][$ltrim]) {
|
||||
$i = $srcContext << 1;
|
||||
while ($i > 0 && $src[0][$ltrim] === $src[$i][$ltrim]) {
|
||||
--$i;
|
||||
}
|
||||
if ($i) {
|
||||
break;
|
||||
}
|
||||
++$ltrim;
|
||||
}
|
||||
if ($ltrim) {
|
||||
foreach ($src as $i => $line) {
|
||||
$src[$i] = substr($line, $ltrim);
|
||||
}
|
||||
}
|
||||
|
||||
return implode('', $src);
|
||||
}
|
||||
}
|
||||
|
@ -18,14 +18,12 @@ namespace Symfony\Component\VarDumper\Caster;
|
||||
*/
|
||||
class FrameStub extends EnumStub
|
||||
{
|
||||
public $srcContext;
|
||||
public $keepArgs;
|
||||
public $inTraceStub;
|
||||
|
||||
public function __construct(array $trace, $srcContext = 1, $keepArgs = true, $inTraceStub = false)
|
||||
public function __construct(array $frame, $keepArgs = true, $inTraceStub = false)
|
||||
{
|
||||
$this->value = $trace;
|
||||
$this->srcContext = $srcContext;
|
||||
$this->value = $frame;
|
||||
$this->keepArgs = $keepArgs;
|
||||
$this->inTraceStub = $inTraceStub;
|
||||
}
|
||||
|
@ -74,6 +74,59 @@ class ReflectionCaster
|
||||
return $a;
|
||||
}
|
||||
|
||||
public static function castGenerator(\Generator $c, array $a, Stub $stub, $isNested)
|
||||
{
|
||||
return class_exists('ReflectionGenerator', false) ? self::castReflectionGenerator(new \ReflectionGenerator($c), $a, $stub, $isNested) : $a;
|
||||
}
|
||||
|
||||
public static function castType(\ReflectionType $c, array $a, Stub $stub, $isNested)
|
||||
{
|
||||
$prefix = Caster::PREFIX_VIRTUAL;
|
||||
|
||||
$a += array(
|
||||
$prefix.'type' => $c->__toString(),
|
||||
$prefix.'allowsNull' => $c->allowsNull(),
|
||||
$prefix.'isBuiltin' => $c->isBuiltin(),
|
||||
);
|
||||
|
||||
return $a;
|
||||
}
|
||||
|
||||
public static function castReflectionGenerator(\ReflectionGenerator $c, array $a, Stub $stub, $isNested)
|
||||
{
|
||||
$prefix = Caster::PREFIX_VIRTUAL;
|
||||
|
||||
if ($c->getThis()) {
|
||||
$a[$prefix.'this'] = new CutStub($c->getThis());
|
||||
}
|
||||
$x = $c->getFunction();
|
||||
$frame = array(
|
||||
'class' => isset($x->class) ? $x->class : null,
|
||||
'type' => isset($x->class) ? ($x->isStatic() ? '::' : '->') : null,
|
||||
'function' => $x->name,
|
||||
'file' => $c->getExecutingFile(),
|
||||
'line' => $c->getExecutingLine(),
|
||||
);
|
||||
if ($trace = $c->getTrace(DEBUG_BACKTRACE_IGNORE_ARGS)) {
|
||||
$x = new \ReflectionGenerator($c->getExecutingGenerator());
|
||||
array_unshift($trace, array(
|
||||
'function' => 'yield',
|
||||
'file' => $x->getExecutingFile(),
|
||||
'line' => $x->getExecutingLine() - 1,
|
||||
));
|
||||
$trace[] = $frame;
|
||||
$a[$prefix.'trace'] = new TraceStub($trace, false, 0, -1, -1);
|
||||
} else {
|
||||
$x = new FrameStub($frame, false, true);
|
||||
$x = ExceptionCaster::castFrameStub($x, array(), $x, true);
|
||||
$a[$prefix.'executing'] = new EnumStub(array(
|
||||
$frame['class'].$frame['type'].$frame['function'].'()' => $x[$prefix.'src'],
|
||||
));
|
||||
}
|
||||
|
||||
return $a;
|
||||
}
|
||||
|
||||
public static function castClass(\ReflectionClass $c, array $a, Stub $stub, $isNested, $filter = 0)
|
||||
{
|
||||
$prefix = Caster::PREFIX_VIRTUAL;
|
||||
|
@ -54,6 +54,7 @@ class StubCaster
|
||||
if ($isNested) {
|
||||
$stub->class = '';
|
||||
$stub->handle = 0;
|
||||
$stub->value = null;
|
||||
|
||||
$a = array();
|
||||
|
||||
|
@ -20,17 +20,17 @@ use Symfony\Component\VarDumper\Cloner\Stub;
|
||||
*/
|
||||
class TraceStub extends Stub
|
||||
{
|
||||
public $srcContext;
|
||||
public $keepArgs;
|
||||
public $offset;
|
||||
public $length;
|
||||
public $sliceOffset;
|
||||
public $sliceLength;
|
||||
public $numberingOffset;
|
||||
|
||||
public function __construct(array $trace, $srcContext = 1, $keepArgs = true, $offset = 0, $length = null)
|
||||
public function __construct(array $trace, $keepArgs = true, $sliceOffset = 0, $sliceLength = null, $numberingOffset = 0)
|
||||
{
|
||||
$this->value = $trace;
|
||||
$this->srcContext = $srcContext;
|
||||
$this->keepArgs = $keepArgs;
|
||||
$this->offset = $offset;
|
||||
$this->length = $length;
|
||||
$this->sliceOffset = $sliceOffset;
|
||||
$this->sliceLength = $sliceLength;
|
||||
$this->numberingOffset = $numberingOffset;
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,9 @@ abstract class AbstractCloner implements ClonerInterface
|
||||
'Symfony\Component\VarDumper\Caster\EnumStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castEnum',
|
||||
|
||||
'Closure' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castClosure',
|
||||
'Generator' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castGenerator',
|
||||
'ReflectionType' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castType',
|
||||
'ReflectionGenerator' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castReflectionGenerator',
|
||||
'ReflectionClass' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castClass',
|
||||
'ReflectionFunctionAbstract' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castFunctionAbstract',
|
||||
'ReflectionMethod' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castMethod',
|
||||
|
@ -149,7 +149,7 @@ class VarCloner extends AbstractCloner
|
||||
$stub->handle = $h;
|
||||
$a = $this->castObject($stub, 0 < $i);
|
||||
if ($v !== $stub->value) {
|
||||
if (Stub::TYPE_OBJECT !== $stub->type) {
|
||||
if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) {
|
||||
break;
|
||||
}
|
||||
if ($useExt) {
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\VarDumper\Tests\Caster;
|
||||
|
||||
use Symfony\Component\VarDumper\Test\VarDumperTestCase;
|
||||
use Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
@ -72,7 +73,7 @@ Closure {
|
||||
\$b: & 123
|
||||
}
|
||||
file: "%sReflectionCasterTest.php"
|
||||
line: "62 to 62"
|
||||
line: "63 to 63"
|
||||
}
|
||||
EOTXT
|
||||
, $var
|
||||
@ -92,11 +93,92 @@ Closure {
|
||||
returnType: "int"
|
||||
class: "Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest"
|
||||
this: Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest { …}
|
||||
file: "%sReflectionCasterTest.php(87) : eval()'d code"
|
||||
file: "%sReflectionCasterTest.php(88) : eval()'d code"
|
||||
line: "1 to 1"
|
||||
}
|
||||
EOTXT
|
||||
, $f
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 7.0
|
||||
*/
|
||||
public function testGenerator()
|
||||
{
|
||||
$g = new GeneratorDemo();
|
||||
$g = $g->baz();
|
||||
$r = new \ReflectionGenerator($g);
|
||||
|
||||
$xDump = <<<'EODUMP'
|
||||
Generator {
|
||||
this: Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo { …}
|
||||
executing: {
|
||||
Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo->baz(): {
|
||||
%sGeneratorDemo.php:14: """
|
||||
{\n
|
||||
yield from bar();\n
|
||||
}\n
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
EODUMP;
|
||||
|
||||
$this->assertDumpMatchesFormat($xDump, $g);
|
||||
|
||||
foreach ($g as $v) {
|
||||
break;
|
||||
}
|
||||
|
||||
$xDump = <<<'EODUMP'
|
||||
array:2 [
|
||||
0 => ReflectionGenerator {
|
||||
this: Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo { …}
|
||||
trace: {
|
||||
3. Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo::foo() ==> yield(): {
|
||||
src: {
|
||||
%sGeneratorDemo.php:9: """
|
||||
{\n
|
||||
yield 1;\n
|
||||
}\n
|
||||
"""
|
||||
}
|
||||
}
|
||||
2. Symfony\Component\VarDumper\Tests\Fixtures\bar() ==> Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo::foo(): {
|
||||
src: {
|
||||
%sGeneratorDemo.php:20: """
|
||||
{\n
|
||||
yield from GeneratorDemo::foo();\n
|
||||
}\n
|
||||
"""
|
||||
}
|
||||
}
|
||||
1. Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo->baz() ==> Symfony\Component\VarDumper\Tests\Fixtures\bar(): {
|
||||
src: {
|
||||
%sGeneratorDemo.php:14: """
|
||||
{\n
|
||||
yield from bar();\n
|
||||
}\n
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1 => Generator {
|
||||
executing: {
|
||||
Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo::foo(): {
|
||||
%sGeneratorDemo.php:10: """
|
||||
yield 1;\n
|
||||
}\n
|
||||
\n
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
EODUMP;
|
||||
|
||||
$this->assertDumpMatchesFormat($xDump, array($r, $r->getExecutingGenerator()));
|
||||
}
|
||||
}
|
||||
|
@ -232,45 +232,45 @@ stream resource {@{$ref}
|
||||
%d. __TwigTemplate_VarDumperFixture_u75a09->doDisplay() ==> new Exception(): {
|
||||
src: {
|
||||
%sTwig.php:19: """
|
||||
// line 2\\n
|
||||
throw new \Exception('Foobar');\\n
|
||||
}\\n
|
||||
// line 2\\n
|
||||
throw new \Exception('Foobar');\\n
|
||||
}\\n
|
||||
"""
|
||||
{$twig} }
|
||||
}
|
||||
%d. Twig_Template->displayWithErrorHandling() ==> __TwigTemplate_VarDumperFixture_u75a09->doDisplay(): {
|
||||
src: {
|
||||
%sTemplate.php:%d: """
|
||||
try {\\n
|
||||
\$this->doDisplay(\$context, \$blocks);\\n
|
||||
} catch (Twig_Error \$e) {\\n
|
||||
try {\\n
|
||||
\$this->doDisplay(\$context, \$blocks);\\n
|
||||
} catch (Twig_Error \$e) {\\n
|
||||
"""
|
||||
}
|
||||
}
|
||||
%d. Twig_Template->display() ==> Twig_Template->displayWithErrorHandling(): {
|
||||
src: {
|
||||
%sTemplate.php:%d: """
|
||||
{\\n
|
||||
\$this->displayWithErrorHandling(\$this->env->mergeGlobals(\$context), array_merge(\$this->blocks, \$blocks));\\n
|
||||
}\\n
|
||||
{\\n
|
||||
\$this->displayWithErrorHandling(\$this->env->mergeGlobals(\$context), array_merge(\$this->blocks, \$blocks));\\n
|
||||
}\\n
|
||||
"""
|
||||
}
|
||||
}
|
||||
%d. Twig_Template->render() ==> Twig_Template->display(): {
|
||||
src: {
|
||||
%sTemplate.php:%d: """
|
||||
try {\\n
|
||||
\$this->display(\$context);\\n
|
||||
} catch (Exception \$e) {\\n
|
||||
try {\\n
|
||||
\$this->display(\$context);\\n
|
||||
} catch (Exception \$e) {\\n
|
||||
"""
|
||||
}
|
||||
}
|
||||
%d. %slosure%s() ==> Twig_Template->render(): {
|
||||
src: {
|
||||
%sCliDumperTest.php:{$line}: """
|
||||
}\\n
|
||||
};'),\\n
|
||||
));\\n
|
||||
}\\n
|
||||
};'),\\n
|
||||
));\\n
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\VarDumper\Tests\Fixtures;
|
||||
|
||||
class GeneratorDemo
|
||||
{
|
||||
public static function foo()
|
||||
{
|
||||
yield 1;
|
||||
}
|
||||
|
||||
public function baz()
|
||||
{
|
||||
yield from bar();
|
||||
}
|
||||
}
|
||||
|
||||
function bar()
|
||||
{
|
||||
yield from GeneratorDemo::foo();
|
||||
}
|
Reference in New Issue
Block a user