feature #31446 [VarDumper] Output the location of calls to dump() (ktherage)

This PR was merged into the 4.4 branch.

Discussion
----------

[VarDumper] Output the location of calls to dump()

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | yes
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | not tested yet
| Fixed tickets | #30830
| License       | MIT
| Doc PR        |

see #30830

Commits
-------

f0a59d3eab [VarDumper] Output the location of calls to dump()
This commit is contained in:
Nicolas Grekas 2019-09-28 18:21:11 +02:00
commit 5440d671ca
7 changed files with 160 additions and 4 deletions

View File

@ -40,6 +40,19 @@
<argument>0</argument> <!-- flags -->
</service>
<service id="var_dumper.contextualized_cli_dumper" class="Symfony\Component\VarDumper\Dumper\ContextualizedDumper" decorates="var_dumper.cli_dumper">
<argument type="service" id="var_dumper.contextualized_cli_dumper.inner" />
<argument type="collection">
<argument type="service" key="source">
<service class="Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider">
<argument>%kernel.charset%</argument>
<argument type="string">%kernel.project_dir%</argument>
<argument type="service" id="debug.file_link_formatter" on-invalid="null" />
</service>
</argument>
</argument>
</service>
<service id="var_dumper.html_dumper" class="Symfony\Component\VarDumper\Dumper\HtmlDumper">
<argument>null</argument>
<argument>%kernel.charset%</argument>

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\VarDumper\Cloner;
use Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
/**
* @author Nicolas Grekas <p@tchwork.com>
@ -24,6 +25,7 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate
private $maxDepth = 20;
private $maxItemsPerDepth = -1;
private $useRefHandles = -1;
private $context = [];
/**
* @param array $data An array as returned by ClonerInterface::cloneVar()
@ -227,6 +229,17 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate
return $data;
}
/**
* @return static
*/
public function withContext(array $context)
{
$data = clone $this;
$data->context = $context;
return $data;
}
/**
* Seeks to a specific key in nested data structures.
*
@ -281,7 +294,18 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate
public function dump(DumperInterface $dumper)
{
$refs = [0];
$this->dumpItem($dumper, new Cursor(), $refs, $this->data[$this->position][$this->key]);
$cursor = new Cursor();
if ($cursor->attr = $this->context[SourceContextProvider::class] ?? []) {
$cursor->attr['if_links'] = true;
$cursor->hashType = -1;
$dumper->dumpScalar($cursor, 'default', '^');
$cursor->attr = ['if_links' => true];
$dumper->dumpScalar($cursor, 'default', ' ');
$cursor->hashType = 0;
}
$this->dumpItem($dumper, $cursor, $refs, $this->data[$this->position][$this->key]);
}
/**

View File

@ -83,7 +83,7 @@ class CliDumper extends AbstractDumper
]);
}
$this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f';
$this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f#L%l';
}
/**
@ -490,6 +490,8 @@ class CliDumper extends AbstractDumper
if (isset($attr['href'])) {
$value = "\033]8;;{$attr['href']}\033\\{$value}\033]8;;\033\\";
}
} elseif ($attr['if_links'] ?? false) {
return '';
}
return $value;
@ -548,6 +550,10 @@ class CliDumper extends AbstractDumper
protected function endValue(Cursor $cursor)
{
if (-1 === $cursor->hashType) {
return;
}
if (Stub::ARRAY_INDEXED === $cursor->hashType || Stub::ARRAY_ASSOC === $cursor->hashType) {
if (self::DUMP_TRAILING_COMMA & $this->flags && 0 < $cursor->depth) {
$this->line .= ',';
@ -628,7 +634,7 @@ class CliDumper extends AbstractDumper
private function getSourceLink(string $file, int $line)
{
if ($fmt = $this->displayOptions['fileLinkFormat']) {
return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : ($fmt->format($file, $line) ?: 'file://'.$file);
return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : ($fmt->format($file, $line) ?: 'file://'.$file.'#L'.$line);
}
return false;

View File

@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarDumper\Dumper;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
/**
* @author Kévin Thérage <therage.kevin@gmail.com>
*/
class ContextualizedDumper implements DataDumperInterface
{
private $wrappedDumper;
private $contextProviders;
/**
* @param ContextProviderInterface[] $contextProviders
*/
public function __construct(DataDumperInterface $wrappedDumper, array $contextProviders)
{
$this->wrappedDumper = $wrappedDumper;
$this->contextProviders = $contextProviders;
}
public function dump(Data $data)
{
$context = [];
foreach ($this->contextProviders as $contextProvider) {
$context[\get_class($contextProvider)] = $contextProvider->getContext();
}
$this->wrappedDumper->dump($data->withContext($context));
}
}

