bug #32232 [ErrorCatcher] some cleanup and better doc (Tobion)

This PR was merged into the 4.4 branch.

Discussion
----------

[ErrorCatcher] some cleanup and better doc

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | no
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| BC breaks?    | no     <!-- see https://symfony.com/bc -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets |
| License       | MIT
| Doc PR        |

Commits
-------

a609f57c03 [ErrorCatcher] some cleanup and better doc
This commit is contained in:
Fabien Potencier 2019-06-28 07:52:45 +02:00
commit 7f6ed32a1d
10 changed files with 47 additions and 47 deletions

View File

@ -22,7 +22,7 @@
<argument type="service" id="debug.file_link_formatter" /> <argument type="service" id="debug.file_link_formatter" />
<argument>%kernel.debug%</argument> <argument>%kernel.debug%</argument>
<argument>%kernel.charset%</argument> <argument>%kernel.charset%</argument>
<argument type="service" id="error_handler.error_renderer" on-invalid="null" /> <argument type="service" id="error_catcher.error_formatter" on-invalid="null" />
</service> </service>
<service id="debug.file_link_formatter" class="Symfony\Component\HttpKernel\Debug\FileLinkFormatter"> <service id="debug.file_link_formatter" class="Symfony\Component\HttpKernel\Debug\FileLinkFormatter">

View File

@ -5,7 +5,7 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd"> xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
<services> <services>
<service id="error_catcher.error_renderer" class="Symfony\Component\ErrorCatcher\DependencyInjection\ErrorRenderer"> <service id="error_catcher.error_formatter" class="Symfony\Component\ErrorCatcher\DependencyInjection\LazyLoadingErrorFormatter">
<argument /> <!-- error renderer locator --> <argument /> <!-- error renderer locator -->
</service> </service>

View File

