[ErrorHandler] merge and remove the ErrorRenderer component

This commit is contained in:
Nicolas Grekas 2019-11-10 14:46:37 +01:00
parent 10a349c37d
commit 6c9157bbc2
99 changed files with 536 additions and 1640 deletions

View File

@ -18,7 +18,6 @@ Console
Debug
-----
* Deprecated the `Debug` class, use the one from the `ErrorRenderer` component instead
* Deprecated the `FlattenException` class, use the one from the `ErrorRenderer` component instead
* Deprecated the component in favor of the `ErrorHandler` component
@ -309,45 +308,35 @@ TwigBundle
* Deprecated all built-in error templates, use the error renderer mechanism of the `ErrorRenderer` component
* Deprecated loading custom error templates in non-html formats. Custom HTML error pages based on Twig keep working as before:
Before (`templates/bundles/TwigBundle/Exception/error.jsonld.twig`):
Before (`templates/bundles/TwigBundle/Exception/error.json.twig`):
```twig
{
"@id": "https://example.com",
"@type": "error",
"@context": {
"title": "{{ status_text }}",
"code": {{ status_code }},
"message": "{{ exception.message }}"
}
"type": "https://example.com/error",
"title": "{{ status_text }}",
"status": {{ status_code }}
}
```
After (`App\ErrorRenderer\JsonLdErrorRenderer`):
After (`App\Serializer\ProblemJsonNormalizer`):
```php
class JsonLdErrorRenderer implements ErrorRendererInterface
class ProblemJsonNormalizer implements NormalizerInterface
{
public static function getFormat(): string
public function normalize($exception, $format = null, array $context = [])
{
return 'jsonld';
return [
'type' => 'https://example.com/error',
'title' => $exception->getStatusText(),
'status' => $exception->getStatusCode(),
];
}
public function render(FlattenException $exception): string
public function supportsNormalization($data, $format = null)
{
return json_encode([
'@id' => 'https://example.com',
'@type' => 'error',
'@context' => [
'title' => $exception->getTitle(),
'code' => $exception->getStatusCode(),
'message' => $exception->getMessage(),
],
]);
return 'json' === $format && $data instanceof FlattenException;
}
}
```
Configure your rendering service tagging it with `error_renderer.renderer`.
Validator
---------

View File

@ -57,7 +57,6 @@ Console
Debug
-----
* Removed the `Debug` class, use the one from the `ErrorRenderer` component instead
* Removed the `FlattenException` class, use the one from the `ErrorRenderer` component instead
* Removed the component in favor of the `ErrorHandler` component

View File

@ -48,7 +48,6 @@
"symfony/dom-crawler": "self.version",
"symfony/dotenv": "self.version",
"symfony/error-handler": "self.version",
"symfony/error-renderer": "self.version",
"symfony/event-dispatcher": "self.version",
"symfony/expression-language": "self.version",
"symfony/filesystem": "self.version",

View File

@ -11,7 +11,7 @@
namespace Symfony\Bridge\Twig\Mime;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\Mime\Header\Headers;
use Symfony\Component\Mime\Part\AbstractPart;
use Twig\Extra\CssInliner\CssInlinerExtension;

View File

@ -32,7 +32,6 @@ class UnusedTagsPass implements CompilerPassInterface
'controller.service_arguments',
'config_cache.resource_checker',
'data_collector',
'error_renderer.renderer',
'form.type',
'form.type_extension',
'form.type_guesser',

View File

@ -33,7 +33,6 @@ use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Component\ErrorRenderer\DependencyInjection\ErrorRendererPass;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\Form\DependencyInjection\FormPass;
use Symfony\Component\HttpClient\DependencyInjection\HttpClientPass;
@ -92,7 +91,6 @@ class FrameworkBundle extends Bundle
KernelEvents::FINISH_REQUEST,
];
$container->addCompilerPass(new ErrorRendererPass());
$container->addCompilerPass(new LoggerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
$container->addCompilerPass(new RegisterControllerArgumentLocatorsPass());
$container->addCompilerPass(new RemoveEmptyControllerArgumentLocatorsPass(), PassConfig::TYPE_BEFORE_REMOVING);

View File

@ -194,12 +194,6 @@
<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>
<service id="console.command.secrets_set" class="Symfony\Bundle\FrameworkBundle\Command\SecretsSetCommand">
<argument type="service" id="secrets.vault" />
<argument type="service" id="secrets.local_vault" on-invalid="ignore" />

View File

@ -5,12 +5,7 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="error_renderer" class="Symfony\Component\ErrorRenderer\DependencyInjection\LazyLoadingErrorRenderer">
<argument /> <!-- error renderer locator -->
</service>
<service id="error_renderer.renderer.html" class="Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer">
<tag name="error_renderer.renderer" />
<service id="error_handler.error_renderer.html" class="Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer">
<argument>%kernel.debug%</argument>
<argument>%kernel.charset%</argument>
<argument type="service" id="debug.file_link_formatter" on-invalid="null" />
@ -19,21 +14,15 @@
<argument type="service" id="logger" on-invalid="null" />
</service>
<service id="error_renderer.renderer.json" class="Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer">
<tag name="error_renderer.renderer" />
<service id="error_handler.error_renderer.serializer" class="Symfony\Component\ErrorHandler\ErrorRenderer\SerializerErrorRenderer">
<argument type="service" id="serializer" />
<argument type="service" id="request_stack" />
<argument type="service" id="error_renderer.html" />
<argument>%kernel.debug%</argument>
</service>
<service id="error_renderer.renderer.xml" class="Symfony\Component\ErrorRenderer\ErrorRenderer\XmlErrorRenderer">
<tag name="error_renderer.renderer" format="atom" />
<tag name="error_renderer.renderer" />
<argument>%kernel.debug%</argument>
<argument>%kernel.charset%</argument>
</service>
<service id="error_renderer.renderer.txt" class="Symfony\Component\ErrorRenderer\ErrorRenderer\TxtErrorRenderer">
<tag name="error_renderer.renderer" />
<argument>%kernel.debug%</argument>
</service>
<service id="error_renderer.html" alias="error_handler.error_renderer.html" />
<service id="error_renderer.serializer" alias="error_handler.error_renderer.serializer" />
<service id="error_renderer" alias="error_renderer.html" />
</services>
</container>

View File

@ -12,6 +12,8 @@
<services>
<defaults public="false" />
<service id="error_renderer" alias="error_renderer.serializer" />
<service id="serializer" class="Symfony\Component\Serializer\Serializer" public="true">
<argument type="collection" />
<argument type="collection" />
@ -59,6 +61,12 @@
<tag name="serializer.normalizer" priority="-900" />
</service>
<service id="serializer.normalizer.problem" class="Symfony\Component\Serializer\Normalizer\ProblemNormalizer">
<argument>%kernel.debug%</argument>
<!-- Run before serializer.normalizer.object -->
<tag name="serializer.normalizer" priority="-890" />
</service>
<service id="serializer.normalizer.object" class="Symfony\Component\Serializer\Normalizer\ObjectNormalizer">
<argument type="service" id="serializer.mapping.class_metadata_factory" />
<argument type="service" id="serializer.name_converter.metadata_aware" />

View File

@ -21,7 +21,6 @@
"symfony/cache": "^4.4|^5.0",
"symfony/config": "^4.3.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/error-renderer": "^4.4|^5.0",
"symfony/http-foundation": "^4.4|^5.0",
"symfony/http-kernel": "^4.4",
"symfony/polyfill-mbstring": "~1.0",
@ -50,7 +49,7 @@
"symfony/process": "^3.4|^4.0|^5.0",
"symfony/security-csrf": "^3.4|^4.0|^5.0",
"symfony/security-http": "^3.4|^4.0|^5.0",
"symfony/serializer": "^4.3|^5.0",
"symfony/serializer": "^4.4|^5.0",
"symfony/stopwatch": "^3.4|^4.0|^5.0",
"symfony/translation": "^4.4|^5.0",
"symfony/templating": "^3.4|^4.0|^5.0",

View File

@ -70,6 +70,6 @@ class JsonLoginTest extends AbstractWebTestCase
$this->assertSame(400, $response->getStatusCode());
$this->assertSame('application/json', $response->headers->get('Content-Type'));
$this->assertSame(['title' => 'Bad Request', 'status' => 400, 'detail' => 'Whoops, looks like something went wrong.'], json_decode($response->getContent(), true));
$this->assertSame(['type' => 'https://tools.ietf.org/html/rfc2616#section-10', 'title' => 'An error occurred', 'status' => 400, 'detail' => 'Bad Request'], json_decode($response->getContent(), true));
}
}

View File

@ -1,6 +1,9 @@
imports:
- { resource: ./../config/framework.yml }
framework:
serializer: ~
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext

View File

@ -35,6 +35,7 @@
"symfony/form": "^3.4|^4.0|^5.0",
"symfony/framework-bundle": "^4.4|^5.0",
"symfony/http-foundation": "^3.4|^4.0|^5.0",
"symfony/serializer": "^4.4|^5.0",
"symfony/translation": "^3.4|^4.0|^5.0",
"symfony/twig-bundle": "^4.4|^5.0",
"symfony/twig-bridge": "^3.4|^4.0|^5.0",