View File

@ -53,6 +53,10 @@ Symfony\Component\VarDumper\Cloner\Data Object
[maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
[maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
[useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
[context:Symfony\Component\VarDumper\Cloner\Data:private] => Array
(
)
)
EOTXT;
@ -141,6 +145,10 @@ Symfony\Component\VarDumper\Cloner\Data Object
[maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
[maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
[useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
[context:Symfony\Component\VarDumper\Cloner\Data:private] => Array
(
)
)
EOTXT;
@ -309,6 +317,10 @@ Symfony\Component\VarDumper\Cloner\Data Object
[maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
[maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
[useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
[context:Symfony\Component\VarDumper\Cloner\Data:private] => Array
(
)
)
EOTXT;
@ -327,7 +339,7 @@ EOTXT;
$clone = $cloner->cloneVar($data);
$expected = <<<'EOTXT'
object(Symfony\Component\VarDumper\Cloner\Data)#%i (6) {
object(Symfony\Component\VarDumper\Cloner\Data)#%d (7) {
["data":"Symfony\Component\VarDumper\Cloner\Data":private]=>
array(2) {
[0]=>
@ -372,6 +384,9 @@ object(Symfony\Component\VarDumper\Cloner\Data)#%i (6) {
int(-1)
["useRefHandles":"Symfony\Component\VarDumper\Cloner\Data":private]=>
int(-1)
["context":"Symfony\Component\VarDumper\Cloner\Data":private]=>
array(0) {
}
}
EOTXT;
@ -432,6 +447,10 @@ Symfony\Component\VarDumper\Cloner\Data Object
[maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
[maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
[useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
[context:Symfony\Component\VarDumper\Cloner\Data:private] => Array
(
)
)
EOTXT;
@ -501,6 +520,10 @@ Symfony\Component\VarDumper\Cloner\Data Object
[maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
[maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
[useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
[context:Symfony\Component\VarDumper\Cloner\Data:private] => Array
(
)
)
EOTXT;

View File

@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarDumper\Tests\Dumper;
use PHPUnit\Framework\TestCase;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
use Symfony\Component\VarDumper\Dumper\ContextualizedDumper;
/**
* @author Kévin Thérage <therage.kevin@gmail.com>
*/
class ContextualizedDumperTest extends TestCase
{
public function testContextualizedCliDumper()
{
$wrappedDumper = new CliDumper('php://output');
$wrappedDumper->setColors(true);
$var = 'example';
$href = sprintf('file://%s#L%s', __FILE__, 37);
$dumper = new ContextualizedDumper($wrappedDumper, [new SourceContextProvider()]);
$cloner = new VarCloner();
$data = $cloner->cloneVar($var);
ob_start();
$dumper->dump($data);
$out = ob_get_clean();
$this->assertStringContainsString("\e]8;;{$href}\e\\\e[", $out);
$this->assertStringContainsString("m{$var}\e[", $out);
}
}

View File

@ -14,6 +14,8 @@ namespace Symfony\Component\VarDumper;
use Symfony\Component\VarDumper\Caster\ReflectionCaster;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
use Symfony\Component\VarDumper\Dumper\ContextualizedDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
// Load the global dump() function
@ -38,6 +40,8 @@ class VarDumper
$dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg']) ? new CliDumper() : new HtmlDumper();
}
$dumper = new ContextualizedDumper($dumper, [new SourceContextProvider()]);
self::$handler = function ($var) use ($cloner, $dumper) {
$dumper->dump($cloner->cloneVar($var));
};