diff --git a/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php index 81bf186e55..51c70dd5db 100644 --- a/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php @@ -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); } } diff --git a/src/Symfony/Component/VarDumper/Caster/FrameStub.php b/src/Symfony/Component/VarDumper/Caster/FrameStub.php index 58686f9dbe..1e1194dc85 100644 --- a/src/Symfony/Component/VarDumper/Caster/FrameStub.php +++ b/src/Symfony/Component/VarDumper/Caster/FrameStub.php @@ -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; } diff --git a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php index 1d83c33544..3352c87b7f 100644 --- a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php @@ -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; diff --git a/src/Symfony/Component/VarDumper/Caster/StubCaster.php b/src/Symfony/Component/VarDumper/Caster/StubCaster.php index 0b90358726..ebad5ba984 100644 --- a/src/Symfony/Component/VarDumper/Caster/StubCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/StubCaster.php @@ -54,6 +54,7 @@ class StubCaster if ($isNested) { $stub->class = ''; $stub->handle = 0; + $stub->value = null; $a = array(); diff --git a/src/Symfony/Component/VarDumper/Caster/TraceStub.php b/src/Symfony/Component/VarDumper/Caster/TraceStub.php index 98d6d892b9..59548acaee 100644 --- a/src/Symfony/Component/VarDumper/Caster/TraceStub.php +++ b/src/Symfony/Component/VarDumper/Caster/TraceStub.php @@ -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; } } diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index 9e298c429c..50dc79161b 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -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', diff --git a/src/Symfony/Component/VarDumper/Cloner/VarCloner.php b/src/Symfony/Component/VarDumper/Cloner/VarCloner.php index 51309c662b..139f59eae6 100644 --- a/src/Symfony/Component/VarDumper/Cloner/VarCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/VarCloner.php @@ -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) { diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php index e304e15e05..dcbbb17898 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php @@ -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
@@ -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())); + } } diff --git a/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php b/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php index 66144165bb..9cb606fadc 100644 --- a/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php +++ b/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php @@ -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 """ } } diff --git a/src/Symfony/Component/VarDumper/Tests/Fixtures/GeneratorDemo.php b/src/Symfony/Component/VarDumper/Tests/Fixtures/GeneratorDemo.php new file mode 100644 index 0000000000..e2d24c8c0e --- /dev/null +++ b/src/Symfony/Component/VarDumper/Tests/Fixtures/GeneratorDemo.php @@ -0,0 +1,21 @@ +