[ErrorRenderer] Add DebugCommand for easy debugging and testing

This commit is contained in:
Yonel Ceruto 2019-07-11 17:24:38 -04:00 committed by Tobias Schultze
parent 86440a4b77
commit 97c89686b1
5 changed files with 227 additions and 1 deletions

View File

@ -194,5 +194,11 @@
<argument type="service" id="debug.file_link_formatter" on-invalid="null" />
<tag name="console.command" command="debug:form" />
</service>
<service id="console.command.error_renderer_debug" class="Symfony\Component\ErrorRenderer\Command\DebugCommand">
<argument type="collection" /> <!-- All error renderers are injected here by ErrorRendererPass -->
<argument type="service" id="debug.file_link_formatter" on-invalid="null" />
<tag name="console.command" command="debug:error-renderer" />
</service>
</services>
</container>

View File

@ -0,0 +1,123 @@
<?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\ErrorRenderer\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
/**
* A console command for retrieving information about error renderers.
*
* @author Yonel Ceruto <yonelceruto@gmail.com>
*
* @internal
*/
class DebugCommand extends Command
{
protected static $defaultName = 'debug:error-renderer';
private $renderers;
private $fileLinkFormatter;
/**
* @param ErrorRendererInterface[] $renderers
*/
public function __construct(array $renderers, FileLinkFormatter $fileLinkFormatter = null)
{
$this->renderers = $renderers;
$this->fileLinkFormatter = $fileLinkFormatter;
parent::__construct();
}
/**
* {@inheritdoc}
*/
protected function configure(): void
{
$this
->addArgument('format', InputArgument::OPTIONAL, sprintf('Outputs a sample in a specific format (one of %s)', implode(', ', array_keys($this->renderers))))
->setDescription('Displays all available error renderers and their formats.')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays all available error renderers and
their formats:
<info>php %command.full_name%</info>
Or output a sample in a specific format:
<info>php %command.full_name% json</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$renderers = $this->renderers;
if ($format = $input->getArgument('format')) {
if (!isset($renderers[$format])) {
throw new InvalidArgumentException(sprintf('No error renderer found for format "%s". Known format are %s.', $format, implode(', ', array_keys($this->renderers))));
}
$exception = FlattenException::createFromThrowable(new \Exception('This is a sample exception.'), 500, ['X-Debug' => false]);
$io->writeln($renderers[$format]->render($exception));
} else {
$tableRows = [];
foreach ($renderers as $format => $renderer) {
$tableRows[] = [sprintf('<fg=cyan>%s</fg=cyan>', $format), $this->formatClassLink(\get_class($renderer))];
}
$io->title('Error Renderers');
$io->text('The following error renderers are available:');
$io->newLine();
$io->table(['Format', 'Class'], $tableRows);
}
}
private function formatClassLink(string $class): string
{
if ('' === $fileLink = $this->getFileLink($class)) {
return $class;
}
return sprintf('<href=%s>%s</>', $fileLink, $class);
}
private function getFileLink(string $class): string
{
if (null === $this->fileLinkFormatter) {
return '';
}
try {
$r = new \ReflectionClass($class);
} catch (\ReflectionException $e) {
return '';
}
return $this->fileLinkFormatter->format($r->getFileName(), $r->getStartLine());
}
}

View File

@ -24,11 +24,13 @@ class ErrorRendererPass implements CompilerPassInterface
{
private $rendererService;
private $rendererTag;
private $debugCommandService;
public function __construct(string $rendererService = 'error_renderer', string $rendererTag = 'error_renderer.renderer')
public function __construct(string $rendererService = 'error_renderer', string $rendererTag = 'error_renderer.renderer', string $debugCommandService = 'console.command.error_renderer_debug')
{
$this->rendererService = $rendererService;
$this->rendererTag = $rendererTag;
$this->debugCommandService = $debugCommandService;
}
/**
@ -61,5 +63,9 @@ class ErrorRendererPass implements CompilerPassInterface
$definition = $container->getDefinition($this->rendererService);
$definition->replaceArgument(0, ServiceLocatorTagPass::register($container, $renderers));
if ($container->hasDefinition($this->debugCommandService)) {
$container->getDefinition($this->debugCommandService)->replaceArgument(0, $renderers);
}
}
}

View File

@ -0,0 +1,90 @@
<?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\ErrorRenderer\Tests\Command;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\ErrorRenderer\Command\DebugCommand;
use Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer;
use Symfony\Component\ErrorRenderer\ErrorRenderer\TxtErrorRenderer;
use Symfony\Component\ErrorRenderer\ErrorRenderer\XmlErrorRenderer;
class DebugCommandTest extends TestCase
{
public function testAvailableRenderers()
{
$tester = $this->createCommandTester();
$ret = $tester->execute([], ['decorated' => false]);
$this->assertEquals(0, $ret, 'Returns 0 in case of success');
$this->assertSame(<<<TXT
Error Renderers
===============
The following error renderers are available:
-------- -----------------------------------------------------------------
Format Class
-------- -----------------------------------------------------------------
json Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer
xml Symfony\Component\ErrorRenderer\ErrorRenderer\XmlErrorRenderer
txt Symfony\Component\ErrorRenderer\ErrorRenderer\TxtErrorRenderer
-------- -----------------------------------------------------------------
TXT
, $tester->getDisplay(true));
}
public function testFormatArgument()
{
$tester = $this->createCommandTester();
$ret = $tester->execute(['format' => 'json'], ['decorated' => false]);
$this->assertEquals(0, $ret, 'Returns 0 in case of success');
$this->assertSame(<<<TXT
{
"title": "Internal Server Error",
"status": 500,
"detail": "This is a sample exception."
}
TXT
, $tester->getDisplay(true));
}
private function createCommandTester()
{
$command = new DebugCommand([
'json' => new JsonErrorRenderer(false),
'xml' => new XmlErrorRenderer(false),
'txt' => new TxtErrorRenderer(false),
]);
$application = new Application();
$application->add($command);
return new CommandTester($application->find('debug:error-renderer'));
}
/**
* @expectedException \Symfony\Component\Console\Exception\InvalidArgumentException
* @expectedExceptionMessage No error renderer found for format "foo". Known format are json, xml, txt.
*/
public function testInvalidFormat()
{
$tester = $this->createCommandTester();
$tester->execute(['format' => 'foo'], ['decorated' => false]);
}
}

View File

@ -20,6 +20,7 @@
"psr/log": "~1.0"
},
"require-dev": {
"symfony/console": "^4.4",
"symfony/dependency-injection": "^4.4",
"symfony/http-kernel": "^4.4"
},