View File

@ -6,8 +6,8 @@ CHANGELOG
* marked the `TemplateIterator` as `internal`
* added HTML comment to beginning and end of `exception_full.html.twig`
* added a new `TwigHtmlErrorRenderer` for `html` format, integrated with the `ErrorRenderer` component
* deprecated `ExceptionController` and `PreviewErrorController` controllers, use `ErrorController` from the `HttpKernel` component instead
* added a new `TwigHtmlErrorRenderer` for `html` format, integrated with the `ErrorHandler` component
* deprecated `ExceptionController` and `PreviewErrorController` controllers, use `ErrorController` from the `HttpKernel` component instead
* deprecated all built-in error templates in favor of the new error renderer mechanism
* deprecated `twig.exception_controller` configuration option, set it to "null" and use `framework.error_controller` configuration instead

View File

@ -11,9 +11,9 @@
namespace Symfony\Bundle\TwigBundle\ErrorRenderer;
use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface;
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Loader\ExistsLoaderInterface;
@ -40,34 +40,20 @@ class TwigHtmlErrorRenderer implements ErrorRendererInterface
/**
* {@inheritdoc}
*/
public static function getFormat(): string
public function render(\Throwable $exception): FlattenException
{
return 'html';
}
$exception = $this->htmlErrorRenderer->render($exception);
/**
* {@inheritdoc}
*/
public function render(FlattenException $exception): string
{
$debug = $this->debug && ($exception->getHeaders()['X-Debug'] ?? true);
if ($debug) {
return $this->htmlErrorRenderer->render($exception);
if ($this->debug || !$template = $this->findTemplate($exception->getStatusCode());
return $exception;
}
$template = $this->findTemplate($exception->getStatusCode());
if (null === $template) {
return $this->htmlErrorRenderer->render($exception);
}
return $this->twig->render($template, [
return $exception->setAsString($this->twig->render($template, [
'legacy' => false, // to be removed in 5.0
'exception' => $exception,
'status_code' => $exception->getStatusCode(),
'status_text' => $exception->getTitle(),
]);
'status_text' => $exception->getStatusText(),
]));
}
private function findTemplate(int $statusCode): ?string

View File

@ -162,10 +162,9 @@
<argument /> <!-- runtime locator -->
</service>
<service id="twig.error_renderer.html" class="Symfony\Bundle\TwigBundle\ErrorRenderer\TwigHtmlErrorRenderer">
<tag name="error_renderer.renderer" priority="1" />
<service id="twig.error_renderer.html" class="Symfony\Bundle\TwigBundle\ErrorRenderer\TwigHtmlErrorRenderer" decorates="error_renderer.html">
<argument type="service" id="twig" />
<argument type="service" id="error_renderer.renderer.html" />
<argument type="service" id="twig.error_renderer.html.inner" />
<argument>%kernel.debug%</argument>
</service>
</services>

View File

@ -13,8 +13,7 @@ namespace Symfony\Bundle\TwigBundle\Tests\ErrorRenderer;
use PHPUnit\Framework\TestCase;
use Symfony\Bundle\TwigBundle\ErrorRenderer\TwigHtmlErrorRenderer;
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Twig\Environment;
use Twig\Loader\ArrayLoader;
@ -23,7 +22,7 @@ class TwigHtmlErrorRendererTest extends TestCase
{
public function testFallbackToNativeRendererIfDebugOn()
{
$exception = FlattenException::createFromThrowable(new \Exception());
$exception = new \Exception();
$twig = $this->createMock(Environment::class);
$nativeRenderer = $this->createMock(HtmlErrorRenderer::class);
@ -33,12 +32,12 @@ class TwigHtmlErrorRendererTest extends TestCase
->with($exception)
;
(new TwigHtmlErrorRenderer($twig, $nativeRenderer, true))->render($exception);
(new TwigHtmlErrorRenderer($twig, $nativeRenderer, true))->render(new \Exception());
}
public function testFallbackToNativeRendererIfCustomTemplateNotFound()
{
$exception = FlattenException::createFromThrowable(new NotFoundHttpException());
$exception = new NotFoundHttpException();
$twig = new Environment(new ArrayLoader([]));
@ -54,7 +53,7 @@ class TwigHtmlErrorRendererTest extends TestCase
public function testRenderCustomErrorTemplate()
{
$exception = FlattenException::createFromThrowable(new NotFoundHttpException());
$exception = new NotFoundHttpException();
$twig = new Environment(new ArrayLoader([
'@Twig/Exception/error404.html.twig' => '<h1>Page Not Found</h1>',
@ -68,6 +67,6 @@ class TwigHtmlErrorRendererTest extends TestCase
$content = (new TwigHtmlErrorRenderer($twig, $nativeRenderer, false))->render($exception);
$this->assertSame('<h1>Page Not Found</h1>', $content);
$this->assertSame('<h1>Page Not Found</h1>', $content->getAsString());
}
}

View File

@ -15,7 +15,7 @@ use Symfony\Bundle\TwigBundle\Tests\TestCase;
use Symfony\Bundle\TwigBundle\TwigBundle;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\ErrorRenderer\ErrorRenderer;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\Kernel;
@ -65,7 +65,8 @@ class EmptyAppKernel extends Kernel
'strict_variables' => false,
'exception_controller' => null,
]);
$container->register('error_renderer', ErrorRenderer::class);
$container->register('error_renderer.html', HtmlErrorRenderer::class);
$container->setAlias('error_renderer', 'error_renderer.html');
$container->setParameter('debug.file_link_format', null);
});
}

View File

@ -17,7 +17,6 @@
],
"require": {
"php": "^7.1.3",
"symfony/error-renderer": "^4.4|^5.0",
"symfony/twig-bridge": "^4.4|^5.0",
"symfony/http-foundation": "^4.3|^5.0",
"symfony/http-kernel": "^4.4",

View File

@ -11,7 +11,7 @@
namespace Symfony\Bundle\WebProfilerBundle\Controller;
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@ -42,11 +42,7 @@ class ExceptionController
$this->profiler = $profiler;
$this->twig = $twig;
$this->debug = $debug;
$this->errorRenderer = $errorRenderer;
if (null === $errorRenderer) {
$this->errorRenderer = new HtmlErrorRenderer($debug, $this->twig->getCharset(), $fileLinkFormat);
}
$this->errorRenderer = $errorRenderer ?? new HtmlErrorRenderer($debug, $this->twig->getCharset(), $fileLinkFormat);
}
/**

View File

@ -11,7 +11,7 @@
namespace Symfony\Bundle\WebProfilerBundle\Controller;
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Profiler\Profiler;
@ -25,12 +25,12 @@ use Symfony\Component\HttpKernel\Profiler\Profiler;
*/
class ExceptionPanelController
{
private $htmlErrorRenderer;
private $errorRenderer;
private $profiler;
public function __construct(HtmlErrorRenderer $htmlErrorRenderer, ?Profiler $profiler)
public function __construct(HtmlErrorRenderer $errorRenderer, Profiler $profiler = null)
{
$this->htmlErrorRenderer = $htmlErrorRenderer;
$this->errorRenderer = $errorRenderer;
$this->profiler = $profiler;
}
@ -48,7 +48,7 @@ class ExceptionPanelController
->getException()
;
return new Response($this->htmlErrorRenderer->getBody($exception), 200, ['Content-Type' => 'text/html']);
return new Response($this->errorRenderer->getBody($exception), 200, ['Content-Type' => 'text/html']);
}
/**
@ -56,6 +56,6 @@ class ExceptionPanelController
*/
public function stylesheet(): Response
{
return new Response($this->htmlErrorRenderer->getStylesheet(), 200, ['Content-Type' => 'text/css']);
return new Response($this->errorRenderer->getStylesheet(), 200, ['Content-Type' => 'text/css']);
}
}

View File

@ -27,12 +27,12 @@
<argument type="service" id="twig" />
<argument>%kernel.debug%</argument>
<argument type="service" id="debug.file_link_formatter" />
<argument type="service" id="error_renderer.renderer.html" />
<argument type="service" id="error_handler.error_renderer.html" />
<deprecated>The "%service_id%" service is deprecated since Symfony 4.4, use the "web_profiler.controller.exception_panel" service instead.</deprecated>
</service>
<service id="web_profiler.controller.exception_panel" class="Symfony\Bundle\WebProfilerBundle\Controller\ExceptionPanelController" public="true">
<argument type="service" id="error_renderer.renderer.html" />
<argument type="service" id="error_handler.error_renderer.html" />
<argument type="service" id="profiler" on-invalid="null" />
</service>

View File

