Skip to content

Commit

Permalink
[VarDumper] Introduce a new way to collect dumps through a server dumper
Browse files Browse the repository at this point in the history
  • Loading branch information
ogizanagi committed Mar 23, 2018
1 parent d4c616c commit 3dc8e81
Show file tree
Hide file tree
Showing 16 changed files with 1,035 additions and 2 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
CHANGELOG
=========

4.1.0
-----

* added a `ServerDumper` to send serialized Data clones to a server
* added a `ServerDumpCommand` and `DumpServer` to run a server collecting
and displaying dumps on a single place with multiple formats support
* added `CliDescriptor` and `HtmlDescriptor` descriptors for `server:dump` cli and html formats support

4.0.0
-----

Expand Down
80 changes: 80 additions & 0 deletions Command/Descriptor/CliDescriptor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\VarDumper\Command\Descriptor;

use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Dumper\CliDumper;

/**
* Describe collected data clones for cli output.
*
* @author Maxime Steinhausser <[email protected]>
*
* @final
*/
class CliDescriptor implements DumpDescriptorInterface
{
private $dumper;
private $lastIdentifier;

public function __construct(CliDumper $dumper)
{
$this->dumper = $dumper;
}

public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void
{
$io = new SymfonyStyle(new ArrayInput(array()), $output);

$rows = array(array('date', date('r', $context['timestamp'])));
$lastIdentifier = $this->lastIdentifier;
$this->lastIdentifier = $clientId;

$section = "Received from client #$clientId";
if (isset($context['request'])) {
$request = $context['request'];
$this->lastIdentifier = $request['identifier'];
$section = sprintf('%s %s', $request['method'], $request['uri']);
if ($controller = $request['controller']) {
$rows[] = array('controller', $controller);
}
} elseif (isset($context['cli'])) {
$this->lastIdentifier = $context['cli']['identifier'];
$section = '$ '.$context['cli']['command_line'];
}

if ($this->lastIdentifier !== $lastIdentifier) {
$io->section($section);
}

if (isset($context['source'])) {
$source = $context['source'];
$rows[] = array('source', sprintf('%s on line %d', $source['name'], $source['line']));
$file = $source['file_relative'] ?? $source['file'];
$rows[] = array('file', $file);
$fileLink = $source['file_link'] ?? null;
}

$io->table(array(), $rows);

if (isset($fileLink)) {
$io->writeln(array('<info>Open source in your IDE/browser:</info>', $fileLink));
$io->newLine();
}

$this->dumper->dump($data);
$io->newLine();
}
}
23 changes: 23 additions & 0 deletions Command/Descriptor/DumpDescriptorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\VarDumper\Command\Descriptor;

use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\VarDumper\Cloner\Data;

/**
* @author Maxime Steinhausser <[email protected]>
*/
interface DumpDescriptorInterface
{
public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void;
}
93 changes: 93 additions & 0 deletions Command/Descriptor/HtmlDescriptor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\VarDumper\Command\Descriptor;

use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;

/**
* Describe collected data clones for html output.
*
* @author Maxime Steinhausser <[email protected]>
*
* @final
*/
class HtmlDescriptor implements DumpDescriptorInterface
{
private $dumper;
private $initialized = false;

public function __construct(HtmlDumper $dumper)
{
$this->dumper = $dumper;
}

public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void
{
if (!$this->initialized) {
$styles = file_get_contents(__DIR__.'/../../Resources/css/htmlDescriptor.css');
$scripts = file_get_contents(__DIR__.'/../../Resources/js/htmlDescriptor.js');
$output->writeln("<style>$styles</style><script>$scripts</script>");
$this->initialized = true;
}

$title = '-';
if (isset($context['request'])) {
$request = $context['request'];
$title = sprintf('<code>%s</code> <a href="%s">%s</a>', $request['method'], $uri = $request['uri'], $uri);
$dedupIdentifier = $request['identifier'];
} elseif (isset($context['cli'])) {
$title = '<code>$ </code>'.$context['cli']['command_line'];
$dedupIdentifier = $context['cli']['identifier'];
} else {
$dedupIdentifier = uniqid('', true);
}

$contextText = array();
if (isset($context['source'])) {
$source = $context['source'];
$sourceDescription = sprintf('%s on line %d', $source['name'], $source['line']);
if (isset($source['file_link'])) {
$sourceDescription = sprintf('<a href="%s">%s</a>', $source['file_link'], $sourceDescription);
}

$contextText[] = $sourceDescription;
}

$contextText = implode('<br />', $contextText);
$isoDate = $this->extractDate($context, 'c');

$output->writeln(<<<HTML
<article data-dedup-id="$dedupIdentifier">
<header>
<h2>$title</h2>
<time class="text-small" title="$isoDate" datetime="$isoDate">
{$this->extractDate($context)}
</time>
</header>
<section class="body">
<p class="text-small">
$contextText
</p>
{$this->dumper->dump($data, true)}
</section>
</article>
HTML
);
}

private function extractDate(array $context, string $format = 'r'): string
{
return date($format, $context['timestamp']);
}
}
101 changes: 101 additions & 0 deletions Command/ServerDumpCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\VarDumper\Command;

