From 1c1818b87675d077808dbf7e05da84c2e1ddc9f8 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 1 Nov 2018 16:20:35 +0100 Subject: [PATCH] [VarDumper] fix dump of closures created from callables --- .../Console/Descriptor/JsonDescriptor.php | 14 ++++++++++ .../Console/Descriptor/MarkdownDescriptor.php | 14 ++++++++++ .../Console/Descriptor/TextDescriptor.php | 20 ++++++++----- .../Console/Descriptor/XmlDescriptor.php | 14 ++++++++++ .../Tests/Fixtures/Descriptor/callable_6.txt | 2 +- .../Descriptor/event_dispatcher_1_event1.txt | 2 +- .../Descriptor/event_dispatcher_1_events.txt | 2 +- .../EventDispatcher/Debug/WrappedListener.php | 13 +++++++-- .../DataCollector/RequestDataCollector.php | 17 ++++++++++- .../VarDumper/Caster/ReflectionCaster.php | 11 ++++++++ .../Tests/Caster/ReflectionCasterTest.php | 28 +++++++++++++++++++ 11 files changed, 124 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php index e51c040068..a8c279815b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -368,6 +368,20 @@ class JsonDescriptor extends Descriptor if ($callable instanceof \Closure) { $data['type'] = 'closure'; + $r = new \ReflectionFunction($callable); + if (false !== strpos($r->name, '{closure}')) { + return $data; + } + $data['name'] = $r->name; + + $class = ($class = $r->getClosureThis()) ? \get_class($class) : null; + if ($scopeClass = $r->getClosureScopeClass() ?: $class) { + $data['class'] = $scopeClass; + if (!$class) { + $data['static'] = true; + } + } + return $data; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php index 339b2db9f9..2e7d07272f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php @@ -354,6 +354,20 @@ class MarkdownDescriptor extends Descriptor if ($callable instanceof \Closure) { $string .= "\n- Type: `closure`"; + $r = new \ReflectionFunction($callable); + if (false !== strpos($r->name, '{closure}')) { + return $this->write($string."\n"); + } + $string .= "\n".sprintf('- Name: `%s`', $r->name); + + $class = ($class = $r->getClosureThis()) ? \get_class($class) : null; + if ($scopeClass = $r->getClosureScopeClass() ?: $class) { + $string .= "\n".sprintf('- Class: `%s`', $class); + if (!$class) { + $string .= "\n- Static: yes"; + } + } + return $this->write($string."\n"); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index d0a2efd8a1..b381c3a499 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -56,12 +56,7 @@ class TextDescriptor extends Descriptor if ($showControllers) { $controller = $route->getDefault('_controller'); - if ($controller instanceof \Closure) { - $controller = 'Closure'; - } elseif (\is_object($controller)) { - $controller = \get_class($controller); - } - $row[] = $controller; + $row[] = $this->formatCallable($controller); } $tableRows[] = $row; @@ -474,7 +469,18 @@ class TextDescriptor extends Descriptor } if ($callable instanceof \Closure) { - return '\Closure()'; + $r = new \ReflectionFunction($callable); + if (false !== strpos($r->name, '{closure}')) { + return 'Closure()'; + } + if ($class = $r->getClosureScopeClass()) { + return sprintf('%s::%s()', $class, $r->name); + } + if ($class = $r->getClosureThis()) { + return sprintf('%s::%s()', \get_class($class), $r->name); + } + + return $r->name.'()'; } if (method_exists($callable, '__invoke')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index aa63315de3..ab74c1f6f2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -580,6 +580,20 @@ class XmlDescriptor extends Descriptor if ($callable instanceof \Closure) { $callableXML->setAttribute('type', 'closure'); + $r = new \ReflectionFunction($callable); + if (false !== strpos($r->name, '{closure}')) { + return $dom; + } + $callableXML->setAttribute('name', $r->name); + + $class = ($class = $r->getClosureThis()) ? \get_class($class) : null; + if ($scopeClass = $r->getClosureScopeClass() ?: $class) { + $callableXML->setAttribute('class', $class); + if (!$class) { + $callableXML->setAttribute('static', 'true'); + } + } + return $dom; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/callable_6.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/callable_6.txt index 9b030ab791..8bf37d37b9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/callable_6.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/callable_6.txt @@ -1 +1 @@ -\Closure() \ No newline at end of file +Closure() diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.txt index 99c7cba66f..f7a3cb0bd9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.txt @@ -6,6 +6,6 @@  Order   Callable   Priority  ------- ------------------- ---------- #1 global_function() 255 - #2 \Closure() -1 + #2 Closure() -1 ------- ------------------- ---------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.txt index 687323e9ed..475ad24cfd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.txt @@ -9,7 +9,7 @@  Order   Callable   Priority  ------- ------------------- ---------- #1 global_function() 255 - #2 \Closure() -1 + #2 Closure() -1 ------- ------------------- ---------- "event2" event diff --git a/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php b/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php index 2d8126a65d..9038ae7798 100644 --- a/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php +++ b/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php @@ -34,7 +34,6 @@ class WrappedListener public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) { $this->listener = $listener; - $this->name = $name; $this->stopwatch = $stopwatch; $this->dispatcher = $dispatcher; $this->called = false; @@ -44,7 +43,17 @@ class WrappedListener $this->name = \is_object($listener[0]) ? \get_class($listener[0]) : $listener[0]; $this->pretty = $this->name.'::'.$listener[1]; } elseif ($listener instanceof \Closure) { - $this->pretty = $this->name = 'closure'; + $r = new \ReflectionFunction($listener); + if (false !== strpos($r->name, '{closure}')) { + $this->pretty = $this->name = 'closure'; + } elseif ($this->name = $r->getClosureScopeClass()) { + $this->pretty = $this->name.'::'.$r->name; + } elseif ($class = $r->getClosureThis()) { + $this->name = \get_class($class); + $this->pretty = $this->name.'::'.$r->name; + } else { + $this->pretty = $this->name = $r->name; + } } elseif (\is_string($listener)) { $this->pretty = $this->name = $listener; } else { diff --git a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php index e415833cc3..2501b93210 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php @@ -380,12 +380,27 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter if ($controller instanceof \Closure) { $r = new \ReflectionFunction($controller); - return array( + $controller = array( 'class' => $r->getName(), 'method' => null, 'file' => $r->getFileName(), 'line' => $r->getStartLine(), ); + + if (false !== strpos($r->name, '{closure}')) { + return $controller; + } + $controller['method'] = $r->name; + + if ($class = $r->getClosureScopeClass()) { + $controller['class'] = $class; + } elseif ($class = $r->getClosureThis()) { + $controller['class'] = \get_class($class); + } else { + return $r->name; + } + + return $controller; } if (\is_object($controller)) { diff --git a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php index 2b049eaecb..a1f0a1a7eb 100644 --- a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php @@ -39,6 +39,17 @@ class ReflectionCaster $stub->class = 'Closure'; // HHVM generates unique class names for closures $a = static::castFunctionAbstract($c, $a, $stub, $isNested, $filter); + if (false === strpos($c->name, '{closure}')) { + if (isset($a[$prefix.'class'])) { + $stub->class = $a[$prefix.'class']->value.'::'.$c->name; + } elseif (isset($a[$prefix.'this'])) { + $stub->class = $a[$prefix.'this']->class.'::'.$c->name; + } else { + $stub->class = $c->name; + } + unset($a[$prefix.'class']); + } + if (isset($a[$prefix.'parameters'])) { foreach ($a[$prefix.'parameters']->value as &$v) { $param = $v; diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php index a66c1d0386..4e67591554 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php @@ -85,6 +85,34 @@ EOTXT ); } + public function testFromCallableClosureCaster() + { + if (\defined('HHVM_VERSION_ID')) { + $this->markTestSkipped('Not for HHVM.'); + } + $var = array( + (new \ReflectionMethod($this, __FUNCTION__))->getClosure($this), + (new \ReflectionMethod(__CLASS__, 'tearDownAfterClass'))->getClosure(), + ); + + $this->assertDumpMatchesFormat( + << Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest::testFromCallableClosureCaster { + this: Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest { …} + file: "%sReflectionCasterTest.php" + line: "%d to %d" + } + 1 => %sTestCase::tearDownAfterClass { + file: "%sTestCase.php" + line: "%d to %d" + } +] +EOTXT + , $var + ); + } + public function testClosureCasterExcludingVerbosity() { $var = function () {};