@ -17,7 +17,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\EventDispatcher\EventDispatcher;
@ -54,7 +54,7 @@ class WebProfilerExtensionTest extends TestCase
$this->kernel = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\KernelInterface')->getMock();
$this->container = new ContainerBuilder();
$this->container->register('error_renderer.renderer.html', HtmlErrorRenderer::class)->setPublic(true);
$this->container->register('error_renderer.html', HtmlErrorRenderer::class)->setPublic(true);
$this->container->register('event_dispatcher', EventDispatcher::class)->setPublic(true);
$this->container->register('router', $this->getMockClass('Symfony\\Component\\Routing\\RouterInterface'))->setPublic(true);
$this->container->register('twig', 'Twig\Environment')->setPublic(true);

View File

@ -18,7 +18,6 @@
"require": {
"php": "^7.1.3",
"symfony/config": "^4.2|^5.0",
"symfony/error-renderer": "^4.4|^5.0",
"symfony/http-kernel": "^4.4",
"symfony/routing": "^3.4|^4.0|^5.0",
"symfony/twig-bundle": "^4.2|^5.0",

View File

@ -4,7 +4,7 @@ CHANGELOG
4.4.0
-----
* deprecated `FlattenException`, use the `FlattenException` of the `ErrorRenderer` component
* deprecated `FlattenException`, use the `FlattenException` of the `ErrorHandler` component
* deprecated the whole component in favor of the `ErrorHandler` component
4.3.0

View File

@ -21,7 +21,7 @@ use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorRenderer\Exception\FlattenException instead.
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\FlattenException instead.
*/
class FlattenException
{

View File

@ -34,4 +34,25 @@ class BufferingLogger extends AbstractLogger
return $logs;
}
public function __destruct()
{
foreach ($this->logs as [$level, $message, $context]) {
if (false !== strpos($message, '{')) {
foreach ($context as $key => $val) {
if (null === $val || is_scalar($val) || (\is_object($val) && \is_callable([$val, '__toString']))) {
$message = str_replace("{{$key}}", $val, $message);
} elseif ($val instanceof \DateTimeInterface) {
$message = str_replace("{{$key}}", $val->format(\DateTime::RFC3339), $message);
} elseif (\is_object($val)) {
$message = str_replace("{{$key}}", '[object '.\get_class($val).']', $message);
} else {
$message = str_replace("{{$key}}", '['.\gettype($val).']', $message);
}
}
}
error_log(sprintf('%s [%s] %s', date(\DateTime::RFC3339), $level, $message));
}
}
}

View File

@ -18,39 +18,19 @@ namespace Symfony\Component\ErrorHandler;
*/
class Debug
{
private static $enabled = false;
/**
* Enables the debug tools.
*
* This method registers an error handler and an exception handler.
*/
public static function enable(int $errorReportingLevel = E_ALL, bool $displayErrors = true): void
public static function enable(): ErrorHandler
{
if (static::$enabled) {
return;
}
static::$enabled = true;
if (null !== $errorReportingLevel) {
error_reporting($errorReportingLevel);
} else {
error_reporting(E_ALL);
}
error_reporting(-1);
if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
ini_set('display_errors', 0);
} elseif ($displayErrors && (!filter_var(ini_get('log_errors'), FILTER_VALIDATE_BOOLEAN) || ini_get('error_log'))) {
} elseif (!filter_var(ini_get('log_errors'), FILTER_VALIDATE_BOOLEAN) || ini_get('error_log')) {
// CLI - display errors only if they're not already logged to STDERR
ini_set('display_errors', 1);
}
if ($displayErrors) {
ErrorHandler::register(new ErrorHandler(new BufferingLogger()));
} else {
ErrorHandler::register()->throwAt(0, true);
}
DebugClassLoader::enable();
return ErrorHandler::register(new ErrorHandler(new BufferingLogger()));
}
}

View File