use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Command\Descriptor\CliDescriptor;
use Symfony\Component\VarDumper\Command\Descriptor\DumpDescriptorInterface;
use Symfony\Component\VarDumper\Command\Descriptor\HtmlDescriptor;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use Symfony\Component\VarDumper\Server\DumpServer;

/**
* Starts a dump server to collect and output dumps on a single place with multiple formats support.
*
* @author Maxime Steinhausser <[email protected]>
*
* @final
*/
class ServerDumpCommand extends Command
{
protected static $defaultName = 'server:dump';

private $logger;

/** @var DumpDescriptorInterface[] */
private $descriptors;

public function __construct(array $descriptors = array(), LoggerInterface $logger = null)
{
$this->logger = $logger;
$this->descriptors = $descriptors + array(
'cli' => new CliDescriptor(new CliDumper()),
'html' => new HtmlDescriptor(new HtmlDumper()),
);

parent::__construct();
}

protected function configure()
{
$availableFormats = implode(', ', array_keys($this->descriptors));

$this
->addOption('format', null, InputOption::VALUE_REQUIRED, "The output format ($availableFormats)", 'cli')
->setDescription('Starts a dump server that collects and displays dumps in a single place')
->setHelp(<<<'EOF'
<info>%command.name%</info> starts a dump server that collects and displays
dumps in a single place for debugging you application:
<info>php %command.full_name%</info>
You can consult dumped data in HTML format in your browser by providing the <comment>--format=html</comment> option
and redirecting the output to a file:
<info>php %command.full_name% --format="html" > dump.html</info>

EOF
)
;
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$format = $input->getOption('format');

if (!$descriptor = $this->descriptors[$format] ?? null) {
throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $format));
}

$errorIo = $io->getErrorStyle();
$errorIo->title('Symfony Var Dumper Server');

$server = new DumpServer(null, $this->logger);
$server->start();

$errorIo->success(sprintf('Server listening on %s', $server->getHost()));
$errorIo->comment('Quit the server with CONTROL-C.');

$server->listen(function (Data $data, array $context, int $clientId) use ($descriptor, $output) {
$descriptor->describe($output, $data, $context, $clientId);
});
}
}
32 changes: 32 additions & 0 deletions Dumper/ContextProvider/CliContextProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\VarDumper\Dumper\ContextProvider;

/**
* Tries to provide context on CLI.
*
* @author Maxime Steinhausser <[email protected]>
*/
final class CliContextProvider implements ContextProviderInterface
{
public function getContext(): ?array
{
if ('cli' !== PHP_SAPI) {
return null;
}

return array(
'command_line' => $commandLine = implode(' ', $_SERVER['argv']),
'identifier' => hash('crc32b', $commandLine.$_SERVER['REQUEST_TIME_FLOAT']),
);
}
}
25 changes: 25 additions & 0 deletions Dumper/ContextProvider/ContextProviderInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\VarDumper\Dumper\ContextProvider;

/**
* Interface to provide contextual data about dump data clones sent to a server.
*
* @author Maxime Steinhausser <[email protected]>
*/
interface ContextProviderInterface
{
/**
* @return array|null Context data or null if unable to provide any context
*/
public function getContext(): ?array;
}
Loading

0 comments on commit 3dc8e81

Please sign in to comment.