Skip to content

Commit

Permalink
feature #16325 [VarDumper] Casters for Generator, ReflectionGenerator…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
nicolas-grekas committed Nov 3, 2015
2 parents 9a1574a + d6c2d75 commit 973fbb3
Show file tree
Hide file tree
Showing 10 changed files with 215 additions and 44 deletions.
43 changes: 28 additions & 15 deletions src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php
Expand Up @@ -21,6 +21,7 @@
*/
class ExceptionCaster
{
public static $srcContext = 1;
public static $traceArgs = true;
public static $errorTypes = array(
E_DEPRECATED => 'E_DEPRECATED',
Expand Down Expand Up @@ -71,7 +72,7 @@ public static function castThrowingCasterException(ThrowingCasterException $e, a
'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']);
Expand All @@ -90,15 +91,15 @@ public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, $is

$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])) {
return array();
}
$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(
Expand All @@ -108,7 +109,6 @@ public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, $is
'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
);
Expand All @@ -122,12 +122,11 @@ public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, $is
'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;
Expand All @@ -146,8 +145,8 @@ public static function castFrameStub(FrameStub $frame, array $a, Stub $stub, $is
$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()));
Expand All @@ -157,7 +156,7 @@ public static function castFrameStub(FrameStub $frame, array $a, Stub $stub, $is
$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) {
}
Expand Down Expand Up @@ -247,15 +246,29 @@ private static function filterExceptionArray($xClass, array $a, $xPrefix, $filte

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";
$src[] = (isset($srcArray[$i]) ? $srcArray[$i] : '')."\n";
}
if (!$srcContext) {
$src = trim($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 $src;
return implode('', $src);
}
}
6 changes: 2 additions & 4 deletions src/Symfony/Component/VarDumper/Caster/FrameStub.php
Expand Up @@ -18,14 +18,12 @@
*/
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;
}
Expand Down
53 changes: 53 additions & 0 deletions src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php
Expand Up @@ -74,6 +74,59 @@ public static function castClosure(\Closure $c, array $a, Stub $stub, $isNested)
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;
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/VarDumper/Caster/StubCaster.php
Expand Up @@ -54,6 +54,7 @@ public static function castEnum(EnumStub $c, array $a, Stub $stub, $isNested)
if ($isNested) {
$stub->class = '';
$stub->handle = 0;
$stub->value = null;

$a = array();

Expand Down
14 changes: 7 additions & 7 deletions src/Symfony/Component/VarDumper/Caster/TraceStub.php
Expand Up @@ -20,17 +20,17 @@
*/
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;
}
}
3 changes: 3 additions & 0 deletions src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/VarDumper/Cloner/VarCloner.php
Expand Up @@ -149,7 +149,7 @@ protected function doClone($var)
$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) {
Expand Down
Expand Up @@ -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>
Expand Down Expand Up @@ -72,7 +73,7 @@ public function testClosureCaster()
\$b: & 123
}
file: "%sReflectionCasterTest.php"
line: "62 to 62"
line: "63 to 63"
}
EOTXT
, $var
Expand All @@ -92,11 +93,92 @@ public function testReturnType()
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()));
}
}

0 comments on commit 973fbb3

Please sign in to comment.