@ -19,9 +19,10 @@ use Symfony\Component\ErrorHandler\ErrorEnhancer\ClassNotFoundErrorEnhancer;
use Symfony\Component\ErrorHandler\ErrorEnhancer\ErrorEnhancerInterface;
use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedFunctionErrorEnhancer;
use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedMethodErrorEnhancer;
use Symfony\Component\ErrorHandler\ErrorRenderer\CliErrorRenderer;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
/**
* A generic ErrorHandler for the PHP engine.
@ -45,10 +46,8 @@ use Symfony\Component\ErrorRenderer\Exception\FlattenException;
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @final since Symfony 4.3
*/
class ErrorHandler
final class ErrorHandler
{
private $levels = [
E_DEPRECATED => 'Deprecated',
@ -145,10 +144,8 @@ class ErrorHandler
$handler->setExceptionHandler($p);
$prev[0]->setExceptionHandler($p);
}
} elseif (null === $prev && !\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
$handler->setExceptionHandler([$handler, 'sendPhpResponse']);
} else {
$handler->setExceptionHandler($prev);
$handler->setExceptionHandler($prev ?? [$handler, 'renderException']);
}
$handler->throwAt(E_ALL & $handler->thrownErrors, true);
@ -280,7 +277,7 @@ class ErrorHandler
/**
* Sets a user exception handler.
*
* @param callable|null $handler A handler that must support \Throwable instances that will be called on Exception
* @param callable(\Throwable $e)|null $handler
*
* @return callable|null The previous exception handler
*/
@ -583,11 +580,7 @@ class ErrorHandler
}
$exceptionHandler = $this->exceptionHandler;
if ((!\is_array($exceptionHandler) || !$exceptionHandler[0] instanceof self || 'sendPhpResponse' !== $exceptionHandler[1]) && !\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
$this->exceptionHandler = [$this, 'sendPhpResponse'];
} else {
$this->exceptionHandler = null;
}
$this->exceptionHandler = null;
try {
if (null !== $exceptionHandler) {
return $exceptionHandler($exception);
@ -684,36 +677,26 @@ class ErrorHandler
}
/**
* Sends the error associated with the given Exception as a plain PHP response.
* Renders the given exception.
*
* As this method is mainly called during Kernel boot, where nothing is yet
* available, the Response content is always HTML.
* As this method is mainly called during boot where nothing is yet available,
* the output is always either HTML or CLI depending where PHP runs.
*/
private function sendPhpResponse(\Throwable $exception)
private function renderException(\Throwable $exception): void
{
$charset = ini_get('default_charset') ?: 'UTF-8';
$statusCode = 500;
$headers = [];
$renderer = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliErrorRenderer() : new HtmlErrorRenderer(0 !== $this->scopedErrors);
if (class_exists(HtmlErrorRenderer::class)) {
$exception = FlattenException::createFromThrowable($exception);
$statusCode = $exception->getStatusCode();
$headers = $exception->getHeaders();
$response = (new HtmlErrorRenderer(0 !== $this->scopedErrors))->render($exception);
} else {
$message = htmlspecialchars($exception->getMessage(), ENT_COMPAT | ENT_SUBSTITUTE, $charset);
$response = sprintf('<!DOCTYPE html><html><head><meta charset="%s" /><meta name="robots" content="noindex,nofollow" /></head><body>%s</body></html>', $charset, $message);
}
$exception = $renderer->render($exception);
if (!headers_sent()) {
header(sprintf('HTTP/1.0 %s', $statusCode));
foreach ($headers as $name => $value) {
http_response_code($exception->getStatusCode());
foreach ($exception->getHeaders() as $name => $value) {
header($name.': '.$value, false);
}
header('Content-Type: text/html; charset='.$charset);
}
echo $response;
echo $exception->getAsString();
}
/**

View File

@ -0,0 +1,46 @@
<?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\ErrorHandler\ErrorRenderer;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class CliErrorRenderer implements ErrorRendererInterface
{
/**
* {@inheritdoc}
*/
public function render(\Throwable $exception): FlattenException
{
$cloner = new VarCloner();
$dumper = new class() extends CliDumper {
protected function supportsColors(): bool
{
$outputStream = $this->outputStream;
$this->outputStream = STDOUT;
try {
return parent::supportsColors();
} finally {
$this->outputStream = $outputStream;
}
}
};
return FlattenException::createFromThrowable($exception)
->setAsString($dumper->dump($cloner->cloneVar($exception), true));
}
}

View File

@ -0,0 +1,27 @@
<?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\ErrorHandler\ErrorRenderer;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
/**
* Formats an exception to be used as response content.
*
* @author Yonel Ceruto <yonelceruto@gmail.com>
*/
interface ErrorRendererInterface
{
/**
* Renders a Throwable as a FlattenException.
*/
public function render(\Throwable $exception): FlattenException;
}

View File

@ -9,10 +9,10 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorRenderer\ErrorRenderer;
namespace Symfony\Component\ErrorHandler\ErrorRenderer;
use Psr\Log\LoggerInterface;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
@ -52,23 +52,17 @@ class HtmlErrorRenderer implements ErrorRendererInterface
/**
* {@inheritdoc}
*/
public static function getFormat(): string
public function render(\Throwable $exception): FlattenException
{
return 'html';
}
/**
* {@inheritdoc}
*/
public function render(FlattenException $exception): string
{
return $this->renderException($exception);
$exception = FlattenException::createFromThrowable($exception, null, [
'Content-Type' => 'text/html; charset='.$this->charset,
]);
return $exception->setAsString($this->renderException($exception));
}
/**
* Gets the HTML content associated with the given exception.
*
* @internal
*/
public function getBody(FlattenException $exception): string
{
@ -77,8 +71,6 @@ class HtmlErrorRenderer implements ErrorRendererInterface
/**
* Gets the stylesheet associated with the given exception.
*
* @internal
*/
public function getStylesheet(): string
{
@ -91,11 +83,10 @@ class HtmlErrorRenderer implements ErrorRendererInterface
private function renderException(FlattenException $exception, string $debugTemplate = 'views/exception_full.html.php'): string
{
$debug = $this->debug && ($exception->getHeaders()['X-Debug'] ?? true);
$statusText = $this->escape($exception->getTitle());
$statusText = $this->escape($exception->getStatusText());
$statusCode = $this->escape($exception->getStatusCode());
if (!$debug) {
if (!$this->debug) {
return $this->include('views/error.html.php', [
'statusText' => $statusText,
'statusCode' => $statusCode,
@ -111,13 +102,13 @@ class HtmlErrorRenderer implements ErrorRendererInterface
'statusText' => $statusText,
'statusCode' => $statusCode,
'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null,
'currentContent' => $request ? $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level')) : null,
'currentContent' => $request ? $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level')) : '',
]);
}
private function getAndCleanOutputBuffering(int $startObLevel): string
private function getAndCleanOutputBuffering(?int $startObLevel): string
{
if (ob_get_level() <= $startObLevel) {
if (null === $startObLevel || ob_get_level() <= $startObLevel) {
return '';
}
@ -321,7 +312,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
{
extract($context, EXTR_SKIP);
ob_start();
include __DIR__.'/../Resources/'.$name;
include __DIR__ . '/../Resources/' .$name;
return trim(ob_get_clean());
}

View File

@ -0,0 +1,50 @@
<?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\ErrorHandler\ErrorRenderer;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
use Symfony\Component\Serializer\SerializerInterface;
/**
* Formats an exception using Serializer for rendering.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class SerializerErrorRenderer
{
private $serializer;
private $requestStack;
private $debug;
public function __construct(SerializerInterface $serializer, RequestStack $requestStack, bool $debug = true)
{
$this->serializer = $serializer;
$this->requestStack = $requestStack;
$this->debug = $debug;
}
/**
* {@inheritdoc}
*/
public function render(\Throwable $exception): FlattenException
{
$format = $this->requestStack->getCurrentRequest()->getPreferredFormat();
$flattenException = FlattenException::createFromThrowable($exception);
try {
return $flattenException->setAsString($this->serializer->serialize($flattenException, $format, ['exception' => $exception]));
} catch (NotEncodableValueException $_) {
return (new HtmlErrorHandler($this->debug))->render($exception);
}
}
}

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorRenderer\Exception;
namespace Symfony\Component\ErrorHandler\Exception;
use Symfony\Component\Debug\Exception\FlattenException as LegacyFlattenException;
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
@ -25,7 +25,6 @@ use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
*/
class FlattenException extends LegacyFlattenException
{
private $title;
private $message;
private $code;
private $previous;
@ -33,9 +32,11 @@ class FlattenException extends LegacyFlattenException
private $traceAsString;
private $class;
private $statusCode;
private $statusText;
private $headers;
private $file;
private $line;
private $asString;
public static function create(\Exception $exception, $statusCode = null, array $headers = []): self
{
@ -60,12 +61,12 @@ class FlattenException extends LegacyFlattenException
}
if (class_exists(Response::class) && isset(Response::$statusTexts[$statusCode])) {
$title = Response::$statusTexts[$statusCode];
$statusText = Response::$statusTexts[$statusCode];
} else {
$title = 'Whoops, looks like something went wrong.';
$statusText = 'Whoops, looks like something went wrong.';
}
$e->setTitle($title);
$e->setStatusText($statusText);
$e->setStatusCode($statusCode);
$e->setHeaders($headers);
$e->setTraceFromThrowable($exception);
@ -171,14 +172,14 @@ class FlattenException extends LegacyFlattenException
return $this;
}
public function getTitle()
public function getStatusText()
{
return $this->title;
return $this->statusText;
}
public function setTitle(string $title): self
public function setStatusText(string $statusText): self
{
$this->title = $title;
$this->statusText = $statusText;
return $this;
}
@ -355,8 +356,19 @@ class FlattenException extends LegacyFlattenException
return $this->traceAsString;
}
public function setAsString(?string $asString)
{
$this->asString = $asString;
return $this;
}
public function getAsString()
{
if (null !== $this->asString) {
return $this->asString;
}
$message = '';
$next = false;

View File

@ -1,25 +0,0 @@
<?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\Debug;
if (!class_exists(Debug::class, false)) {
class_alias(\Symfony\Component\ErrorHandler\Debug::class, Debug::class);
}
if (false) {
/**
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Debug instead.
*/
class Debug extends \Symfony\Component\ErrorHandler\Debug
{
}
}

View File

@ -0,0 +1,43 @@
<!-- <?= $_message = sprintf('%s (%d %s)', $exceptionMessage, $statusCode, $statusText); ?> -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="<?= $this->charset; ?>" />
<meta name="robots" content="noindex,nofollow" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title><?= $_message; ?></title>
<link rel="icon" type="image/png" href="<?= $this->include('assets/images/favicon.png.base64'); ?>">
<style><?= $this->include('assets/css/exception.css'); ?></style>
<style><?= $this->include('assets/css/exception_full.css'); ?></style>
</head>
<body>
<?php if (class_exists('Symfony\Component\HttpKernel\Kernel')) { ?>
<header>
<div class="container">
<h1 class="logo"><?= $this->include('assets/images/symfony-logo.svg'); ?> Symfony Exception</h1>
<div class="help-link">
<a href="https://symfony.com/doc/<?= Symfony\Component\HttpKernel\Kernel::VERSION; ?>/index.html">
<span class="icon"><?= $this->include('assets/images/icon-book.svg'); ?></span>
<span class="hidden-xs-down">Symfony</span> Docs
</a>
</div>
<div class="help-link">
<a href="https://symfony.com/support">
<span class="icon"><?= $this->include('assets/images/icon-support.svg'); ?></span>
<span class="hidden-xs-down">Symfony</span> Support
</a>
</div>
</div>
</header>
<?php } ?>
<?= $this->include('views/exception.html.php', $context); ?>
<script>
<?= $this->include('assets/js/exception.js'); ?>
</script>
</body>
</html>
<!-- <?= $_message; ?> -->

View File

@ -0,0 +1,53 @@
<?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\ErrorHandler\Tests\ErrorRenderer;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRenderer;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRendererInterface;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ProblemNormalizer;
use Symfony\Component\Serializer\Serializer;
class ErrorRendererTest extends TestCase
{
public function testDefaultContent()
{
$errorRenderer = new ErrorRenderer();
self::assertStringContainsString('<h2>The server returned a "500 Internal Server Error".</h2>', $errorRenderer->render(new \RuntimeException(), 'html'));
}
public function testCustomContent()
{
$errorRenderer = new ErrorRenderer(new CustomHtmlErrorRenderer());
$this->assertSame('Foo', $errorRenderer->render(new \RuntimeException('Foo'), 'html'));
}
public function testSerializerContent()
{
$exception = new \RuntimeException('Foo');
$errorRenderer = new ErrorRenderer(null, new Serializer([new ProblemNormalizer()], [new JsonEncoder()]));
$this->assertSame('{"type":"https:\/\/tools.ietf.org\/html\/rfc2616#section-10","title":"An error occurred","status":500,"detail":"Internal Server Error"}', $errorRenderer->render($exception, 'json'));
}
}
class CustomHtmlErrorRenderer implements HtmlErrorRendererInterface
{
public function render(FlattenException $exception): string
{
return $exception->getMessage();
}
}

View File

@ -9,21 +9,20 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorRenderer\Tests\ErrorRenderer;
namespace Symfony\Component\ErrorHandler\Tests\ErrorRenderer;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface;
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
class HtmlErrorRendererTest extends TestCase
{
/**
* @dataProvider getRenderData
*/
public function testRender(FlattenException $exception, ErrorRendererInterface $errorRenderer, string $expected)
public function testRender(FlattenException $exception, HtmlErrorRenderer $errorRenderer, string $expected)
{
$this->assertStringMatchesFormat($expected, $errorRenderer->render($exception));
$this->assertStringMatchesFormat($expected, $errorRenderer->render($exception)->getAsString());
}
public function getRenderData(): iterable
@ -55,17 +54,5 @@ HTML;
new HtmlErrorRenderer(false),
$expectedNonDebug,
];
yield '->render() returns the HTML content WITHOUT stack traces in debug mode FORCING non-debug via X-Debug header' => [
FlattenException::createFromThrowable(new \RuntimeException('Foo'), null, ['X-Debug' => false]),
new HtmlErrorRenderer(true),
$expectedNonDebug,
];
yield '->render() returns the HTML content WITHOUT stack traces in non-debug mode EVEN FORCING debug via X-Debug header' => [
FlattenException::createFromThrowable(new \RuntimeException('Foo'), null, ['X-Debug' => true]),
new HtmlErrorRenderer(false),
$expectedNonDebug,
];
}
}

View File

@ -9,10 +9,10 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorRenderer\Tests\Exception;
namespace Symfony\Component\ErrorHandler\Tests\Exception;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

View File

@ -17,20 +17,18 @@
],
"require": {
"php": "^7.1.3",
"psr/log": "~1.0"
"psr/log": "~1.0",
"symfony/debug": "^4.4",
"symfony/var-dumper": "^4.4|^5.0"
},
"require-dev": {
"symfony/http-kernel": "^3.4|^4.0|^5.0"
},
"suggest": {
"symfony/error-renderer": "For better error rendering"
"symfony/http-kernel": "^4.4|^5.0"
},
"conflict": {
"symfony/http-kernel": "<3.4"
"symfony/http-kernel": "<4.4"
},
"autoload": {
"psr-4": { "Symfony\\Component\\ErrorHandler\\": "" },
"classmap": [ "Resources/stubs/Debug.php" ],
"exclude-from-classmap": [
"/Tests/"
]

View File

@ -1,3 +0,0 @@
/Tests export-ignore
/phpunit.xml.dist export-ignore
/.gitignore export-ignore

View File

@ -1,3 +0,0 @@
vendor/
composer.lock
phpunit.xml

View File

@ -1,7 +0,0 @@
CHANGELOG
=========
4.4.0
-----
* added the component

View File

@ -1,125 +0,0 @@
<?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);
}
return 0;
}
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

@ -1,71 +0,0 @@
<?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\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface;
/**
* @author Yonel Ceruto <yonelceruto@gmail.com>
*/
class ErrorRendererPass implements CompilerPassInterface
{
private $rendererService;
private $rendererTag;
private $debugCommandService;
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;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->rendererService)) {
return;
}
$renderers = [];
foreach ($container->findTaggedServiceIds($this->rendererTag, true) as $serviceId => $tags) {
/** @var ErrorRendererInterface $class */
$class = $container->getDefinition($serviceId)->getClass();
foreach ($tags as $tag) {
$format = $tag['format'] ?? $class::getFormat();
$priority = $tag['priority'] ?? 0;
if (!isset($renderers[$priority][$format])) {
$renderers[$priority][$format] = new Reference($serviceId);
}
}
}
if ($renderers) {
ksort($renderers);
$renderers = array_merge(...$renderers);
}
$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

