Skip to content

Commit dc0f057

Browse files
committed
Add DebugCommand for easy debugging and testing
1 parent f4f5ea6 commit dc0f057

File tree

5 files changed

+218
-6
lines changed

5 files changed

+218
-6
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,5 +194,10 @@
194194
<argument type="service" id="debug.file_link_formatter" on-invalid="null" />
195195
<tag name="console.command" command="debug:form" />
196196
</service>
197+
198+
<service id="console.command.error_renderer_debug" class="Symfony\Component\ErrorRenderer\Command\DebugCommand">
199+
<argument type="collection" />
200+
<tag name="console.command" command="debug:error-renderer" />
201+
</service>
197202
</services>
198203
</container>
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\ErrorRenderer\Command;
13+
14+
use Symfony\Component\Console\Command\Command;
15+
use Symfony\Component\Console\Exception\InvalidArgumentException;
16+
use Symfony\Component\Console\Input\InputArgument;
17+
use Symfony\Component\Console\Input\InputInterface;
18+
use Symfony\Component\Console\Output\OutputInterface;
19+
use Symfony\Component\Console\Style\SymfonyStyle;
20+
use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface;
21+
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
22+
23+
/**
24+
* A console command for retrieving information about error renderers.
25+
*
26+
* @author Yonel Ceruto <yonelceruto@gmail.com>
27+
*/
28+
class DebugCommand extends Command
29+
{
30+
protected static $defaultName = 'debug:error-renderer';
31+
32+
private $renderers;
33+
34+
/**
35+
* @param ErrorRendererInterface[] $renderers
36+
*/
37+
public function __construct(array $renderers)
38+
{
39+
$this->renderers = $renderers;
40+
41+
parent::__construct();
42+
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
protected function configure(): void
48+
{
49+
$this
50+
->addArgument('format', InputArgument::OPTIONAL, sprintf('Outputs a sample in a specific format (one of %s)', implode(', ', array_keys($this->renderers))))
51+
->setDescription('Displays all available error renderers and their formats.')
52+
->setHelp(<<<'EOF'
53+
The <info>%command.name%</info> command displays all available error renderers and
54+
their formats:
55+
56+
<info>php %command.full_name%</info>
57+
58+
Or outputs a sample in a specific format:
59+
60+
<info>php %command.full_name% format</info>
61+
62+
EOF
63+
)
64+
;
65+
}
66+
67+
/**
68+
* {@inheritdoc}
69+
*/
70+
protected function execute(InputInterface $input, OutputInterface $output)
71+
{
72+
$io = new SymfonyStyle($input, $output);
73+
$renderers = $this->renderers;
74+
75+
if ($format = $input->getArgument('format')) {
76+
if (!isset($renderers[$format])) {
77+
throw new InvalidArgumentException(sprintf('No error renderer found for format "%s". Known format are %s.', $format, implode(', ', array_keys($this->renderers))));
78+
}
79+
80+
$exception = FlattenException::createFromThrowable(new \Exception('Something has intentionally gone wrong.'), 500, ['X-Debug' => false]);
81+
$io->writeln($renderers[$format]->render($exception));
82+
} else {
83+
$tableRows = [];
84+
foreach ($renderers as $format => $renderer) {
85+
$tableRows[] = [sprintf('<fg=cyan>%s</fg=cyan>', $format), \get_class($renderer)];
86+
}
87+
88+
$io->title('ErrorRenderer');
89+
$io->text('The following error renderers are available:');
90+
$io->newLine();
91+
$io->table(['Format', 'Class'], $tableRows);
92+
}
93+
}
94+
}

src/Symfony/Component/ErrorRenderer/DependencyInjection/ErrorRendererPass.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ class ErrorRendererPass implements CompilerPassInterface
2424
{
2525
private $rendererService;
2626
private $rendererTag;
27+
private $debugCommandService;
2728

28-
public function __construct(string $rendererService = 'error_renderer', string $rendererTag = 'error_renderer.renderer')
29+
public function __construct(string $rendererService = 'error_renderer', string $rendererTag = 'error_renderer.renderer', string $debugCommandService = 'console.command.error_renderer_debug')
2930
{
3031
$this->rendererService = $rendererService;
3132
$this->rendererTag = $rendererTag;
33+
$this->debugCommandService = $debugCommandService;
3234
}
3335

3436
/**
@@ -40,27 +42,30 @@ public function process(ContainerBuilder $container)
4042
return;
4143
}
4244

43-
$renderers = $registered = [];
45+
$renderers = [];
4446
foreach ($container->findTaggedServiceIds($this->rendererTag, true) as $serviceId => $tags) {
4547
/** @var ErrorRendererInterface $class */
4648
$class = $container->getDefinition($serviceId)->getClass();
4749

4850
foreach ($tags as $tag) {
4951
$format = $tag['format'] ?? $class::getFormat();
50-
if (!isset($registered[$format])) {
51-
$priority = $tag['priority'] ?? 0;
52+
$priority = $tag['priority'] ?? 0;
53+
if (!isset($renderers[$priority][$format])) {
5254
$renderers[$priority][$format] = new Reference($serviceId);
53-
$registered[$format] = true;
5455
}
5556
}
5657
}
5758

5859
if ($renderers) {
59-
krsort($renderers);
60+
ksort($renderers);
6061
$renderers = array_merge(...$renderers);
6162
}
6263

6364
$definition = $container->getDefinition($this->rendererService);
6465
$definition->replaceArgument(0, ServiceLocatorTagPass::register($container, $renderers));
66+
67+
if ($container->hasDefinition($this->debugCommandService)) {
68+
$container->getDefinition($this->debugCommandService)->replaceArgument(0, $renderers);
69+
}
6570
}
6671
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\ErrorRenderer\Tests\Command;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Console\Application;
16+
use Symfony\Component\Console\Tester\CommandTester;
17+
use Symfony\Component\ErrorRenderer\Command\DebugCommand;
18+
use Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer;
19+
use Symfony\Component\ErrorRenderer\ErrorRenderer\TxtErrorRenderer;
20+
use Symfony\Component\ErrorRenderer\ErrorRenderer\XmlErrorRenderer;
21+
22+
class DebugCommandTest extends TestCase
23+
{
24+
public function testDebugDefaults()
25+
{
26+
$tester = $this->createCommandTester();
27+
$ret = $tester->execute([], ['decorated' => false]);
28+
29+
$this->assertEquals(0, $ret, 'Returns 0 in case of success');
30+
$this->assertSame(<<<TXT
31+
32+
ErrorRenderer
33+
=============
34+
35+
The following error renderers are available:
36+
37+
-------- -----------------------------------------------------------------
38+
Format Class
39+
-------- -----------------------------------------------------------------
40+
json Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer
41+
xml Symfony\Component\ErrorRenderer\ErrorRenderer\XmlErrorRenderer
42+
txt Symfony\Component\ErrorRenderer\ErrorRenderer\TxtErrorRenderer
43+
-------- -----------------------------------------------------------------
44+
45+
46+
TXT
47+
, $tester->getDisplay(true));
48+
}
49+
50+
public function testDebugDeprecatedDefaults()
51+
{
52+
$tester = $this->createCommandTester();
53+
$ret = $tester->execute(['format' => 'json'], ['decorated' => false]);
54+
55+
$this->assertEquals(0, $ret, 'Returns 0 in case of success');
56+
$this->assertSame(<<<TXT
57+
{
58+
"title": "Internal Server Error",
59+
"status": 500,
60+
"detail": "Something has intentionally gone wrong."
61+
}
62+
63+
TXT
64+
, $tester->getDisplay(true));
65+
}
66+
67+
private function createCommandTester()
68+
{
69+
$command = new DebugCommand([
70+
'json' => new JsonErrorRenderer(false),
71+
'xml' => new XmlErrorRenderer(false),
72+
'txt' => new TxtErrorRenderer(false),
73+
]);
74+
75+
$application = new Application();
76+
$application->add($command);
77+
78+
return new CommandTester($application->find('debug:error-renderer'));
79+
}
80+
81+
/**
82+
* @expectedException \Symfony\Component\Console\Exception\InvalidArgumentException
83+
* @expectedExceptionMessage No error renderer found for format "foo". Known format are json, xml, txt.
84+
*/
85+
public function testDebugSingleFormTypeNotFound()
86+
{
87+
$tester = $this->createCommandTester();
88+
$tester->execute(['format' => 'foo'], ['decorated' => false]);
89+
}
90+
}

src/Symfony/Component/ErrorRenderer/Tests/DependencyInjection/ErrorRendererPassTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,22 @@ public function testProcess()
4848
];
4949
$this->assertEquals($expected, $serviceLocatorDefinition->getArgument(0));
5050
}
51+
52+
public function testServicesAreOrderedAccordingToPriority()
53+
{
54+
$container = new ContainerBuilder();
55+
56+
$definition = $container->register('error_renderer')->setArguments([null]);
57+
$container->register('r2')->addTag('error_renderer.renderer', ['format' => 'json', 'priority' => 100]);
58+
$container->register('r1')->addTag('error_renderer.renderer', ['format' => 'json', 'priority' => 200]);
59+
$container->register('r3')->addTag('error_renderer.renderer', ['format' => 'json']);
60+
61+
(new ErrorRendererPass())->process($container);
62+
63+
$expected = [
64+
'json' => new ServiceClosureArgument(new Reference('r1')),
65+
];
66+
$serviceLocatorDefinition = $container->getDefinition((string) $definition->getArgument(0));
67+
$this->assertEquals($expected, $serviceLocatorDefinition->getArgument(0));
68+
}
5169
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy