[ErrorHandler] merge and remove the ErrorRenderer component
@ -18,7 +18,6 @@ Console
|
|||||||
Debug
|
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 `FlattenException` class, use the one from the `ErrorRenderer` component instead
|
||||||
* Deprecated the component in favor of the `ErrorHandler` component
|
* 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 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:
|
* 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
|
```twig
|
||||||
{
|
{
|
||||||
"@id": "https://example.com",
|
"type": "https://example.com/error",
|
||||||
"@type": "error",
|
|
||||||
"@context": {
|
|
||||||
"title": "{{ status_text }}",
|
"title": "{{ status_text }}",
|
||||||
"code": {{ status_code }},
|
"status": {{ status_code }}
|
||||||
"message": "{{ exception.message }}"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
After (`App\ErrorRenderer\JsonLdErrorRenderer`):
|
After (`App\Serializer\ProblemJsonNormalizer`):
|
||||||
```php
|
```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([
|
return 'json' === $format && $data instanceof FlattenException;
|
||||||
'@id' => 'https://example.com',
|
|
||||||
'@type' => 'error',
|
|
||||||
'@context' => [
|
|
||||||
'title' => $exception->getTitle(),
|
|
||||||
'code' => $exception->getStatusCode(),
|
|
||||||
'message' => $exception->getMessage(),
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Configure your rendering service tagging it with `error_renderer.renderer`.
|
|
||||||
|
|
||||||
Validator
|
Validator
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
@ -57,7 +57,6 @@ Console
|
|||||||
Debug
|
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 `FlattenException` class, use the one from the `ErrorRenderer` component instead
|
||||||
* Removed the component in favor of the `ErrorHandler` component
|
* Removed the component in favor of the `ErrorHandler` component
|
||||||
|
|
||||||
|
@ -48,7 +48,6 @@
|
|||||||
"symfony/dom-crawler": "self.version",
|
"symfony/dom-crawler": "self.version",
|
||||||
"symfony/dotenv": "self.version",
|
"symfony/dotenv": "self.version",
|
||||||
"symfony/error-handler": "self.version",
|
"symfony/error-handler": "self.version",
|
||||||
"symfony/error-renderer": "self.version",
|
|
||||||
"symfony/event-dispatcher": "self.version",
|
"symfony/event-dispatcher": "self.version",
|
||||||
"symfony/expression-language": "self.version",
|
"symfony/expression-language": "self.version",
|
||||||
"symfony/filesystem": "self.version",
|
"symfony/filesystem": "self.version",
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Bridge\Twig\Mime;
|
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\Header\Headers;
|
||||||
use Symfony\Component\Mime\Part\AbstractPart;
|
use Symfony\Component\Mime\Part\AbstractPart;
|
||||||
use Twig\Extra\CssInliner\CssInlinerExtension;
|
use Twig\Extra\CssInliner\CssInlinerExtension;
|
||||||
|
@ -32,7 +32,6 @@ class UnusedTagsPass implements CompilerPassInterface
|
|||||||
'controller.service_arguments',
|
'controller.service_arguments',
|
||||||
'config_cache.resource_checker',
|
'config_cache.resource_checker',
|
||||||
'data_collector',
|
'data_collector',
|
||||||
'error_renderer.renderer',
|
|
||||||
'form.type',
|
'form.type',
|
||||||
'form.type_extension',
|
'form.type_extension',
|
||||||
'form.type_guesser',
|
'form.type_guesser',
|
||||||
|
@ -33,7 +33,6 @@ use Symfony\Component\DependencyInjection\Compiler\PassConfig;
|
|||||||
use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass;
|
use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\ErrorHandler\ErrorHandler;
|
use Symfony\Component\ErrorHandler\ErrorHandler;
|
||||||
use Symfony\Component\ErrorRenderer\DependencyInjection\ErrorRendererPass;
|
|
||||||
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
|
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
|
||||||
use Symfony\Component\Form\DependencyInjection\FormPass;
|
use Symfony\Component\Form\DependencyInjection\FormPass;
|
||||||
use Symfony\Component\HttpClient\DependencyInjection\HttpClientPass;
|
use Symfony\Component\HttpClient\DependencyInjection\HttpClientPass;
|
||||||
@ -92,7 +91,6 @@ class FrameworkBundle extends Bundle
|
|||||||
KernelEvents::FINISH_REQUEST,
|
KernelEvents::FINISH_REQUEST,
|
||||||
];
|
];
|
||||||
|
|
||||||
$container->addCompilerPass(new ErrorRendererPass());
|
|
||||||
$container->addCompilerPass(new LoggerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
|
$container->addCompilerPass(new LoggerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
|
||||||
$container->addCompilerPass(new RegisterControllerArgumentLocatorsPass());
|
$container->addCompilerPass(new RegisterControllerArgumentLocatorsPass());
|
||||||
$container->addCompilerPass(new RemoveEmptyControllerArgumentLocatorsPass(), PassConfig::TYPE_BEFORE_REMOVING);
|
$container->addCompilerPass(new RemoveEmptyControllerArgumentLocatorsPass(), PassConfig::TYPE_BEFORE_REMOVING);
|
||||||
|
@ -194,12 +194,6 @@
|
|||||||
<tag name="console.command" command="debug:form" />
|
<tag name="console.command" command="debug:form" />
|
||||||
</service>
|
</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">
|
<service id="console.command.secrets_set" class="Symfony\Bundle\FrameworkBundle\Command\SecretsSetCommand">
|
||||||
<argument type="service" id="secrets.vault" />
|
<argument type="service" id="secrets.vault" />
|
||||||
<argument type="service" id="secrets.local_vault" on-invalid="ignore" />
|
<argument type="service" id="secrets.local_vault" on-invalid="ignore" />
|
||||||
|
@ -5,12 +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_renderer" class="Symfony\Component\ErrorRenderer\DependencyInjection\LazyLoadingErrorRenderer">
|
<service id="error_handler.error_renderer.html" class="Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer">
|
||||||
<argument /> <!-- error renderer locator -->
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<service id="error_renderer.renderer.html" class="Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer">
|
|
||||||
<tag name="error_renderer.renderer" />
|
|
||||||
<argument>%kernel.debug%</argument>
|
<argument>%kernel.debug%</argument>
|
||||||
<argument>%kernel.charset%</argument>
|
<argument>%kernel.charset%</argument>
|
||||||
<argument type="service" id="debug.file_link_formatter" on-invalid="null" />
|
<argument type="service" id="debug.file_link_formatter" on-invalid="null" />
|
||||||
@ -19,21 +14,15 @@
|
|||||||
<argument type="service" id="logger" on-invalid="null" />
|
<argument type="service" id="logger" on-invalid="null" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="error_renderer.renderer.json" class="Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer">
|
<service id="error_handler.error_renderer.serializer" class="Symfony\Component\ErrorHandler\ErrorRenderer\SerializerErrorRenderer">
|
||||||
<tag name="error_renderer.renderer" />
|
<argument type="service" id="serializer" />
|
||||||
|
<argument type="service" id="request_stack" />
|
||||||
|
<argument type="service" id="error_renderer.html" />
|
||||||
<argument>%kernel.debug%</argument>
|
<argument>%kernel.debug%</argument>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="error_renderer.renderer.xml" class="Symfony\Component\ErrorRenderer\ErrorRenderer\XmlErrorRenderer">
|
<service id="error_renderer.html" alias="error_handler.error_renderer.html" />
|
||||||
<tag name="error_renderer.renderer" format="atom" />
|
<service id="error_renderer.serializer" alias="error_handler.error_renderer.serializer" />
|
||||||
<tag name="error_renderer.renderer" />
|
<service id="error_renderer" alias="error_renderer.html" />
|
||||||
<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>
|
|
||||||
</services>
|
</services>
|
||||||
</container>
|
</container>
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
<services>
|
<services>
|
||||||
<defaults public="false" />
|
<defaults public="false" />
|
||||||
|
|
||||||
|
<service id="error_renderer" alias="error_renderer.serializer" />
|
||||||
|
|
||||||
<service id="serializer" class="Symfony\Component\Serializer\Serializer" public="true">
|
<service id="serializer" class="Symfony\Component\Serializer\Serializer" public="true">
|
||||||
<argument type="collection" />
|
<argument type="collection" />
|
||||||
<argument type="collection" />
|
<argument type="collection" />
|
||||||
@ -59,6 +61,12 @@
|
|||||||
<tag name="serializer.normalizer" priority="-900" />
|
<tag name="serializer.normalizer" priority="-900" />
|
||||||
</service>
|
</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">
|
<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.mapping.class_metadata_factory" />
|
||||||
<argument type="service" id="serializer.name_converter.metadata_aware" />
|
<argument type="service" id="serializer.name_converter.metadata_aware" />
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
"symfony/cache": "^4.4|^5.0",
|
"symfony/cache": "^4.4|^5.0",
|
||||||
"symfony/config": "^4.3.4|^5.0",
|
"symfony/config": "^4.3.4|^5.0",
|
||||||
"symfony/dependency-injection": "^4.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-foundation": "^4.4|^5.0",
|
||||||
"symfony/http-kernel": "^4.4",
|
"symfony/http-kernel": "^4.4",
|
||||||
"symfony/polyfill-mbstring": "~1.0",
|
"symfony/polyfill-mbstring": "~1.0",
|
||||||
@ -50,7 +49,7 @@
|
|||||||
"symfony/process": "^3.4|^4.0|^5.0",
|
"symfony/process": "^3.4|^4.0|^5.0",
|
||||||
"symfony/security-csrf": "^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/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/stopwatch": "^3.4|^4.0|^5.0",
|
||||||
"symfony/translation": "^4.4|^5.0",
|
"symfony/translation": "^4.4|^5.0",
|
||||||
"symfony/templating": "^3.4|^4.0|^5.0",
|
"symfony/templating": "^3.4|^4.0|^5.0",
|
||||||
|
@ -70,6 +70,6 @@ class JsonLoginTest extends AbstractWebTestCase
|
|||||||
|
|
||||||
$this->assertSame(400, $response->getStatusCode());
|
$this->assertSame(400, $response->getStatusCode());
|
||||||
$this->assertSame('application/json', $response->headers->get('Content-Type'));
|
$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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
imports:
|
imports:
|
||||||
- { resource: ./../config/framework.yml }
|
- { resource: ./../config/framework.yml }
|
||||||
|
|
||||||
|
framework:
|
||||||
|
serializer: ~
|
||||||
|
|
||||||
security:
|
security:
|
||||||
encoders:
|
encoders:
|
||||||
Symfony\Component\Security\Core\User\User: plaintext
|
Symfony\Component\Security\Core\User\User: plaintext
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
"symfony/form": "^3.4|^4.0|^5.0",
|
"symfony/form": "^3.4|^4.0|^5.0",
|
||||||
"symfony/framework-bundle": "^4.4|^5.0",
|
"symfony/framework-bundle": "^4.4|^5.0",
|
||||||
"symfony/http-foundation": "^3.4|^4.0|^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/translation": "^3.4|^4.0|^5.0",
|
||||||
"symfony/twig-bundle": "^4.4|^5.0",
|
"symfony/twig-bundle": "^4.4|^5.0",
|
||||||
"symfony/twig-bridge": "^3.4|^4.0|^5.0",
|
"symfony/twig-bridge": "^3.4|^4.0|^5.0",
|
||||||
|
@ -6,7 +6,7 @@ CHANGELOG
|
|||||||
|
|
||||||
* marked the `TemplateIterator` as `internal`
|
* marked the `TemplateIterator` as `internal`
|
||||||
* added HTML comment to beginning and end of `exception_full.html.twig`
|
* added HTML comment to beginning and end of `exception_full.html.twig`
|
||||||
* added a new `TwigHtmlErrorRenderer` for `html` format, integrated with the `ErrorRenderer` component
|
* 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 `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 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
|
* deprecated `twig.exception_controller` configuration option, set it to "null" and use `framework.error_controller` configuration instead
|
||||||
|
@ -11,9 +11,9 @@
|
|||||||
|
|
||||||
namespace Symfony\Bundle\TwigBundle\ErrorRenderer;
|
namespace Symfony\Bundle\TwigBundle\ErrorRenderer;
|
||||||
|
|
||||||
use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface;
|
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface;
|
||||||
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
|
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
|
||||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
use Twig\Environment;
|
use Twig\Environment;
|
||||||
use Twig\Error\LoaderError;
|
use Twig\Error\LoaderError;
|
||||||
use Twig\Loader\ExistsLoaderInterface;
|
use Twig\Loader\ExistsLoaderInterface;
|
||||||
@ -40,34 +40,20 @@ class TwigHtmlErrorRenderer implements ErrorRendererInterface
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public static function getFormat(): string
|
public function render(\Throwable $exception): FlattenException
|
||||||
{
|
{
|
||||||
return 'html';
|
$exception = $this->htmlErrorRenderer->render($exception);
|
||||||
|
|
||||||
|
if ($this->debug || !$template = $this->findTemplate($exception->getStatusCode());
|
||||||
|
return $exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return $exception->setAsString($this->twig->render($template, [
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function render(FlattenException $exception): string
|
|
||||||
{
|
|
||||||
$debug = $this->debug && ($exception->getHeaders()['X-Debug'] ?? true);
|
|
||||||
|
|
||||||
if ($debug) {
|
|
||||||
return $this->htmlErrorRenderer->render($exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
$template = $this->findTemplate($exception->getStatusCode());
|
|
||||||
|
|
||||||
if (null === $template) {
|
|
||||||
return $this->htmlErrorRenderer->render($exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->twig->render($template, [
|
|
||||||
'legacy' => false, // to be removed in 5.0
|
'legacy' => false, // to be removed in 5.0
|
||||||
'exception' => $exception,
|
'exception' => $exception,
|
||||||
'status_code' => $exception->getStatusCode(),
|
'status_code' => $exception->getStatusCode(),
|
||||||
'status_text' => $exception->getTitle(),
|
'status_text' => $exception->getStatusText(),
|
||||||
]);
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function findTemplate(int $statusCode): ?string
|
private function findTemplate(int $statusCode): ?string
|
||||||
|
@ -162,10 +162,9 @@
|
|||||||
<argument /> <!-- runtime locator -->
|
<argument /> <!-- runtime locator -->
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="twig.error_renderer.html" class="Symfony\Bundle\TwigBundle\ErrorRenderer\TwigHtmlErrorRenderer">
|
<service id="twig.error_renderer.html" class="Symfony\Bundle\TwigBundle\ErrorRenderer\TwigHtmlErrorRenderer" decorates="error_renderer.html">
|
||||||
<tag name="error_renderer.renderer" priority="1" />
|
|
||||||
<argument type="service" id="twig" />
|
<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>
|
<argument>%kernel.debug%</argument>
|
||||||
</service>
|
</service>
|
||||||
</services>
|
</services>
|
||||||
|
@ -13,8 +13,7 @@ namespace Symfony\Bundle\TwigBundle\Tests\ErrorRenderer;
|
|||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Bundle\TwigBundle\ErrorRenderer\TwigHtmlErrorRenderer;
|
use Symfony\Bundle\TwigBundle\ErrorRenderer\TwigHtmlErrorRenderer;
|
||||||
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
|
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
|
||||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
use Twig\Environment;
|
use Twig\Environment;
|
||||||
use Twig\Loader\ArrayLoader;
|
use Twig\Loader\ArrayLoader;
|
||||||
@ -23,7 +22,7 @@ class TwigHtmlErrorRendererTest extends TestCase
|
|||||||
{
|
{
|
||||||
public function testFallbackToNativeRendererIfDebugOn()
|
public function testFallbackToNativeRendererIfDebugOn()
|
||||||
{
|
{
|
||||||
$exception = FlattenException::createFromThrowable(new \Exception());
|
$exception = new \Exception();
|
||||||
|
|
||||||
$twig = $this->createMock(Environment::class);
|
$twig = $this->createMock(Environment::class);
|
||||||
$nativeRenderer = $this->createMock(HtmlErrorRenderer::class);
|
$nativeRenderer = $this->createMock(HtmlErrorRenderer::class);
|
||||||
@ -33,12 +32,12 @@ class TwigHtmlErrorRendererTest extends TestCase
|
|||||||
->with($exception)
|
->with($exception)
|
||||||
;
|
;
|
||||||
|
|
||||||
(new TwigHtmlErrorRenderer($twig, $nativeRenderer, true))->render($exception);
|
(new TwigHtmlErrorRenderer($twig, $nativeRenderer, true))->render(new \Exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFallbackToNativeRendererIfCustomTemplateNotFound()
|
public function testFallbackToNativeRendererIfCustomTemplateNotFound()
|
||||||
{
|
{
|
||||||
$exception = FlattenException::createFromThrowable(new NotFoundHttpException());
|
$exception = new NotFoundHttpException();
|
||||||
|
|
||||||
$twig = new Environment(new ArrayLoader([]));
|
$twig = new Environment(new ArrayLoader([]));
|
||||||
|
|
||||||
@ -54,7 +53,7 @@ class TwigHtmlErrorRendererTest extends TestCase
|
|||||||
|
|
||||||
public function testRenderCustomErrorTemplate()
|
public function testRenderCustomErrorTemplate()
|
||||||
{
|
{
|
||||||
$exception = FlattenException::createFromThrowable(new NotFoundHttpException());
|
$exception = new NotFoundHttpException();
|
||||||
|
|
||||||
$twig = new Environment(new ArrayLoader([
|
$twig = new Environment(new ArrayLoader([
|
||||||
'@Twig/Exception/error404.html.twig' => '<h1>Page Not Found</h1>',
|
'@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);
|
$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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ use Symfony\Bundle\TwigBundle\Tests\TestCase;
|
|||||||
use Symfony\Bundle\TwigBundle\TwigBundle;
|
use Symfony\Bundle\TwigBundle\TwigBundle;
|
||||||
use Symfony\Component\Config\Loader\LoaderInterface;
|
use Symfony\Component\Config\Loader\LoaderInterface;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\ErrorRenderer\ErrorRenderer;
|
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
use Symfony\Component\HttpKernel\Kernel;
|
use Symfony\Component\HttpKernel\Kernel;
|
||||||
|
|
||||||
@ -65,7 +65,8 @@ class EmptyAppKernel extends Kernel
|
|||||||
'strict_variables' => false,
|
'strict_variables' => false,
|
||||||
'exception_controller' => null,
|
'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);
|
$container->setParameter('debug.file_link_format', null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"symfony/error-renderer": "^4.4|^5.0",
|
|
||||||
"symfony/twig-bridge": "^4.4|^5.0",
|
"symfony/twig-bridge": "^4.4|^5.0",
|
||||||
"symfony/http-foundation": "^4.3|^5.0",
|
"symfony/http-foundation": "^4.3|^5.0",
|
||||||
"symfony/http-kernel": "^4.4",
|
"symfony/http-kernel": "^4.4",
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Bundle\WebProfilerBundle\Controller;
|
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\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
|
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
@ -42,11 +42,7 @@ class ExceptionController
|
|||||||
$this->profiler = $profiler;
|
$this->profiler = $profiler;
|
||||||
$this->twig = $twig;
|
$this->twig = $twig;
|
||||||
$this->debug = $debug;
|
$this->debug = $debug;
|
||||||
$this->errorRenderer = $errorRenderer;
|
$this->errorRenderer = $errorRenderer ?? new HtmlErrorRenderer($debug, $this->twig->getCharset(), $fileLinkFormat);
|
||||||
|
|
||||||
if (null === $errorRenderer) {
|
|
||||||
$this->errorRenderer = new HtmlErrorRenderer($debug, $this->twig->getCharset(), $fileLinkFormat);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Bundle\WebProfilerBundle\Controller;
|
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\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
use Symfony\Component\HttpKernel\Profiler\Profiler;
|
use Symfony\Component\HttpKernel\Profiler\Profiler;
|
||||||
@ -25,12 +25,12 @@ use Symfony\Component\HttpKernel\Profiler\Profiler;
|
|||||||
*/
|
*/
|
||||||
class ExceptionPanelController
|
class ExceptionPanelController
|
||||||
{
|
{
|
||||||
private $htmlErrorRenderer;
|
private $errorRenderer;
|
||||||
private $profiler;
|
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;
|
$this->profiler = $profiler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ class ExceptionPanelController
|
|||||||
->getException()
|
->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
|
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']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,12 +27,12 @@
|
|||||||
<argument type="service" id="twig" />
|
<argument type="service" id="twig" />
|
||||||
<argument>%kernel.debug%</argument>
|
<argument>%kernel.debug%</argument>
|
||||||
<argument type="service" id="debug.file_link_formatter" />
|
<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>
|
<deprecated>The "%service_id%" service is deprecated since Symfony 4.4, use the "web_profiler.controller.exception_panel" service instead.</deprecated>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="web_profiler.controller.exception_panel" class="Symfony\Bundle\WebProfilerBundle\Controller\ExceptionPanelController" public="true">
|
<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" />
|
<argument type="service" id="profiler" on-invalid="null" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ use Symfony\Component\DependencyInjection\Container;
|
|||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Definition;
|
use Symfony\Component\DependencyInjection\Definition;
|
||||||
use Symfony\Component\DependencyInjection\Reference;
|
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\DependencyInjection\RegisterListenersPass;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ class WebProfilerExtensionTest extends TestCase
|
|||||||
$this->kernel = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\KernelInterface')->getMock();
|
$this->kernel = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\KernelInterface')->getMock();
|
||||||
|
|
||||||
$this->container = new ContainerBuilder();
|
$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('event_dispatcher', EventDispatcher::class)->setPublic(true);
|
||||||
$this->container->register('router', $this->getMockClass('Symfony\\Component\\Routing\\RouterInterface'))->setPublic(true);
|
$this->container->register('router', $this->getMockClass('Symfony\\Component\\Routing\\RouterInterface'))->setPublic(true);
|
||||||
$this->container->register('twig', 'Twig\Environment')->setPublic(true);
|
$this->container->register('twig', 'Twig\Environment')->setPublic(true);
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"symfony/config": "^4.2|^5.0",
|
"symfony/config": "^4.2|^5.0",
|
||||||
"symfony/error-renderer": "^4.4|^5.0",
|
|
||||||
"symfony/http-kernel": "^4.4",
|
"symfony/http-kernel": "^4.4",
|
||||||
"symfony/routing": "^3.4|^4.0|^5.0",
|
"symfony/routing": "^3.4|^4.0|^5.0",
|
||||||
"symfony/twig-bundle": "^4.2|^5.0",
|
"symfony/twig-bundle": "^4.2|^5.0",
|
||||||
|
@ -4,7 +4,7 @@ CHANGELOG
|
|||||||
4.4.0
|
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
|
* deprecated the whole component in favor of the `ErrorHandler` component
|
||||||
|
|
||||||
4.3.0
|
4.3.0
|
||||||
|
@ -21,7 +21,7 @@ use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
|||||||
*
|
*
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
* @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
|
class FlattenException
|
||||||
{
|
{
|
||||||
|
@ -34,4 +34,25 @@ class BufferingLogger extends AbstractLogger
|
|||||||
|
|
||||||
return $logs;
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,39 +18,19 @@ namespace Symfony\Component\ErrorHandler;
|
|||||||
*/
|
*/
|
||||||
class Debug
|
class Debug
|
||||||
{
|
{
|
||||||
private static $enabled = false;
|
public static function enable(): ErrorHandler
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
{
|
{
|
||||||
if (static::$enabled) {
|
error_reporting(-1);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
static::$enabled = true;
|
|
||||||
|
|
||||||
if (null !== $errorReportingLevel) {
|
|
||||||
error_reporting($errorReportingLevel);
|
|
||||||
} else {
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
|
if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
|
||||||
ini_set('display_errors', 0);
|
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
|
// CLI - display errors only if they're not already logged to STDERR
|
||||||
ini_set('display_errors', 1);
|
ini_set('display_errors', 1);
|
||||||
}
|
}
|
||||||
if ($displayErrors) {
|
|
||||||
ErrorHandler::register(new ErrorHandler(new BufferingLogger()));
|
|
||||||
} else {
|
|
||||||
ErrorHandler::register()->throwAt(0, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
DebugClassLoader::enable();
|
DebugClassLoader::enable();
|
||||||
|
|
||||||
|
return ErrorHandler::register(new ErrorHandler(new BufferingLogger()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,10 @@ use Symfony\Component\ErrorHandler\ErrorEnhancer\ClassNotFoundErrorEnhancer;
|
|||||||
use Symfony\Component\ErrorHandler\ErrorEnhancer\ErrorEnhancerInterface;
|
use Symfony\Component\ErrorHandler\ErrorEnhancer\ErrorEnhancerInterface;
|
||||||
use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedFunctionErrorEnhancer;
|
use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedFunctionErrorEnhancer;
|
||||||
use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedMethodErrorEnhancer;
|
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\ErrorHandler\Exception\SilencedErrorContext;
|
||||||
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
|
|
||||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic ErrorHandler for the PHP engine.
|
* A generic ErrorHandler for the PHP engine.
|
||||||
@ -45,10 +46,8 @@ use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
|||||||
*
|
*
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||||
*
|
|
||||||
* @final since Symfony 4.3
|
|
||||||
*/
|
*/
|
||||||
class ErrorHandler
|
final class ErrorHandler
|
||||||
{
|
{
|
||||||
private $levels = [
|
private $levels = [
|
||||||
E_DEPRECATED => 'Deprecated',
|
E_DEPRECATED => 'Deprecated',
|
||||||
@ -145,10 +144,8 @@ class ErrorHandler
|
|||||||
$handler->setExceptionHandler($p);
|
$handler->setExceptionHandler($p);
|
||||||
$prev[0]->setExceptionHandler($p);
|
$prev[0]->setExceptionHandler($p);
|
||||||
}
|
}
|
||||||
} elseif (null === $prev && !\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
|
|
||||||
$handler->setExceptionHandler([$handler, 'sendPhpResponse']);
|
|
||||||
} else {
|
} else {
|
||||||
$handler->setExceptionHandler($prev);
|
$handler->setExceptionHandler($prev ?? [$handler, 'renderException']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$handler->throwAt(E_ALL & $handler->thrownErrors, true);
|
$handler->throwAt(E_ALL & $handler->thrownErrors, true);
|
||||||
@ -280,7 +277,7 @@ class ErrorHandler
|
|||||||
/**
|
/**
|
||||||
* Sets a user exception handler.
|
* 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
|
* @return callable|null The previous exception handler
|
||||||
*/
|
*/
|
||||||
@ -583,11 +580,7 @@ class ErrorHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
$exceptionHandler = $this->exceptionHandler;
|
$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 {
|
try {
|
||||||
if (null !== $exceptionHandler) {
|
if (null !== $exceptionHandler) {
|
||||||
return $exceptionHandler($exception);
|
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
|
* As this method is mainly called during boot where nothing is yet available,
|
||||||
* available, the Response content is always HTML.
|
* 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';
|
$renderer = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliErrorRenderer() : new HtmlErrorRenderer(0 !== $this->scopedErrors);
|
||||||
$statusCode = 500;
|
|
||||||
$headers = [];
|
|
||||||
|
|
||||||
if (class_exists(HtmlErrorRenderer::class)) {
|
$exception = $renderer->render($exception);
|
||||||
$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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!headers_sent()) {
|
if (!headers_sent()) {
|
||||||
header(sprintf('HTTP/1.0 %s', $statusCode));
|
http_response_code($exception->getStatusCode());
|
||||||
foreach ($headers as $name => $value) {
|
|
||||||
|
foreach ($exception->getHeaders() as $name => $value) {
|
||||||
header($name.': '.$value, false);
|
header($name.': '.$value, false);
|
||||||
}
|
}
|
||||||
header('Content-Type: text/html; charset='.$charset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
echo $response;
|
echo $exception->getAsString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
@ -9,10 +9,10 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\ErrorRenderer\ErrorRenderer;
|
namespace Symfony\Component\ErrorHandler\ErrorRenderer;
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
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\RequestStack;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
|
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
|
||||||
@ -52,23 +52,17 @@ class HtmlErrorRenderer implements ErrorRendererInterface
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public static function getFormat(): string
|
public function render(\Throwable $exception): FlattenException
|
||||||
{
|
{
|
||||||
return 'html';
|
$exception = FlattenException::createFromThrowable($exception, null, [
|
||||||
}
|
'Content-Type' => 'text/html; charset='.$this->charset,
|
||||||
|
]);
|
||||||
|
|
||||||
/**
|
return $exception->setAsString($this->renderException($exception));
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function render(FlattenException $exception): string
|
|
||||||
{
|
|
||||||
return $this->renderException($exception);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the HTML content associated with the given exception.
|
* Gets the HTML content associated with the given exception.
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
*/
|
||||||
public function getBody(FlattenException $exception): string
|
public function getBody(FlattenException $exception): string
|
||||||
{
|
{
|
||||||
@ -77,8 +71,6 @@ class HtmlErrorRenderer implements ErrorRendererInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the stylesheet associated with the given exception.
|
* Gets the stylesheet associated with the given exception.
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
*/
|
||||||
public function getStylesheet(): string
|
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
|
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->getStatusText());
|
||||||
$statusText = $this->escape($exception->getTitle());
|
|
||||||
$statusCode = $this->escape($exception->getStatusCode());
|
$statusCode = $this->escape($exception->getStatusCode());
|
||||||
|
|
||||||
if (!$debug) {
|
if (!$this->debug) {
|
||||||
return $this->include('views/error.html.php', [
|
return $this->include('views/error.html.php', [
|
||||||
'statusText' => $statusText,
|
'statusText' => $statusText,
|
||||||
'statusCode' => $statusCode,
|
'statusCode' => $statusCode,
|
||||||
@ -111,13 +102,13 @@ class HtmlErrorRenderer implements ErrorRendererInterface
|
|||||||
'statusText' => $statusText,
|
'statusText' => $statusText,
|
||||||
'statusCode' => $statusCode,
|
'statusCode' => $statusCode,
|
||||||
'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null,
|
'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 '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,7 +312,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
|
|||||||
{
|
{
|
||||||
extract($context, EXTR_SKIP);
|
extract($context, EXTR_SKIP);
|
||||||
ob_start();
|
ob_start();
|
||||||
include __DIR__.'/../Resources/'.$name;
|
include __DIR__ . '/../Resources/' .$name;
|
||||||
|
|
||||||
return trim(ob_get_clean());
|
return trim(ob_get_clean());
|
||||||
}
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,7 @@
|
|||||||
* file that was distributed with this source code.
|
* 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\Debug\Exception\FlattenException as LegacyFlattenException;
|
||||||
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
|
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
|
||||||
@ -25,7 +25,6 @@ use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
class FlattenException extends LegacyFlattenException
|
class FlattenException extends LegacyFlattenException
|
||||||
{
|
{
|
||||||
private $title;
|
|
||||||
private $message;
|
private $message;
|
||||||
private $code;
|
private $code;
|
||||||
private $previous;
|
private $previous;
|
||||||
@ -33,9 +32,11 @@ class FlattenException extends LegacyFlattenException
|
|||||||
private $traceAsString;
|
private $traceAsString;
|
||||||
private $class;
|
private $class;
|
||||||
private $statusCode;
|
private $statusCode;
|
||||||
|
private $statusText;
|
||||||
private $headers;
|
private $headers;
|
||||||
private $file;
|
private $file;
|
||||||
private $line;
|
private $line;
|
||||||
|
private $asString;
|
||||||
|
|
||||||
public static function create(\Exception $exception, $statusCode = null, array $headers = []): self
|
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])) {
|
if (class_exists(Response::class) && isset(Response::$statusTexts[$statusCode])) {
|
||||||
$title = Response::$statusTexts[$statusCode];
|
$statusText = Response::$statusTexts[$statusCode];
|
||||||
} else {
|
} 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->setStatusCode($statusCode);
|
||||||
$e->setHeaders($headers);
|
$e->setHeaders($headers);
|
||||||
$e->setTraceFromThrowable($exception);
|
$e->setTraceFromThrowable($exception);
|
||||||
@ -171,14 +172,14 @@ class FlattenException extends LegacyFlattenException
|
|||||||
return $this;
|
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;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -355,8 +356,19 @@ class FlattenException extends LegacyFlattenException
|
|||||||
return $this->traceAsString;
|
return $this->traceAsString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setAsString(?string $asString)
|
||||||
|
{
|
||||||
|
$this->asString = $asString;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function getAsString()
|
public function getAsString()
|
||||||
{
|
{
|
||||||
|
if (null !== $this->asString) {
|
||||||
|
return $this->asString;
|
||||||
|
}
|
||||||
|
|
||||||
$message = '';
|
$message = '';
|
||||||
$next = false;
|
$next = false;
|
||||||
|
|
Before Width: | Height: | Size: 276 B After Width: | Height: | Size: 276 B |
Before Width: | Height: | Size: 913 B After Width: | Height: | Size: 913 B |
Before Width: | Height: | Size: 432 B After Width: | Height: | Size: 432 B |
Before Width: | Height: | Size: 337 B After Width: | Height: | Size: 337 B |
Before Width: | Height: | Size: 526 B After Width: | Height: | Size: 526 B |
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 442 B |
Before Width: | Height: | Size: 634 B After Width: | Height: | Size: 634 B |
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 942 B After Width: | Height: | Size: 942 B |
@ -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
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
@ -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; ?> -->
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -9,21 +9,20 @@
|
|||||||
* file that was distributed with this source code.
|
* 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 PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface;
|
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
|
||||||
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
|
||||||
|
|
||||||
class HtmlErrorRendererTest extends TestCase
|
class HtmlErrorRendererTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @dataProvider getRenderData
|
* @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
|
public function getRenderData(): iterable
|
||||||
@ -55,17 +54,5 @@ HTML;
|
|||||||
new HtmlErrorRenderer(false),
|
new HtmlErrorRenderer(false),
|
||||||
$expectedNonDebug,
|
$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,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,10 +9,10 @@
|
|||||||
* file that was distributed with this source code.
|
* 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 PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
|
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
|
||||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
@ -17,20 +17,18 @@
|
|||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1.3",
|
"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": {
|
"require-dev": {
|
||||||
"symfony/http-kernel": "^3.4|^4.0|^5.0"
|
"symfony/http-kernel": "^4.4|^5.0"
|
||||||
},
|
|
||||||
"suggest": {
|
|
||||||
"symfony/error-renderer": "For better error rendering"
|
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"symfony/http-kernel": "<3.4"
|
"symfony/http-kernel": "<4.4"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": { "Symfony\\Component\\ErrorHandler\\": "" },
|
"psr-4": { "Symfony\\Component\\ErrorHandler\\": "" },
|
||||||
"classmap": [ "Resources/stubs/Debug.php" ],
|
|
||||||
"exclude-from-classmap": [
|
"exclude-from-classmap": [
|
||||||
"/Tests/"
|
"/Tests/"
|
||||||
]
|
]
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
/Tests export-ignore
|
|
||||||
/phpunit.xml.dist export-ignore
|
|
||||||
/.gitignore export-ignore
|
|
@ -1,3 +0,0 @@
|
|||||||
vendor/
|
|
||||||
composer.lock
|
|
||||||
phpunit.xml
|
|
@ -1,7 +0,0 @@
|
|||||||
CHANGELOG
|
|
||||||
=========
|
|
||||||
|
|
||||||
4.4.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* added the component
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
{
|
|
||||||
}
|
|
@ -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.
|
|
@ -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)
|
|
@ -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; ?> -->
|
|
@ -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]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
@ -11,9 +11,8 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\HttpKernel\Controller;
|
namespace Symfony\Component\HttpKernel\Controller;
|
||||||
|
|
||||||
use Symfony\Component\ErrorRenderer\ErrorRenderer;
|
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface;
|
||||||
use Symfony\Component\ErrorRenderer\Exception\ErrorRendererNotFoundException;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||||
@ -30,26 +29,22 @@ class ErrorController
|
|||||||
private $controller;
|
private $controller;
|
||||||
private $errorRenderer;
|
private $errorRenderer;
|
||||||
|
|
||||||
public function __construct(HttpKernelInterface $kernel, $controller, ErrorRenderer $errorRenderer)
|
public function __construct(HttpKernelInterface $kernel, $controller, ErrorRendererInterface $errorRenderer)
|
||||||
{
|
{
|
||||||
$this->kernel = $kernel;
|
$this->kernel = $kernel;
|
||||||
$this->controller = $controller;
|
$this->controller = $controller;
|
||||||
$this->errorRenderer = $errorRenderer;
|
$this->errorRenderer = $errorRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __invoke(Request $request, FlattenException $exception): Response
|
public function __invoke(\Throwable $exception): Response
|
||||||
{
|
{
|
||||||
try {
|
$exception = $this->errorRenderer->render($exception);
|
||||||
return new Response($this->errorRenderer->render($exception, $request->getPreferredFormat()), $exception->getStatusCode(), $exception->getHeaders());
|
|
||||||
} catch (ErrorRendererNotFoundException $_) {
|
return new Response($exception->getAsString(), $exception->getStatusCode(), $exception->getHeaders());
|
||||||
return new Response($this->errorRenderer->render($exception), $exception->getStatusCode(), $exception->getHeaders());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function preview(Request $request, int $code): Response
|
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
|
* This Request mimics the parameters set by
|
||||||
* \Symfony\Component\HttpKernel\EventListener\ErrorListener::duplicateRequest, with
|
* \Symfony\Component\HttpKernel\EventListener\ErrorListener::duplicateRequest, with
|
||||||
@ -57,7 +52,7 @@ class ErrorController
|
|||||||
*/
|
*/
|
||||||
$subRequest = $request->duplicate(null, null, [
|
$subRequest = $request->duplicate(null, null, [
|
||||||
'_controller' => $this->controller,
|
'_controller' => $this->controller,
|
||||||
'exception' => $exception,
|
'exception' => new \Exception('This is a sample exception.'),
|
||||||
'logger' => null,
|
'logger' => null,
|
||||||
'showException' => false,
|
'showException' => false,
|
||||||
]);
|
]);
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\HttpKernel\DataCollector;
|
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\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ class ErrorListener implements EventSubscriberInterface
|
|||||||
{
|
{
|
||||||
$attributes = [
|
$attributes = [
|
||||||
'_controller' => $this->controller,
|
'_controller' => $this->controller,
|
||||||
'exception' => FlattenException::createFromThrowable($exception),
|
'exception' => $exception,
|
||||||
'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null,
|
'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null,
|
||||||
];
|
];
|
||||||
$request = $request->duplicate(null, null, $attributes);
|
$request = $request->duplicate(null, null, $attributes);
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
namespace Symfony\Component\HttpKernel\EventListener;
|
namespace Symfony\Component\HttpKernel\EventListener;
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
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\EventDispatcherInterface;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
@ -12,10 +12,9 @@
|
|||||||
namespace Symfony\Component\HttpKernel\Tests\Controller;
|
namespace Symfony\Component\HttpKernel\Tests\Controller;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\ErrorRenderer\ErrorRenderer;
|
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRenderer;
|
||||||
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
|
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
|
||||||
use Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\Controller\ErrorController;
|
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)
|
public function testInvokeController(Request $request, FlattenException $exception, int $statusCode, string $content)
|
||||||
{
|
{
|
||||||
$kernel = $this->getMockBuilder(HttpKernelInterface::class)->getMock();
|
$kernel = $this->getMockBuilder(HttpKernelInterface::class)->getMock();
|
||||||
$errorRenderer = new ErrorRenderer([new HtmlErrorRenderer(), new JsonErrorRenderer()]);
|
$errorRenderer = new ErrorRenderer(new HtmlErrorRenderer());
|
||||||
$controller = new ErrorController($kernel, null, $errorRenderer);
|
$controller = new ErrorController($kernel, null, $errorRenderer);
|
||||||
$response = $controller($request, $exception);
|
$response = $controller($request, $exception);
|
||||||
|
|
||||||
@ -55,33 +54,6 @@ class ErrorControllerTest extends TestCase
|
|||||||
'The server returned a "404 Not Found".',
|
'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 = new Request();
|
||||||
$request->attributes->set('_format', 'unknown');
|
$request->attributes->set('_format', 'unknown');
|
||||||
yield 'default HTML format for unknown formats' => [
|
yield 'default HTML format for unknown formats' => [
|
||||||
@ -116,7 +88,7 @@ class ErrorControllerTest extends TestCase
|
|||||||
)
|
)
|
||||||
->willReturn($response = new Response());
|
->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));
|
$this->assertSame($response, $controller->preview(new Request(), $code));
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
namespace Symfony\Component\HttpKernel\Tests\DataCollector;
|
namespace Symfony\Component\HttpKernel\Tests\DataCollector;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
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\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector;
|
use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector;
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"symfony/error-handler": "^4.4|^5.0",
|
"symfony/error-handler": "^4.4|^5.0",
|
||||||
"symfony/error-renderer": "^4.4|^5.0",
|
|
||||||
"symfony/event-dispatcher": "^4.4",
|
"symfony/event-dispatcher": "^4.4",
|
||||||
"symfony/http-foundation": "^4.4|^5.0",
|
"symfony/http-foundation": "^4.4|^5.0",
|
||||||
"symfony/polyfill-ctype": "^1.8",
|
"symfony/polyfill-ctype": "^1.8",
|
||||||
@ -40,7 +39,6 @@
|
|||||||
"symfony/templating": "^3.4|^4.0|^5.0",
|
"symfony/templating": "^3.4|^4.0|^5.0",
|
||||||
"symfony/translation": "^4.2|^5.0",
|
"symfony/translation": "^4.2|^5.0",
|
||||||
"symfony/translation-contracts": "^1.1|^2",
|
"symfony/translation-contracts": "^1.1|^2",
|
||||||
"symfony/var-dumper": "^4.1.1|^5.0",
|
|
||||||
"psr/cache": "~1.0",
|
"psr/cache": "~1.0",
|
||||||
"twig/twig": "^1.34|^2.4|^3.0"
|
"twig/twig": "^1.34|^2.4|^3.0"
|
||||||
},
|
},
|
||||||
@ -53,15 +51,13 @@
|
|||||||
"symfony/console": ">=5",
|
"symfony/console": ">=5",
|
||||||
"symfony/dependency-injection": "<4.3",
|
"symfony/dependency-injection": "<4.3",
|
||||||
"symfony/translation": "<4.2",
|
"symfony/translation": "<4.2",
|
||||||
"symfony/var-dumper": "<4.1.1",
|
|
||||||
"twig/twig": "<1.34|<2.4,>=2"
|
"twig/twig": "<1.34|<2.4,>=2"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"symfony/browser-kit": "",
|
"symfony/browser-kit": "",
|
||||||
"symfony/config": "",
|
"symfony/config": "",
|
||||||
"symfony/console": "",
|
"symfony/console": "",
|
||||||
"symfony/dependency-injection": "",
|
"symfony/dependency-injection": ""
|
||||||
"symfony/var-dumper": ""
|
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": { "Symfony\\Component\\HttpKernel\\": "" },
|
"psr-4": { "Symfony\\Component\\HttpKernel\\": "" },
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
namespace Symfony\Component\Messenger\EventListener;
|
namespace Symfony\Component\Messenger\EventListener;
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
||||||
use Symfony\Component\Messenger\Exception\HandlerFailedException;
|
use Symfony\Component\Messenger\Exception\HandlerFailedException;
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Messenger\Stamp;
|
namespace Symfony\Component\Messenger\Stamp;
|
||||||
|
|
||||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
use Symfony\Component\Messenger\Envelope;
|
use Symfony\Component\Messenger\Envelope;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
namespace Symfony\Component\Messenger\Tests\Stamp;
|
namespace Symfony\Component\Messenger\Tests\Stamp;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
||||||
|
|
||||||
class RedeliveryStampTest extends TestCase
|
class RedeliveryStampTest extends TestCase
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
"doctrine/persistence": "~1.0",
|
"doctrine/persistence": "~1.0",
|
||||||
"symfony/console": "^3.4|^4.0|^5.0",
|
"symfony/console": "^3.4|^4.0|^5.0",
|
||||||
"symfony/dependency-injection": "^3.4.19|^4.1.8|^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/event-dispatcher": "^4.3|^5.0",
|
||||||
"symfony/http-kernel": "^4.4",
|
"symfony/http-kernel": "^4.4",
|
||||||
"symfony/process": "^3.4|^4.0|^5.0",
|
"symfony/process": "^3.4|^4.0|^5.0",
|
||||||
|
@ -6,6 +6,7 @@ CHANGELOG
|
|||||||
|
|
||||||
* deprecated the `XmlEncoder::TYPE_CASE_ATTRIBUTES` constant, use `XmlEncoder::TYPE_CAST_ATTRIBUTES` instead
|
* 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 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
|
4.3.0
|
||||||
-----
|
-----
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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'))));
|
||||||
|
}
|
||||||
|
}
|
@ -20,17 +20,18 @@
|
|||||||
"symfony/polyfill-ctype": "~1.8"
|
"symfony/polyfill-ctype": "~1.8"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/yaml": "^3.4|^4.0|^5.0",
|
"doctrine/annotations": "~1.0",
|
||||||
"symfony/config": "^3.4|^4.0|^5.0",
|
"doctrine/cache": "~1.0",
|
||||||
"symfony/property-access": "^3.4|^4.0|^5.0",
|
"phpdocumentor/reflection-docblock": "^3.0|^4.0",
|
||||||
"symfony/http-foundation": "^3.4|^4.0|^5.0",
|
|
||||||
"symfony/cache": "^3.4|^4.0|^5.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/property-info": "^3.4.13|~4.0|^5.0",
|
||||||
"symfony/validator": "^3.4|^4.0|^5.0",
|
"symfony/validator": "^3.4|^4.0|^5.0",
|
||||||
"doctrine/annotations": "~1.0",
|
"symfony/yaml": "^3.4|^4.0|^5.0"
|
||||||
"symfony/dependency-injection": "^3.4|^4.0|^5.0",
|
|
||||||
"doctrine/cache": "~1.0",
|
|
||||||
"phpdocumentor/reflection-docblock": "^3.0|^4.0"
|
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"phpdocumentor/type-resolver": "<0.2.1",
|
"phpdocumentor/type-resolver": "<0.2.1",
|
||||||
|