@ -1,44 +0,0 @@
<?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\DependencyInjection;
use Psr\Container\ContainerInterface;
use Symfony\Component\ErrorRenderer\ErrorRenderer;
/**
* Lazily loads error renderers from the dependency injection container.
*
* @author Yonel Ceruto <yonelceruto@gmail.com>
*/
class LazyLoadingErrorRenderer extends ErrorRenderer
{
private $container;
private $initialized = [];
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* {@inheritdoc}
*/
public function render($exception, string $format = 'html'): string
{
if (!isset($this->initialized[$format]) && $this->container->has($format)) {
$this->addRenderer($this->container->get($format), $format);
$this->initialized[$format] = true;
}
return parent::render($exception, $format);
}
}

View File

@ -1,77 +0,0 @@
<?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;
use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface;
use Symfony\Component\ErrorRenderer\Exception\ErrorRendererNotFoundException;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
/**
* Formats an exception to be used as response content.
*
* It delegates to implementations of ErrorRendererInterface depending on the format.
*
* @see ErrorRendererInterface
*
* @author Yonel Ceruto <yonelceruto@gmail.com>
*/
class ErrorRenderer
{
private $renderers = [];
/**
* @param ErrorRendererInterface[] $renderers
*/
public function __construct(iterable $renderers)
{
foreach ($renderers as $renderer) {
$this->addRenderer($renderer);
}
}
/**
* 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::getFormat()] = $renderer;
return $this;
}
/**
* Renders an Exception and returns the Response content.
*
* @param \Throwable|FlattenException $exception A \Throwable or FlattenException instance
* @param string $format The request format (html, json, xml, etc.)
*
* @return string The Response content as a string
*
* @throws ErrorRendererNotFoundException if no renderer is found
*/
public function render($exception, string $format = 'html'): string
{
if (!isset($this->renderers[$format])) {
throw new ErrorRendererNotFoundException(sprintf('No error renderer found for format "%s".', $format));
}
if ($exception instanceof \Throwable) {
$exception = FlattenException::createFromThrowable($exception);
}
return $this->renderers[$format]->render($exception);
}
}

View File

@ -1,32 +0,0 @@
<?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\ErrorRenderer;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
/**
* Interface for classes that can render errors in a specific format.
*
* @author Yonel Ceruto <yonelceruto@gmail.com>
*/
interface ErrorRendererInterface
{
/**
* Gets the format this renderer can return errors as.
*/
public static function getFormat(): string;
/**
* Returns the response content of the rendered exception.
*/
public function render(FlattenException $exception): string;
}

View File

@ -1,60 +0,0 @@
<?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\ErrorRenderer;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
/**
* @author Yonel Ceruto <yonelceruto@gmail.com>
*/
class JsonErrorRenderer implements ErrorRendererInterface
{
private $debug;
public function __construct(bool $debug = false)
{
$this->debug = $debug;
}
/**
* {@inheritdoc}
*/
public static function getFormat(): string
{
return 'json';
}
/**
* {@inheritdoc}
*/
public function render(FlattenException $exception): string
{
$debug = $this->debug && ($exception->getHeaders()['X-Debug'] ?? true);
if ($debug) {
$message = $exception->getMessage();
} else {
$message = 404 === $exception->getStatusCode() ? 'Sorry, the page you are looking for could not be found.' : 'Whoops, looks like something went wrong.';
}
$content = [
'title' => $exception->getTitle(),
'status' => $exception->getStatusCode(),
'detail' => $message,
];
if ($debug) {
$content['exceptions'] = $exception->toArray();
}
return (string) json_encode($content, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS | JSON_PRESERVE_ZERO_FRACTION);
}
}

View File

@ -1,104 +0,0 @@
<?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\ErrorRenderer;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
/**
* @author Yonel Ceruto <yonelceruto@gmail.com>
*/
class TxtErrorRenderer implements ErrorRendererInterface
{
private $debug;
public function __construct(bool $debug = false)
{
$this->debug = $debug;
}
/**
* {@inheritdoc}
*/
public static function getFormat(): string
{
return 'txt';
}
/**
* {@inheritdoc}
*/
public function render(FlattenException $exception): string
{
$debug = $this->debug && ($exception->getHeaders()['X-Debug'] ?? true);
if ($debug) {
$message = $exception->getMessage();
} else {
$message = 404 === $exception->getStatusCode() ? 'Sorry, the page you are looking for could not be found.' : 'Whoops, looks like something went wrong.';
}
$content = sprintf("[title] %s\n", $exception->getTitle());
$content .= sprintf("[status] %s\n", $exception->getStatusCode());
$content .= sprintf("[detail] %s\n", $message);
if ($debug) {
foreach ($exception->toArray() as $i => $e) {
$content .= sprintf("[%d] %s: %s\n", $i + 1, $e['class'], $e['message']);
foreach ($e['trace'] as $trace) {
if ($trace['function']) {
$content .= sprintf('at %s%s%s(%s) ', $trace['class'], $trace['type'], $trace['function'], $this->formatArgs($trace['args']));
}
if (isset($trace['file'], $trace['line'])) {
$content .= $this->formatPath($trace['file'], $trace['line']);
}
$content .= "\n";
}
}
}
return $content;
}
private function formatPath(string $path, int $line): string
{
$file = preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path;
return sprintf('in %s %s', $path, 0 < $line ? ' line '.$line : '');
}
/**
* Formats an array as a string.
*/
private function formatArgs(array $args): string
{
$result = [];
foreach ($args as $key => $item) {
if ('object' === $item[0]) {
$formattedValue = sprintf('object(%s)', $item[1]);
} elseif ('array' === $item[0]) {
$formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
} elseif ('null' === $item[0]) {
$formattedValue = 'null';
} elseif ('boolean' === $item[0]) {
$formattedValue = strtolower(var_export($item[1], true));
} elseif ('resource' === $item[0]) {
$formattedValue = 'resource';
} else {
$formattedValue = str_replace("\n", '', var_export($item[1], true));
}
$result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue);
}
return implode(', ', $result);
}
}

View File

