bug #29054 [VarDumper] fix dump of closures created from callables (nicolas-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
-------

1c1818b876 [VarDumper] fix dump of closures created from callables
This commit is contained in:
Nicolas Grekas 2018-11-06 17:26:47 +01:00
commit 41eaba5af5
11 changed files with 124 additions and 13 deletions

View File

@ -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;
}

View File

@ -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");
}

View File

@ -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')) {

View File

@ -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;
}

View File

@ -6,6 +6,6 @@
 Order   Callable   Priority 
------- ------------------- ----------
#1 global_function() 255
#2 \Closure() -1
#2 Closure() -1
------- ------------------- ----------

View File

@ -9,7 +9,7 @@
 Order   Callable   Priority 
------- ------------------- ----------
#1 global_function() 255
#2 \Closure() -1
#2 Closure() -1
------- ------------------- ----------
"event2" event

View File

@ -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 {

View File

@ -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)) {

View File

@ -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;

View File

@ -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(
<<<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 () {};