Permalink
Browse files

bug #29054 [VarDumper] fix dump of closures created from callables (n…

…icolas-grekas)

This PR was merged into the 3.4 branch.

Discussion
----------

[VarDumper] fix dump of closures created from callables

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

We are missing displaying full information about closures created using `ReflectionMethod::getClosure()` or `Closure::fromCallable()`.

This PR fixes it. For VarDumper but also other places where we have logic to display them.

Commits
-------

1c1818b [VarDumper] fix dump of closures created from callables
  • Loading branch information...
nicolas-grekas committed Nov 6, 2018
2 parents 85df96a + 1c1818b commit 41eaba5af5989054c15b09a0275596150ef4a65f
@@ -368,6 +368,20 @@ private function getCallableData($callable, array $options = array())
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;
}
@@ -354,6 +354,20 @@ protected function describeCallable($callable, array $options = array())
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");
}
@@ -56,12 +56,7 @@ protected function describeRouteCollection(RouteCollection $routes, array $optio
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 @@ private function formatCallable($callable)
}
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')) {
@@ -580,6 +580,20 @@ private function getCallableDocument($callable)
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;
}
@@ -6,6 +6,6 @@
 Order   Callable   Priority 
------- ------------------- ----------
#1 global_function() 255
#2 \Closure() -1
#2 Closure() -1
------- ------------------- ----------
@@ -9,7 +9,7 @@
 Order   Callable   Priority 
------- ------------------- ----------
#1 global_function() 255
#2 \Closure() -1
#2 Closure() -1
------- ------------------- ----------
"event2" event
@@ -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 @@ public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatc
$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 {
@@ -380,12 +380,27 @@ protected function parseController($controller)
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)) {
@@ -39,6 +39,17 @@ public static function castClosure(\Closure $c, array $a, Stub $stub, $isNested,
$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;
@@ -85,6 +85,34 @@ public function testClosureCaster()
);
}
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(
<<<EOTXT
array:2 [
0 => 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 () {};

0 comments on commit 41eaba5

Please sign in to comment.