@ -1,125 +0,0 @@
<?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\ErrorRenderer;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
/**
* @author Yonel Ceruto <yonelceruto@gmail.com>
*/
class XmlErrorRenderer implements ErrorRendererInterface
{
private $debug;
private $charset;
public function __construct(bool $debug = false, string $charset = null)
{
$this->debug = $debug;
$this->charset = $charset ?: (ini_get('default_charset') ?: 'UTF-8');
}
/**
* {@inheritdoc}
*/
public static function getFormat(): string
{
return 'xml';
}
/**
* {@inheritdoc}
*/
public function render(FlattenException $exception): string
{
$debug = $this->debug && ($exception->getHeaders()['X-Debug'] ?? true);
$title = $this->escapeXml($exception->getTitle());
if ($debug) {
$message = $this->escapeXml($exception->getMessage());
} else {
$message = 404 === $exception->getStatusCode() ? 'Sorry, the page you are looking for could not be found.' : 'Whoops, looks like something went wrong.';
}
$statusCode = $this->escapeXml($exception->getStatusCode());
$charset = $this->escapeXml($this->charset);
$exceptions = '';
if ($debug) {
$exceptions .= '<exceptions>';
foreach ($exception->toArray() as $e) {
$exceptions .= sprintf('<exception class="%s" message="%s"><traces>', $e['class'], $this->escapeXml($e['message']));
foreach ($e['trace'] as $trace) {
$exceptions .= '<trace>';
if ($trace['function']) {
$exceptions .= sprintf('at %s%s%s(%s) ', $trace['class'], $trace['type'], $trace['function'], $this->formatArgs($trace['args']));
}
if (isset($trace['file'], $trace['line'])) {
$exceptions .= $this->formatPath($trace['file'], $trace['line']);
}
$exceptions .= '</trace>';
}
$exceptions .= '</traces></exception>';
}
$exceptions .= '</exceptions>';
}
return <<<EOF
<?xml version="1.0" encoding="{$charset}" ?>
<problem xmlns="urn:ietf:rfc:7807">
<title>{$title}</title>
<status>{$statusCode}</status>
<detail>{$message}</detail>
{$exceptions}
</problem>
EOF;
}
/**
* XML-encodes a string.
*/
private function escapeXml(string $str): string
{
return htmlspecialchars($str, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset);
}
private function formatPath(string $path, int $line): string
{
$file = $this->escapeXml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path);
return sprintf('in %s %s', $this->escapeXml($path), 0 < $line ? ' line '.$line : '');
}
/**
* Formats an array as a string.
*/
private function formatArgs(array $args): string
{
$result = [];
foreach ($args as $key => $item) {
if ('object' === $item[0]) {
$formattedValue = sprintf('object(%s)', $item[1]);
} elseif ('array' === $item[0]) {
$formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
} elseif ('null' === $item[0]) {
$formattedValue = 'null';
} elseif ('boolean' === $item[0]) {
$formattedValue = strtolower(var_export($item[1], true));
} elseif ('resource' === $item[0]) {
$formattedValue = 'resource';
} else {
$formattedValue = str_replace("\n", '', $this->escapeXml(var_export($item[1], true)));
}
$result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escapeXml($key), $formattedValue);
}
return implode(', ', $result);
}
}

View File

@ -1,16 +0,0 @@
<?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\Exception;
class ErrorRendererNotFoundException extends \RuntimeException
{
}

View File

@ -1,19 +0,0 @@
Copyright (c) 2019 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,12 +0,0 @@
ErrorRenderer Component
======================
The ErrorRenderer component provides tools to display errors and exceptions.
Resources
---------
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

View File

@ -1,41 +0,0 @@
<!-- <?= $_message = sprintf('%s (%d %s)', $exceptionMessage, $statusCode, $statusText); ?> -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="<?= $this->charset; ?>" />
<meta name="robots" content="noindex,nofollow" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title><?= $_message; ?></title>
<link rel="icon" type="image/png" href="<?= $this->include('assets/images/favicon.png.base64'); ?>">
<style><?= $this->include('assets/css/exception.css'); ?></style>
<style><?= $this->include('assets/css/exception_full.css'); ?></style>
</head>
<body>
<header>
<div class="container">
<h1 class="logo"><?= $this->include('assets/images/symfony-logo.svg'); ?> Symfony Exception</h1>
<div class="help-link">
<a href="https://symfony.com/doc/<?= Symfony\Component\HttpKernel\Kernel::VERSION; ?>/index.html">
<span class="icon"><?= $this->include('assets/images/icon-book.svg'); ?></span>
<span class="hidden-xs-down">Symfony</span> Docs
</a>
</div>
<div class="help-link">
<a href="https://symfony.com/support">
<span class="icon"><?= $this->include('assets/images/icon-support.svg'); ?></span>
<span class="hidden-xs-down">Symfony</span> Support
</a>
</div>
</div>
</header>
<?= $this->include('views/exception.html.php', $context); ?>
<script>
<?= $this->include('assets/js/exception.js'); ?>
</script>
</body>
</html>
<!-- <?= $_message; ?> -->

View File

@ -1,88 +0,0 @@
<?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": "Whoops, looks like something went wrong."
}
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'));
}
public function testInvalidFormat()
{
$this->expectException('Symfony\Component\Console\Exception\InvalidArgumentException');
$this->expectExceptionMessage('No error renderer found for format "foo". Known format are json, xml, txt.');
$tester = $this->createCommandTester();
$tester->execute(['format' => 'foo'], ['decorated' => false]);
}
}

View File

@ -1,67 +0,0 @@
<?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\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\ErrorRenderer\DependencyInjection\ErrorRendererPass;
use Symfony\Component\ErrorRenderer\DependencyInjection\LazyLoadingErrorRenderer;
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer;
class ErrorRendererPassTest extends TestCase
{
public function testProcess()
{
$container = new ContainerBuilder();
$container->setParameter('kernel.debug', true);
$definition = $container->register('error_renderer', LazyLoadingErrorRenderer::class)
->addArgument([])
;
$container->register('error_renderer.renderer.html', HtmlErrorRenderer::class)
->addTag('error_renderer.renderer')
;
$container->register('error_renderer.renderer.json', JsonErrorRenderer::class)
->addTag('error_renderer.renderer')
;
(new ErrorRendererPass())->process($container);
$serviceLocatorDefinition = $container->getDefinition((string) $definition->getArgument(0));
$this->assertSame(ServiceLocator::class, $serviceLocatorDefinition->getClass());
$expected = [
'html' => new ServiceClosureArgument(new Reference('error_renderer.renderer.html')),
'json' => new ServiceClosureArgument(new Reference('error_renderer.renderer.json')),
];
$this->assertEquals($expected, $serviceLocatorDefinition->getArgument(0));
}
public function testServicesAreOrderedAccordingToPriority()
{
$container = new ContainerBuilder();
$definition = $container->register('error_renderer')->setArguments([null]);
$container->register('r2')->addTag('error_renderer.renderer', ['format' => 'json', 'priority' => 100]);
$container->register('r1')->addTag('error_renderer.renderer', ['format' => 'json', 'priority' => 200]);
$container->register('r3')->addTag('error_renderer.renderer', ['format' => 'json']);
(new ErrorRendererPass())->process($container);
$expected = [
'json' => new ServiceClosureArgument(new Reference('r1')),
];
$serviceLocatorDefinition = $container->getDefinition((string) $definition->getArgument(0));
$this->assertEquals($expected, $serviceLocatorDefinition->getArgument(0));
}
}

View File

@ -1,66 +0,0 @@
<?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\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Symfony\Component\ErrorRenderer\DependencyInjection\LazyLoadingErrorRenderer;
use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
class LazyLoadingErrorRendererTest extends TestCase
{
public function testInvalidErrorRenderer()
{
$this->expectException('Symfony\Component\ErrorRenderer\Exception\ErrorRendererNotFoundException');
$this->expectExceptionMessage('No error renderer found for format "foo".');
$container = $this->getMockBuilder(ContainerInterface::class)->getMock();
$container->expects($this->once())->method('has')->with('foo')->willReturn(false);
$exception = FlattenException::createFromThrowable(new \Exception('Foo'));
(new LazyLoadingErrorRenderer($container))->render($exception, 'foo');
}
public function testCustomErrorRenderer()
{
$container = $this->getMockBuilder(ContainerInterface::class)->getMock();
$container
->expects($this->once())
->method('has')
->with('foo')
->willReturn(true)
;
$container
->expects($this->once())
->method('get')
->willReturn(new FooErrorRenderer())
;
$errorRenderer = new LazyLoadingErrorRenderer($container);
$exception = FlattenException::createFromThrowable(new \RuntimeException('Foo'));
$this->assertSame('Foo', $errorRenderer->render($exception, 'foo'));
}
}
class FooErrorRenderer implements ErrorRendererInterface
{
public static function getFormat(): string
{
return 'foo';
}
public function render(FlattenException $exception): string
{
return $exception->getMessage();
}
}

View File