@ -25,7 +25,7 @@ class ErrorCatcherPass implements CompilerPassInterface
private $rendererService; private $rendererService;
private $rendererTag; private $rendererTag;
public function __construct(string $rendererService = 'error_catcher.error_renderer', string $rendererTag = 'error_catcher.renderer') public function __construct(string $rendererService = 'error_catcher.error_formatter', string $rendererTag = 'error_catcher.renderer')
{ {
$this->rendererService = $rendererService; $this->rendererService = $rendererService;
$this->rendererTag = $rendererTag; $this->rendererTag = $rendererTag;

View File

@ -12,14 +12,14 @@
namespace Symfony\Component\ErrorCatcher\DependencyInjection; namespace Symfony\Component\ErrorCatcher\DependencyInjection;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Symfony\Component\ErrorCatcher\ErrorRenderer\ErrorRenderer as BaseErrorRenderer; use Symfony\Component\ErrorCatcher\ErrorRenderer\ErrorFormatter;
/** /**
* Lazily loads error renderers from the dependency injection container. * Lazily loads error renderers from the dependency injection container.
* *
* @author Yonel Ceruto <yonelceruto@gmail.com> * @author Yonel Ceruto <yonelceruto@gmail.com>
*/ */
class ErrorRenderer extends BaseErrorRenderer class LazyLoadingErrorFormatter extends ErrorFormatter
{ {
private $container; private $container;
private $initialized = []; private $initialized = [];

View File

@ -15,33 +15,38 @@ use Symfony\Component\ErrorCatcher\Exception\ErrorRendererNotFoundException;
use Symfony\Component\ErrorCatcher\Exception\FlattenException; use Symfony\Component\ErrorCatcher\Exception\FlattenException;
/** /**
* Renders an Exception that represents a Response content. * Formats an exception to be used as response content.
*
* It delegates to implementations of ErrorRendererInterface depending on the format.
* *
* @see ErrorRendererInterface * @see ErrorRendererInterface
* *
* @author Yonel Ceruto <yonelceruto@gmail.com> * @author Yonel Ceruto <yonelceruto@gmail.com>
*/ */
class ErrorRenderer class ErrorFormatter
{ {
private $renderers = []; private $renderers = [];
/** /**
* @param ErrorRendererInterface[] $renderers * @param ErrorRendererInterface[] $renderers
*/ */
public function __construct(array $renderers) public function __construct(iterable $renderers)
{ {
foreach ($renderers as $renderer) { foreach ($renderers as $renderer) {
if (!$renderer instanceof ErrorRendererInterface) { $this->addRenderer($renderer);
throw new \InvalidArgumentException(sprintf('Error renderer "%s" must implement "%s".', \get_class($renderer), ErrorRendererInterface::class));
}
$this->addRenderer($renderer, $renderer::getFormat());
} }
} }
public function addRenderer(ErrorRendererInterface $renderer, string $format): self /**
* Registers an error renderer that is format specific.
*
* By passing an explicit format you can register a renderer for a different format than what
* ErrorRendererInterface::getFormat() would return in order to register the same renderer for
* several format aliases.
*/
public function addRenderer(ErrorRendererInterface $renderer, string $format = null): self
{ {
$this->renderers[$format] = $renderer; $this->renderers[$format ?? $renderer::getFormat()] = $renderer;
return $this; return $this;
} }

View File

@ -14,23 +14,19 @@ namespace Symfony\Component\ErrorCatcher\ErrorRenderer;
use Symfony\Component\ErrorCatcher\Exception\FlattenException; use Symfony\Component\ErrorCatcher\Exception\FlattenException;
/** /**
* Interface implemented by all error renderers. * Interface for classes that can render errors in a specific format.
* *
* @author Yonel Ceruto <yonelceruto@gmail.com> * @author Yonel Ceruto <yonelceruto@gmail.com>
*/ */
interface ErrorRendererInterface interface ErrorRendererInterface
{ {
/** /**
* Gets the format of the content. * Gets the format this renderer can return errors as.
*
* @return string The content format
*/ */
public static function getFormat(): string; public static function getFormat(): string;
/** /**
* Renders an Exception and returns the Response content. * Returns the response content of the rendered exception.
*
* @return string The Response content as a string
*/ */
public function render(FlattenException $exception): string; public function render(FlattenException $exception): string;
} }

View File

@ -17,17 +17,17 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\ErrorCatcher\DependencyInjection\ErrorCatcherPass; use Symfony\Component\ErrorCatcher\DependencyInjection\ErrorCatcherPass;
use Symfony\Component\ErrorCatcher\DependencyInjection\ErrorRenderer; use Symfony\Component\ErrorCatcher\DependencyInjection\LazyLoadingErrorFormatter;
use Symfony\Component\ErrorCatcher\ErrorRenderer\HtmlErrorRenderer; use Symfony\Component\ErrorCatcher\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorCatcher\ErrorRenderer\JsonErrorRenderer; use Symfony\Component\ErrorCatcher\ErrorRenderer\JsonErrorRenderer;
class ErrorPassTest extends TestCase class ErrorCatcherPassTest extends TestCase
{ {
public function testProcess() public function testProcess()
{ {
$container = new ContainerBuilder(); $container = new ContainerBuilder();
$container->setParameter('kernel.debug', true); $container->setParameter('kernel.debug', true);
$definition = $container->register('error_catcher.error_renderer', ErrorRenderer::class) $definition = $container->register('error_catcher.error_formatter', LazyLoadingErrorFormatter::class)
->addArgument([]) ->addArgument([])
; ;
$container->register('error_catcher.renderer.html', HtmlErrorRenderer::class) $container->register('error_catcher.renderer.html', HtmlErrorRenderer::class)

View File

@ -12,11 +12,12 @@
namespace Symfony\Component\ErrorCatcher\Tests\DependencyInjection; namespace Symfony\Component\ErrorCatcher\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorCatcher\DependencyInjection\ErrorRenderer; use Psr\Container\ContainerInterface;
use Symfony\Component\ErrorCatcher\DependencyInjection\LazyLoadingErrorFormatter;
use Symfony\Component\ErrorCatcher\ErrorRenderer\ErrorRendererInterface; use Symfony\Component\ErrorCatcher\ErrorRenderer\ErrorRendererInterface;
use Symfony\Component\ErrorCatcher\Exception\FlattenException; use Symfony\Component\ErrorCatcher\Exception\FlattenException;
class ErrorRendererTest extends TestCase class LazyLoadingErrorFormatterTest extends TestCase
{ {
/** /**
* @expectedException \Symfony\Component\ErrorCatcher\Exception\ErrorRendererNotFoundException * @expectedException \Symfony\Component\ErrorCatcher\Exception\ErrorRendererNotFoundException
@ -24,16 +25,16 @@ class ErrorRendererTest extends TestCase
*/ */
public function testInvalidErrorRenderer() public function testInvalidErrorRenderer()
{ {
$container = $this->getMockBuilder('Psr\Container\ContainerInterface')->getMock(); $container = $this->getMockBuilder(ContainerInterface::class)->getMock();
$container->expects($this->once())->method('has')->with('foo')->willReturn(false); $container->expects($this->once())->method('has')->with('foo')->willReturn(false);
$exception = FlattenException::createFromThrowable(new \Exception('Foo')); $exception = FlattenException::createFromThrowable(new \Exception('Foo'));
(new ErrorRenderer($container))->render($exception, 'foo'); (new LazyLoadingErrorFormatter($container))->render($exception, 'foo');
} }
public function testCustomErrorRenderer() public function testCustomErrorRenderer()
{ {
$container = $this->getMockBuilder('Psr\Container\ContainerInterface')->getMock(); $container = $this->getMockBuilder(ContainerInterface::class)->getMock();
$container $container
->expects($this->once()) ->expects($this->once())
->method('has') ->method('has')
@ -46,7 +47,7 @@ class ErrorRendererTest extends TestCase
->willReturn(new FooErrorRenderer()) ->willReturn(new FooErrorRenderer())
; ;
$errorRenderer = new ErrorRenderer($container); $errorRenderer = new LazyLoadingErrorFormatter($container);
$exception = FlattenException::createFromThrowable(new \RuntimeException('Foo')); $exception = FlattenException::createFromThrowable(new \RuntimeException('Foo'));
$this->assertSame('Foo', $errorRenderer->render($exception, 'foo')); $this->assertSame('Foo', $errorRenderer->render($exception, 'foo'));

View File

@ -12,11 +12,11 @@
namespace Symfony\Component\ErrorCatcher\Tests\ErrorRenderer; namespace Symfony\Component\ErrorCatcher\Tests\ErrorRenderer;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorCatcher\ErrorRenderer\ErrorRenderer; use Symfony\Component\ErrorCatcher\ErrorRenderer\ErrorFormatter;
use Symfony\Component\ErrorCatcher\ErrorRenderer\ErrorRendererInterface; use Symfony\Component\ErrorCatcher\ErrorRenderer\ErrorRendererInterface;
use Symfony\Component\ErrorCatcher\Exception\FlattenException; use Symfony\Component\ErrorCatcher\Exception\FlattenException;
class ErrorRendererTest extends TestCase class ErrorFormatterTest extends TestCase
{ {
/** /**
* @expectedException \Symfony\Component\ErrorCatcher\Exception\ErrorRendererNotFoundException * @expectedException \Symfony\Component\ErrorCatcher\Exception\ErrorRendererNotFoundException
@ -25,23 +25,21 @@ class ErrorRendererTest extends TestCase
public function testErrorRendererNotFound() public function testErrorRendererNotFound()
{ {
$exception = FlattenException::createFromThrowable(new \Exception('foo')); $exception = FlattenException::createFromThrowable(new \Exception('foo'));
(new ErrorRenderer([]))->render($exception, 'foo'); (new ErrorFormatter([]))->render($exception, 'foo');
} }
/** /**
* @expectedException \InvalidArgumentException * @expectedException \TypeError
* @expectedExceptionMessage Error renderer "stdClass" must implement "Symfony\Component\ErrorCatcher\ErrorRenderer\ErrorRendererInterface".
*/ */
public function testInvalidErrorRenderer() public function testInvalidErrorRenderer()
{ {
$exception = FlattenException::createFromThrowable(new \Exception('foo')); new ErrorFormatter([new \stdClass()]);
(new ErrorRenderer([new \stdClass()]))->render($exception, 'foo');
} }
public function testCustomErrorRenderer() public function testCustomErrorRenderer()
{ {
$renderers = [new FooErrorRenderer()]; $renderers = [new FooErrorRenderer()];
$errorRenderer = new ErrorRenderer($renderers); $errorRenderer = new ErrorFormatter($renderers);
$exception = FlattenException::createFromThrowable(new \RuntimeException('Foo')); $exception = FlattenException::createFromThrowable(new \RuntimeException('Foo'));
$this->assertSame('Foo', $errorRenderer->render($exception, 'foo')); $this->assertSame('Foo', $errorRenderer->render($exception, 'foo'));

View File

@ -16,7 +16,7 @@ use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleEvent; use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\ErrorCatcher\ErrorHandler; use Symfony\Component\ErrorCatcher\ErrorHandler;
use Symfony\Component\ErrorCatcher\ErrorRenderer\ErrorRenderer; use Symfony\Component\ErrorCatcher\ErrorRenderer\ErrorFormatter;
use Symfony\Component\ErrorCatcher\ErrorRenderer\HtmlErrorRenderer; use Symfony\Component\ErrorCatcher\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorCatcher\Exception\ErrorRendererNotFoundException; use Symfony\Component\ErrorCatcher\Exception\ErrorRendererNotFoundException;
use Symfony\Component\ErrorCatcher\ExceptionHandler; use Symfony\Component\ErrorCatcher\ExceptionHandler;
@ -46,7 +46,7 @@ class DebugHandlersListener implements EventSubscriberInterface
private $fileLinkFormat; private $fileLinkFormat;
private $scope; private $scope;
private $charset; private $charset;
private $errorRenderer; private $errorFormatter;
private $firstCall = true; private $firstCall = true;
private $hasTerminatedWithException; private $hasTerminatedWithException;
@ -59,7 +59,7 @@ class DebugHandlersListener implements EventSubscriberInterface
* @param string|FileLinkFormatter|null $fileLinkFormat The format for links to source files * @param string|FileLinkFormatter|null $fileLinkFormat The format for links to source files
* @param bool $scope Enables/disables scoping mode * @param bool $scope Enables/disables scoping mode
*/ */
public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = E_ALL, ?int $throwAt = E_ALL, bool $scream = true, $fileLinkFormat = null, bool $scope = true, string $charset = null, ErrorRenderer $errorRenderer = null) public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = E_ALL, ?int $throwAt = E_ALL, bool $scream = true, $fileLinkFormat = null, bool $scope = true, string $charset = null, ErrorFormatter $errorFormatter = null)
{ {
$this->exceptionHandler = $exceptionHandler; $this->exceptionHandler = $exceptionHandler;
$this->logger = $logger; $this->logger = $logger;
@ -69,7 +69,7 @@ class DebugHandlersListener implements EventSubscriberInterface
$this->fileLinkFormat = $fileLinkFormat; $this->fileLinkFormat = $fileLinkFormat;
$this->scope = $scope; $this->scope = $scope;
$this->charset = $charset; $this->charset = $charset;
$this->errorRenderer = $errorRenderer; $this->errorFormatter = $errorFormatter;
} }
/** /**
@ -167,16 +167,16 @@ class DebugHandlersListener implements EventSubscriberInterface
$debug = $this->scream && $this->scope; $debug = $this->scream && $this->scope;
$controller = function (Request $request) use ($debug) { $controller = function (Request $request) use ($debug) {
if (null === $this->errorRenderer) { if (null === $this->errorFormatter) {
$this->errorRenderer = new ErrorRenderer([new HtmlErrorRenderer($debug, $this->charset, $this->fileLinkFormat)]); $this->errorFormatter = new ErrorFormatter([new HtmlErrorRenderer($debug, $this->charset, $this->fileLinkFormat)]);
} }
$e = $request->attributes->get('exception'); $e = $request->attributes->get('exception');
try { try {
return new Response($this->errorRenderer->render($e, $request->getRequestFormat()), $e->getStatusCode(), $e->getHeaders()); return new Response($this->errorFormatter->render($e, $request->getRequestFormat()), $e->getStatusCode(), $e->getHeaders());
} catch (ErrorRendererNotFoundException $_) { } catch (ErrorRendererNotFoundException $_) {
return new Response($this->errorRenderer->render($e), $e->getStatusCode(), $e->getHeaders()); return new Response($this->errorFormatter->render($e), $e->getStatusCode(), $e->getHeaders());
} }
}; };