@ -1,76 +0,0 @@
<?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\ErrorRenderer;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface;
use Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
class JsonErrorRendererTest extends TestCase
{
/**
* @dataProvider getRenderData
*/
public function testRender(FlattenException $exception, ErrorRendererInterface $errorRenderer, string $expected)
{
$this->assertStringMatchesFormat($expected, $errorRenderer->render($exception));
}
public function getRenderData(): iterable
{
$expectedDebug = <<<JSON
{
"title": "Internal Server Error",
"status": 500,
"detail": "Foo",
"exceptions": [
{
"message": "Foo",
"class": "RuntimeException",
"trace": [
%A
JSON;
$expectedNonDebug = <<<JSON
{
"title": "Internal Server Error",
"status": 500,
"detail": "Whoops, looks like something went wrong."
}
JSON;
yield '->render() returns the JSON content WITH stack traces in debug mode' => [
FlattenException::createFromThrowable(new \RuntimeException('Foo')),
new JsonErrorRenderer(true),
$expectedDebug,
];
yield '->render() returns the JSON content WITHOUT stack traces in non-debug mode' => [
FlattenException::createFromThrowable(new \RuntimeException('Foo')),
new JsonErrorRenderer(false),
$expectedNonDebug,
];
yield '->render() returns the JSON content WITHOUT stack traces in debug mode FORCING non-debug via X-Debug header' => [
FlattenException::createFromThrowable(new \RuntimeException('Foo'), null, ['X-Debug' => false]),
new JsonErrorRenderer(true),
$expectedNonDebug,
];
yield '->render() returns the JSON content WITHOUT stack traces in non-debug mode EVEN FORCING debug via X-Debug header' => [
FlattenException::createFromThrowable(new \RuntimeException('Foo'), null, ['X-Debug' => true]),
new JsonErrorRenderer(false),
$expectedNonDebug,
];
}
}

View File

@ -1,69 +0,0 @@
<?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\ErrorRenderer;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface;
use Symfony\Component\ErrorRenderer\ErrorRenderer\TxtErrorRenderer;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
class TxtErrorRendererTest extends TestCase
{
/**
* @dataProvider getRenderData
*/
public function testRender(FlattenException $exception, ErrorRendererInterface $errorRenderer, string $expected)
{
$this->assertStringMatchesFormat($expected, $errorRenderer->render($exception));
}
public function getRenderData(): iterable
{
$expectedDebug = <<<TXT
[title] Internal Server Error
[status] 500
[detail] Foo
[1] RuntimeException: Foo
in %A
TXT;
$expectedNonDebug = <<<TXT
[title] Internal Server Error
[status] 500
[detail] Whoops, looks like something went wrong.
TXT;
yield '->render() returns the TXT content WITH stack traces in debug mode' => [
FlattenException::createFromThrowable(new \RuntimeException('Foo')),
new TxtErrorRenderer(true),
$expectedDebug,
];
yield '->render() returns the TXT content WITHOUT stack traces in non-debug mode' => [
FlattenException::createFromThrowable(new \RuntimeException('Foo')),
new TxtErrorRenderer(false),
$expectedNonDebug,
];
yield '->render() returns the TXT content WITHOUT stack traces in debug mode FORCING non-debug via X-Debug header' => [
FlattenException::createFromThrowable(new \RuntimeException('Foo'), null, ['X-Debug' => false]),
new TxtErrorRenderer(true),
$expectedNonDebug,
];
yield '->render() returns the TXT content WITHOUT stack traces in non-debug mode EVEN FORCING debug via X-Debug header' => [
FlattenException::createFromThrowable(new \RuntimeException('Foo'), null, ['X-Debug' => true]),
new TxtErrorRenderer(false),
$expectedNonDebug,
];
}
}

View File

@ -1,75 +0,0 @@
<?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\ErrorRenderer;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface;
use Symfony\Component\ErrorRenderer\ErrorRenderer\XmlErrorRenderer;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
class XmlErrorRendererTest extends TestCase
{
/**
* @dataProvider getRenderData
*/
public function testRender(FlattenException $exception, ErrorRendererInterface $errorRenderer, string $expected)
{
$this->assertStringMatchesFormat($expected, $errorRenderer->render($exception));
}
public function getRenderData(): iterable
{
$expectedDebug = <<<XML
<?xml version="1.0" encoding="UTF-8" ?>
<problem xmlns="urn:ietf:rfc:7807">
<title>Internal Server Error</title>
<status>500</status>
<detail>Foo</detail>
<exceptions><exception class="RuntimeException" message="Foo"><traces><trace>%A</trace></traces></exception></exceptions>
</problem>
XML;
$expectedNonDebug = <<<XML
<?xml version="1.0" encoding="UTF-8" ?>
<problem xmlns="urn:ietf:rfc:7807">
<title>Internal Server Error</title>
<status>500</status>
<detail>Whoops, looks like something went wrong.</detail>
</problem>
XML;
yield '->render() returns the XML content WITH stack traces in debug mode' => [
FlattenException::createFromThrowable(new \RuntimeException('Foo')),
new XmlErrorRenderer(true),
$expectedDebug,
];
yield '->render() returns the XML content WITHOUT stack traces in non-debug mode' => [
FlattenException::createFromThrowable(new \RuntimeException('Foo')),
new XmlErrorRenderer(false),
$expectedNonDebug,
];
yield '->render() returns the XML content WITHOUT stack traces in debug mode FORCING non-debug via X-Debug header' => [
FlattenException::createFromThrowable(new \RuntimeException('Foo'), null, ['X-Debug' => false]),
new XmlErrorRenderer(true),
$expectedNonDebug,
];
yield '->render() returns the XML content WITHOUT stack traces in non-debug mode EVEN FORCING debug via X-Debug header' => [
FlattenException::createFromThrowable(new \RuntimeException('Foo'), null, ['X-Debug' => true]),
new XmlErrorRenderer(false),
$expectedNonDebug,
];
}
}

View File

@ -1,56 +0,0 @@
<?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;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorRenderer\ErrorRenderer;
use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
class ErrorRendererTest extends TestCase
{
public function testErrorRendererNotFound()
{
$this->expectException('Symfony\Component\ErrorRenderer\Exception\ErrorRendererNotFoundException');
$this->expectExceptionMessage('No error renderer found for format "foo".');
$exception = FlattenException::createFromThrowable(new \Exception('foo'));
(new ErrorRenderer([]))->render($exception, 'foo');
}
public function testInvalidErrorRenderer()
{
$this->expectException('TypeError');
new ErrorRenderer([new \stdClass()]);
}
public function testCustomErrorRenderer()
{
$renderers = [new FooErrorRenderer()];
$errorRenderer = new ErrorRenderer($renderers);
$exception = FlattenException::createFromThrowable(new \RuntimeException('Foo'));
$this->assertSame('Foo', $errorRenderer->render($exception, 'foo'));
}
}
class FooErrorRenderer implements ErrorRendererInterface
{
public static function getFormat(): string
{
return 'foo';
}
public function render(FlattenException $exception): string
{
return $exception->getMessage();
}
}

View File

@ -1,47 +0,0 @@
{
"name": "symfony/error-renderer",
"type": "library",
"description": "Symfony ErrorRenderer Component",
"keywords": [],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Yonel Ceruto",
"email": "yonelceruto@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": "^7.1.3",
"psr/log": "~1.0",
"symfony/debug": "^4.4"
},
"require-dev": {
"symfony/console": "^4.4",
"symfony/dependency-injection": "^4.4",
"symfony/http-kernel": "^4.4"
},
"conflict": {
"symfony/http-kernel": "<4.4"
},
"autoload": {
"psr-4": { "Symfony\\Component\\ErrorRenderer\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "4.4-dev"
}
}
}

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="Symfony ErrorRenderer Component Test Suite">
<directory>./Tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./Tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@ -11,9 +11,8 @@
namespace Symfony\Component\HttpKernel\Controller;
use Symfony\Component\ErrorRenderer\ErrorRenderer;
use Symfony\Component\ErrorRenderer\Exception\ErrorRendererNotFoundException;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
@ -30,26 +29,22 @@ class ErrorController
private $controller;
private $errorRenderer;
public function __construct(HttpKernelInterface $kernel, $controller, ErrorRenderer $errorRenderer)
public function __construct(HttpKernelInterface $kernel, $controller, ErrorRendererInterface $errorRenderer)
{
$this->kernel = $kernel;
$this->controller = $controller;
$this->errorRenderer = $errorRenderer;
}
public function __invoke(Request $request, FlattenException $exception): Response
public function __invoke(\Throwable $exception): Response
{
try {
return new Response($this->errorRenderer->render($exception, $request->getPreferredFormat()), $exception->getStatusCode(), $exception->getHeaders());
} catch (ErrorRendererNotFoundException $_) {
return new Response($this->errorRenderer->render($exception), $exception->getStatusCode(), $exception->getHeaders());
}
$exception = $this->errorRenderer->render($exception);
return new Response($exception->getAsString(), $exception->getStatusCode(), $exception->getHeaders());
}
public function preview(Request $request, int $code): Response
{
$exception = FlattenException::createFromThrowable(new \Exception('This is a sample exception.'), $code, ['X-Debug' => false]);
/*
* This Request mimics the parameters set by
* \Symfony\Component\HttpKernel\EventListener\ErrorListener::duplicateRequest, with
@ -57,7 +52,7 @@ class ErrorController
*/
$subRequest = $request->duplicate(null, null, [
'_controller' => $this->controller,
'exception' => $exception,
'exception' => new \Exception('This is a sample exception.'),
'logger' => null,
'showException' => false,
]);

View File

@ -11,7 +11,7 @@
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

View File

@ -123,7 +123,7 @@ class ErrorListener implements EventSubscriberInterface
{
$attributes = [
'_controller' => $this->controller,
'exception' => FlattenException::createFromThrowable($exception),
'exception' => $exception,
'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null,
];
$request = $request->duplicate(null, null, $attributes);

View File

@ -12,7 +12,7 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;

View File

@ -12,10 +12,9 @@
namespace Symfony\Component\HttpKernel\Tests\Controller;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorRenderer\ErrorRenderer;
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRenderer;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Controller\ErrorController;
@ -31,7 +30,7 @@ class ErrorControllerTest extends TestCase
public function testInvokeController(Request $request, FlattenException $exception, int $statusCode, string $content)
{
$kernel = $this->getMockBuilder(HttpKernelInterface::class)->getMock();
$errorRenderer = new ErrorRenderer([new HtmlErrorRenderer(), new JsonErrorRenderer()]);
$errorRenderer = new ErrorRenderer(new HtmlErrorRenderer());
$controller = new ErrorController($kernel, null, $errorRenderer);
$response = $controller($request, $exception);
@ -55,33 +54,6 @@ class ErrorControllerTest extends TestCase
'The server returned a "404 Not Found".',
];
$request = new Request();
$request->attributes->set('_format', 'json');
yield 'custom format via _format attribute' => [
$request,
FlattenException::createFromThrowable(new \Exception('foo')),
500,
'{"title": "Internal Server Error","status": 500,"detail": "Whoops, looks like something went wrong."}',
];
$request = new Request();
$request->headers->set('Accept', 'application/json');
yield 'custom format via Accept header' => [
$request,
FlattenException::createFromThrowable(new HttpException(405, 'Invalid request.')),
405,
'{"title": "Method Not Allowed","status": 405,"detail": "Whoops, looks like something went wrong."}',
];
$request = new Request();
$request->headers->set('Content-Type', 'application/json');
yield 'custom format via Content-Type header' => [
$request,
FlattenException::createFromThrowable(new HttpException(405, 'Invalid request.')),
405,
'{"title": "Method Not Allowed","status": 405,"detail": "Whoops, looks like something went wrong."}',
];
$request = new Request();
$request->attributes->set('_format', 'unknown');
yield 'default HTML format for unknown formats' => [
@ -116,7 +88,7 @@ class ErrorControllerTest extends TestCase
)
->willReturn($response = new Response());
$controller = new ErrorController($kernel, $_controller, new ErrorRenderer([]));
$controller = new ErrorController($kernel, $_controller, new ErrorRenderer());
$this->assertSame($response, $controller->preview(new Request(), $code));
}

View File

@ -12,7 +12,7 @@
namespace Symfony\Component\HttpKernel\Tests\DataCollector;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector;

View File

@ -18,7 +18,6 @@
"require": {
"php": "^7.1.3",
"symfony/error-handler": "^4.4|^5.0",
"symfony/error-renderer": "^4.4|^5.0",
"symfony/event-dispatcher": "^4.4",
"symfony/http-foundation": "^4.4|^5.0",
"symfony/polyfill-ctype": "^1.8",
@ -40,7 +39,6 @@
"symfony/templating": "^3.4|^4.0|^5.0",
"symfony/translation": "^4.2|^5.0",
"symfony/translation-contracts": "^1.1|^2",
"symfony/var-dumper": "^4.1.1|^5.0",
"psr/cache": "~1.0",
"twig/twig": "^1.34|^2.4|^3.0"
},
@ -53,15 +51,13 @@
"symfony/console": ">=5",
"symfony/dependency-injection": "<4.3",
"symfony/translation": "<4.2",
"symfony/var-dumper": "<4.1.1",
"twig/twig": "<1.34|<2.4,>=2"
},
"suggest": {
"symfony/browser-kit": "",
"symfony/config": "",
"symfony/console": "",
"symfony/dependency-injection": "",
"symfony/var-dumper": ""
"symfony/dependency-injection": ""
},
"autoload": {
"psr-4": { "Symfony\\Component\\HttpKernel\\": "" },

View File

@ -11,7 +11,7 @@
namespace Symfony\Component\Messenger\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
use Symfony\Component\Messenger\Exception\HandlerFailedException;

View File

@ -11,7 +11,7 @@
namespace Symfony\Component\Messenger\Stamp;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\Messenger\Envelope;
/**

View File

@ -12,7 +12,7 @@
namespace Symfony\Component\Messenger\Tests\Stamp;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
class RedeliveryStampTest extends TestCase

View File

@ -25,7 +25,6 @@
"doctrine/persistence": "~1.0",
"symfony/console": "^3.4|^4.0|^5.0",
"symfony/dependency-injection": "^3.4.19|^4.1.8|^5.0",
"symfony/error-renderer": "^4.4|^5.0",
"symfony/event-dispatcher": "^4.3|^5.0",
"symfony/http-kernel": "^4.4",
"symfony/process": "^3.4|^4.0|^5.0",

View File

@ -6,6 +6,7 @@ CHANGELOG
* deprecated the `XmlEncoder::TYPE_CASE_ATTRIBUTES` constant, use `XmlEncoder::TYPE_CAST_ATTRIBUTES` instead
* added option to output a UTF-8 BOM in CSV encoder via `CsvEncoder::OUTPUT_UTF8_BOM_KEY` context option
* added `ProblemNormalizer` to normalize errors according to the API Problem spec (RFC 7807)
4.3.0
-----

View File

@ -0,0 +1,74 @@
<?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\Serializer\Normalizer;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
/**
* Normalizes errors according to the API Problem spec (RFC 7807).
*
* @see https://tools.ietf.org/html/rfc7807
*
* @author Kévin Dunglas <dunglas@gmail.com>
* @author Yonel Ceruto <yonelceruto@gmail.com>
*/
class ProblemNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface
{
private $debug;
private $defaultContext = [
'type' => 'https://tools.ietf.org/html/rfc2616#section-10',
'title' => 'An error occurred',
];
public function __construct(bool $debug = false, array $defaultContext = [])
{
$this->debug = $debug;
$this->defaultContext = $defaultContext + $this->defaultContext;
}
/**
* {@inheritdoc}
*/
public function normalize($exception, $format = null, array $context = [])
{
$context += $this->defaultContext;
$data = [
'type' => $context['type'],
'title' => $context['title'],
'status' => $context['status'] ?? $exception->getStatusCode(),
'detail' => $this->debug ? $exception->getMessage() : $exception->getStatusText(),
];
if ($this->debug) {
$data['class'] = $exception->getClass();
$data['trace'] = $exception->getTrace();
}
return $data;
}
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = null): bool
{
return $data instanceof FlattenException;
}
/**
* {@inheritdoc}
*/
public function hasCacheableSupportsMethod(): bool
{
return true;
}
}

View File

@ -0,0 +1,48 @@
<?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\Serializer\Tests\Normalizer;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\Serializer\Normalizer\ProblemNormalizer;
class ProblemNormalizerTest extends TestCase
{
/**
* @var ProblemNormalizer
*/
private $normalizer;
protected function setUp(): void
{
$this->normalizer = new ProblemNormalizer(false);
}
public function testSupportNormalization()
{
$this->assertTrue($this->normalizer->supportsNormalization(FlattenException::createFromThrowable(new \Exception())));
$this->assertFalse($this->normalizer->supportsNormalization(new \Exception()));
$this->assertFalse($this->normalizer->supportsNormalization(new \stdClass()));
}
public function testNormalize()
{
$expected = [
'type' => 'https://tools.ietf.org/html/rfc2616#section-10',
'title' => 'An error occurred',
'status' => 500,
'detail' => 'Internal Server Error',
];
$this->assertSame($expected, $this->normalizer->normalize(FlattenException::createFromThrowable(new \RuntimeException('Error'))));
}
}

View File

@ -20,17 +20,18 @@
"symfony/polyfill-ctype": "~1.8"
},
"require-dev": {
"symfony/yaml": "^3.4|^4.0|^5.0",
"symfony/config": "^3.4|^4.0|^5.0",
"symfony/property-access": "^3.4|^4.0|^5.0",
"symfony/http-foundation": "^3.4|^4.0|^5.0",
"doctrine/annotations": "~1.0",
"doctrine/cache": "~1.0",
"phpdocumentor/reflection-docblock": "^3.0|^4.0",
"symfony/cache": "^3.4|^4.0|^5.0",
"symfony/config": "^3.4|^4.0|^5.0",
"symfony/dependency-injection": "^3.4|^4.0|^5.0",
"symfony/error-handler": "^4.4|^5.0",
"symfony/http-foundation": "^3.4|^4.0|^5.0",
"symfony/property-access": "^3.4|^4.0|^5.0",
"symfony/property-info": "^3.4.13|~4.0|^5.0",
"symfony/validator": "^3.4|^4.0|^5.0",
"doctrine/annotations": "~1.0",
"symfony/dependency-injection": "^3.4|^4.0|^5.0",
"doctrine/cache": "~1.0",
"phpdocumentor/reflection-docblock": "^3.0|^4.0"
"symfony/yaml": "^3.4|^4.0|^5.0"
},
"conflict": {
"phpdocumentor/type-resolver": "<0.2.1",