From cccb67802ee0f48a14a68dfbac033af505dbb729 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 17 Apr 2020 17:02:26 +0200 Subject: [PATCH] add listener mapping exceptions to status codes --- Controller/ExceptionController.php | 4 +- .../Compiler/JMSHandlersPass.php | 5 +- DependencyInjection/Configuration.php | 46 +++- DependencyInjection/FOSRestExtension.php | 72 +++++-- EventListener/ExceptionListener.php | 4 +- EventListener/ResponseStatusCodeListener.php | 85 ++++++++ Resources/config/exception_listener.xml | 18 ++ Serializer/Normalizer/ExceptionHandler.php | 4 +- Serializer/Normalizer/ExceptionNormalizer.php | 4 +- .../Normalizer/FlattenExceptionHandler.php | 109 ++++++++++ .../Normalizer/FlattenExceptionNormalizer.php | 78 +++++++ Tests/Controller/ExceptionControllerTest.php | 3 + .../DependencyInjection/ConfigurationTest.php | 18 +- .../FOSRestExtensionTest.php | 202 +++++++++++++++--- .../ResponseStatusCodeListenerTest.php | 186 ++++++++++++++++ .../JmsSerializerErrorRenderer.php | 37 ++++ Tests/Functional/DependencyInjectionTest.php | 3 +- Tests/Functional/ParamFetcherTest.php | 6 +- Tests/Functional/SerializerErrorTest.php | 159 +++++++++++++- .../app/AllowedMethodsListener/config.yml | 4 +- Tests/Functional/app/Configuration/config.yml | 1 - .../bundles.php | 17 ++ .../config.yml | 24 +++ .../bundles.php | 17 ++ .../config.yml | 24 +++ .../bundles.php | 16 ++ .../config.yml | 19 ++ .../bundles.php | 16 ++ .../config.yml | 20 ++ .../bundles.php | 16 ++ .../config.yml | 19 ++ .../app/FormErrorHandler/bundles.php | 17 ++ .../app/FormErrorHandler/config.yml | 15 ++ .../app/FormErrorNormalizer/bundles.php | 16 ++ .../app/FormErrorNormalizer/config.yml | 19 ++ Tests/Functional/app/ParamFetcher/config.yml | 6 +- .../app/RequestBodyParamConverter/config.yml | 4 +- .../config.yml | 4 +- .../config.yml | 2 + Tests/Functional/app/Version/config.yml | 4 +- .../app/ViewResponseListener/config.yml | 4 +- .../config.yml | 3 +- UPGRADING-2.8.md | 26 ++- UPGRADING-3.0.md | 6 +- Util/ExceptionValueMap.php | 8 + 45 files changed, 1271 insertions(+), 99 deletions(-) create mode 100644 EventListener/ResponseStatusCodeListener.php create mode 100644 Serializer/Normalizer/FlattenExceptionHandler.php create mode 100644 Serializer/Normalizer/FlattenExceptionNormalizer.php create mode 100644 Tests/EventListener/ResponseStatusCodeListenerTest.php create mode 100644 Tests/Functional/Bundle/TestBundle/ErrorRenderer/JmsSerializerErrorRenderer.php create mode 100644 Tests/Functional/app/FlattenExceptionHandlerLegacyFormat/bundles.php create mode 100644 Tests/Functional/app/FlattenExceptionHandlerLegacyFormat/config.yml create mode 100644 Tests/Functional/app/FlattenExceptionHandlerRfc7807Format/bundles.php create mode 100644 Tests/Functional/app/FlattenExceptionHandlerRfc7807Format/config.yml create mode 100644 Tests/Functional/app/FlattenExceptionNormalizerLegacyFormat/bundles.php create mode 100644 Tests/Functional/app/FlattenExceptionNormalizerLegacyFormat/config.yml create mode 100644 Tests/Functional/app/FlattenExceptionNormalizerLegacyFormatDebug/bundles.php create mode 100644 Tests/Functional/app/FlattenExceptionNormalizerLegacyFormatDebug/config.yml create mode 100644 Tests/Functional/app/FlattenExceptionNormalizerRfc7807Format/bundles.php create mode 100644 Tests/Functional/app/FlattenExceptionNormalizerRfc7807Format/config.yml create mode 100644 Tests/Functional/app/FormErrorHandler/bundles.php create mode 100644 Tests/Functional/app/FormErrorHandler/config.yml create mode 100644 Tests/Functional/app/FormErrorNormalizer/bundles.php create mode 100644 Tests/Functional/app/FormErrorNormalizer/config.yml diff --git a/Controller/ExceptionController.php b/Controller/ExceptionController.php index cf70a9cf5..30e9d6bf8 100644 --- a/Controller/ExceptionController.php +++ b/Controller/ExceptionController.php @@ -11,6 +11,8 @@ namespace FOS\RestBundle\Controller; +@trigger_error(sprintf('The %s\ExceptionController class is deprecated since FOSRestBundle 2.8.', __NAMESPACE__), E_USER_DEPRECATED); + use FOS\RestBundle\Exception\FlattenException as FosFlattenException; use FOS\RestBundle\Util\ExceptionValueMap; use FOS\RestBundle\View\View; @@ -24,7 +26,7 @@ /** * Custom ExceptionController that uses the view layer and supports HTTP response status code mapping. * - * @final since 2.8 + * @deprecated since 2.8 */ class ExceptionController { diff --git a/DependencyInjection/Compiler/JMSHandlersPass.php b/DependencyInjection/Compiler/JMSHandlersPass.php index 06b681ff3..dc948a948 100644 --- a/DependencyInjection/Compiler/JMSHandlersPass.php +++ b/DependencyInjection/Compiler/JMSHandlersPass.php @@ -33,8 +33,11 @@ public function process(ContainerBuilder $container) return; } + if ($container->hasDefinition('fos_rest.serializer.exception_normalizer.jms')) { + $container->removeDefinition('fos_rest.serializer.exception_normalizer.jms'); + } + $container->removeDefinition('fos_rest.serializer.handler_registry'); - $container->removeDefinition('fos_rest.serializer.exception_normalizer.jms'); $container->getParameterBag()->remove('jms_serializer.form_error_handler.class'); } } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index bc3575106..b60aeae60 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -473,22 +473,52 @@ private function addExceptionSection(ArrayNodeDefinition $rootNode) ->addDefaultsIfNotSet() ->canBeEnabled() ->children() + ->booleanNode('map_exception_codes') + ->defaultFalse() + ->info('Enables an event listener that maps exception codes to response status codes based on the map configured with the "fos_rest.exception.codes" option.') + ->end() + ->booleanNode('exception_listener') + ->defaultValue(function () { + @trigger_error('Enabling the "fos_rest.exception.exception_listener" option is deprecated since FOSRestBundle 2.8.', E_USER_DEPRECATED); + + return true; + }) + ->beforeNormalization() + ->ifTrue() + ->then(function ($v) { + @trigger_error('Enabling the "fos_rest.exception.exception_listener" option is deprecated since FOSRestBundle 2.8.', E_USER_DEPRECATED); + + return $v; + }) + ->end() + ->end() ->scalarNode('exception_controller') - ->defaultValue(static function () { - @trigger_error('Not setting the "fos_rest.exception.exception_controller" configuration option is deprecated since FOSRestBundle 2.8. Its default value will be set to "fos_rest.exception.controller::showAction" in 3.0.', E_USER_DEPRECATED); + ->defaultNull() + ->setDeprecated('The "%path%.%node%" option is deprecated since FOSRestBundle 2.8.') + ->end() + ->booleanNode('serialize_exceptions') + ->defaultValue(function () { + @trigger_error('Enabling the "fos_rest.exception.serialize_exceptions" option is deprecated since FOSRestBundle 2.8.', E_USER_DEPRECATED); - return null; + return true; }) - ->validate() - ->ifTrue(static function ($v) { return null === $v; }) - ->then(static function ($v) { - @trigger_error('Not setting the "fos_rest.exception.exception_controller" configuration option is deprecated since FOSRestBundle 2.8. Its default value will be set to "fos_rest.exception.controller::showAction" in 3.0.', E_USER_DEPRECATED); + ->beforeNormalization() + ->ifTrue() + ->then(function ($v) { + @trigger_error('Enabling the "fos_rest.exception.serialize_exceptions" option is deprecated since FOSRestBundle 2.8.', E_USER_DEPRECATED); return $v; }) ->end() ->end() - ->scalarNode('service')->defaultNull()->end() + ->enumNode('flatten_exception_format') + ->defaultValue('legacy') + ->values(['legacy', 'rfc7807']) + ->end() + ->scalarNode('service') + ->defaultNull() + ->setDeprecated('The "%path%.%node%" option is deprecated since FOSRestBundle 2.8.') + ->end() ->arrayNode('codes') ->useAttributeAsKey('name') ->beforeNormalization() diff --git a/DependencyInjection/FOSRestExtension.php b/DependencyInjection/FOSRestExtension.php index 7522b36fd..043ff3012 100644 --- a/DependencyInjection/FOSRestExtension.php +++ b/DependencyInjection/FOSRestExtension.php @@ -11,6 +11,7 @@ namespace FOS\RestBundle\DependencyInjection; +use FOS\RestBundle\EventListener\ResponseStatusCodeListener; use FOS\RestBundle\Inflector\DoctrineInflector; use FOS\RestBundle\View\ViewHandler; use Symfony\Component\Config\FileLocator; @@ -388,28 +389,43 @@ private function loadException(array $config, XmlFileLoader $loader, ContainerBu if ($config['exception']['enabled']) { $loader->load('exception_listener.xml'); - if (!empty($config['exception']['service'])) { - $service = $container->getDefinition('fos_rest.exception_listener'); - $service->clearTag('kernel.event_subscriber'); + if ($config['exception']['map_exception_codes']) { + $container->register('fos_rest.exception.response_status_code_listener', ResponseStatusCodeListener::class) + ->setArguments([ + new Reference('fos_rest.exception.codes_map'), + ]) + ->addTag('kernel.event_subscriber'); } - $controller = $config['exception']['exception_controller'] ?? null; + if ($config['exception']['exception_listener']) { + if (!empty($config['exception']['service'])) { + $service = $container->getDefinition('fos_rest.exception_listener'); + $service->clearTag('kernel.event_subscriber'); + } - if (class_exists(ErrorListener::class)) { - $container->register('fos_rest.error_listener', ErrorListener::class) - ->setArguments([ - $controller, - new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE), - '%kernel.debug%', - ]) - ->addTag('monolog.logger', ['channel' => 'request']); + $controller = $config['exception']['exception_controller'] ?? null; + + if (class_exists(ErrorListener::class)) { + $container->register('fos_rest.error_listener', ErrorListener::class) + ->setArguments([ + $controller, + new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE), + '%kernel.debug%', + ]) + ->addTag('monolog.logger', ['channel' => 'request']); + } else { + $container->register('fos_rest.error_listener', LegacyHttpKernelExceptionListener::class) + ->setArguments([ + $controller, + new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE), + ]) + ->addTag('monolog.logger', ['channel' => 'request']); + } + + $container->getDefinition('fos_rest.exception.controller') + ->replaceArgument(2, $config['exception']['debug']); } else { - $container->register('fos_rest.error_listener', LegacyHttpKernelExceptionListener::class) - ->setArguments([ - $controller, - new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE), - ]) - ->addTag('monolog.logger', ['channel' => 'request']); + $container->removeDefinition('fos_rest.exception_listener'); } $container->getDefinition('fos_rest.exception.codes_map') @@ -417,12 +433,24 @@ private function loadException(array $config, XmlFileLoader $loader, ContainerBu $container->getDefinition('fos_rest.exception.messages_map') ->replaceArgument(0, $config['exception']['messages']); - $container->getDefinition('fos_rest.exception.controller') - ->replaceArgument(2, $config['exception']['debug']); - $container->getDefinition('fos_rest.serializer.exception_normalizer.jms') + $container->getDefinition('fos_rest.serializer.flatten_exception_handler') ->replaceArgument(1, $config['exception']['debug']); - $container->getDefinition('fos_rest.serializer.exception_normalizer.symfony') + $container->getDefinition('fos_rest.serializer.flatten_exception_handler') + ->replaceArgument(2, 'rfc7807' === $config['exception']['flatten_exception_format']); + $container->getDefinition('fos_rest.serializer.flatten_exception_normalizer') ->replaceArgument(1, $config['exception']['debug']); + $container->getDefinition('fos_rest.serializer.flatten_exception_normalizer') + ->replaceArgument(2, 'rfc7807' === $config['exception']['flatten_exception_format']); + + if ($config['exception']['serialize_exceptions']) { + $container->getDefinition('fos_rest.serializer.exception_normalizer.jms') + ->replaceArgument(1, $config['exception']['debug']); + $container->getDefinition('fos_rest.serializer.exception_normalizer.symfony') + ->replaceArgument(1, $config['exception']['debug']); + } else { + $container->removeDefinition('fos_rest.serializer.exception_normalizer.jms'); + $container->removeDefinition('fos_rest.serializer.exception_normalizer.symfony'); + } } } diff --git a/EventListener/ExceptionListener.php b/EventListener/ExceptionListener.php index 78f285349..efe330c20 100644 --- a/EventListener/ExceptionListener.php +++ b/EventListener/ExceptionListener.php @@ -11,6 +11,8 @@ namespace FOS\RestBundle\EventListener; +@trigger_error(sprintf('The %s\ExceptionListener class is deprecated since FOSRestBundle 2.8.', __NAMESPACE__), E_USER_DEPRECATED); + use FOS\RestBundle\FOSRestBundle; use Symfony\Component\Debug\Exception\FlattenException as LegacyFlattenException; use Symfony\Component\ErrorHandler\Exception\FlattenException; @@ -27,7 +29,7 @@ * * @author Ener-Getick * - * @internal + * @deprecated since 2.8 */ class ExceptionListener implements EventSubscriberInterface { diff --git a/EventListener/ResponseStatusCodeListener.php b/EventListener/ResponseStatusCodeListener.php new file mode 100644 index 000000000..02b29761d --- /dev/null +++ b/EventListener/ResponseStatusCodeListener.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FOS\RestBundle\EventListener; + +use FOS\RestBundle\FOSRestBundle; +use FOS\RestBundle\Util\ExceptionValueMap; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\ExceptionEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * @author Christian Flothmann + */ +class ResponseStatusCodeListener implements EventSubscriberInterface +{ + private $exceptionValueMap; + private $responseStatusCode; + + public function __construct(ExceptionValueMap $exceptionValueMap) + { + $this->exceptionValueMap = $exceptionValueMap; + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::EXCEPTION => 'getResponseStatusCodeFromThrowable', + KernelEvents::RESPONSE => 'setResponseStatusCode', + ]; + } + + /** + * @param ExceptionEvent $event + */ + public function getResponseStatusCodeFromThrowable($event): void + { + if (!$event->isMasterRequest()) { + return; + } + + $request = $event->getRequest(); + + if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) { + return; + } + + if (method_exists($event, 'getThrowable')) { + $throwable = $event->getThrowable(); + } else { + $throwable = $event->getException(); + } + + $statusCode = $this->exceptionValueMap->resolveThrowable($throwable); + + if (is_int($statusCode)) { + $this->responseStatusCode = $statusCode; + } + } + + /** + * @param ResponseEvent $event + */ + public function setResponseStatusCode($event): void + { + if (!$event->isMasterRequest()) { + return; + } + + if (null !== $this->responseStatusCode) { + $event->getResponse()->setStatusCode($this->responseStatusCode); + + $this->responseStatusCode = null; + } + } +} diff --git a/Resources/config/exception_listener.xml b/Resources/config/exception_listener.xml index 429f0cd88..2cb0c5eaa 100644 --- a/Resources/config/exception_listener.xml +++ b/Resources/config/exception_listener.xml @@ -9,12 +9,14 @@ + The "%service_id%" service is deprecated since FOSRestBundle 2.8. + The "%service_id%" service is deprecated since FOSRestBundle 2.8. @@ -34,12 +36,28 @@ + The "%service_id%" service is deprecated since FOSRestBundle 2.8. + The "%service_id%" service is deprecated since FOSRestBundle 2.8. + + + + + + + + + + + + + + diff --git a/Serializer/Normalizer/ExceptionHandler.php b/Serializer/Normalizer/ExceptionHandler.php index 73814b70e..fffedb557 100644 --- a/Serializer/Normalizer/ExceptionHandler.php +++ b/Serializer/Normalizer/ExceptionHandler.php @@ -11,6 +11,8 @@ namespace FOS\RestBundle\Serializer\Normalizer; +@trigger_error(sprintf('The %s\ExceptionHandler class is deprecated since FOSRestBundle 2.8.', __NAMESPACE__), E_USER_DEPRECATED); + use JMS\Serializer\Context; use JMS\Serializer\GraphNavigatorInterface; use JMS\Serializer\Handler\SubscribingHandlerInterface; @@ -18,7 +20,7 @@ use JMS\Serializer\XmlSerializationVisitor; /** - * @internal since 2.8 + * @deprecated since 2.8 */ class ExceptionHandler extends AbstractExceptionNormalizer implements SubscribingHandlerInterface { diff --git a/Serializer/Normalizer/ExceptionNormalizer.php b/Serializer/Normalizer/ExceptionNormalizer.php index b7dc5552a..4abaf12fd 100644 --- a/Serializer/Normalizer/ExceptionNormalizer.php +++ b/Serializer/Normalizer/ExceptionNormalizer.php @@ -11,6 +11,8 @@ namespace FOS\RestBundle\Serializer\Normalizer; +@trigger_error(sprintf('The %s\ExceptionNormalizer class is deprecated since FOSRestBundle 2.8.', __NAMESPACE__), E_USER_DEPRECATED); + use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** @@ -18,7 +20,7 @@ * * @author Ener-Getick * - * @internal since 2.8 + * @deprecated since 2.8 */ class ExceptionNormalizer extends AbstractExceptionNormalizer implements NormalizerInterface { diff --git a/Serializer/Normalizer/FlattenExceptionHandler.php b/Serializer/Normalizer/FlattenExceptionHandler.php new file mode 100644 index 000000000..2b4fc659b --- /dev/null +++ b/Serializer/Normalizer/FlattenExceptionHandler.php @@ -0,0 +1,109 @@ + + * + * @internal + */ +class FlattenExceptionHandler implements SubscribingHandlerInterface +{ + private $messagesMap; + private $debug; + private $rfc7807; + + public function __construct(ExceptionValueMap $messagesMap, bool $debug, bool $rfc7807) + { + $this->messagesMap = $messagesMap; + $this->debug = $debug; + $this->rfc7807 = $rfc7807; + } + + public static function getSubscribingMethods(): array + { + return [ + [ + 'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION, + 'format' => 'json', + 'type' => FlattenException::class, + 'method' => 'serializeToJson', + ], + [ + 'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION, + 'format' => 'xml', + 'type' => FlattenException::class, + 'method' => 'serializeToXml', + ], + ]; + } + + public function serializeToJson(JsonSerializationVisitor $visitor, FlattenException $exception, array $type, Context $context) + { + return $visitor->visitArray($this->convertToArray($exception, $context), $type); + } + + public function serializeToXml(XmlSerializationVisitor $visitor, FlattenException $exception, array $type, Context $context) + { + $data = $this->convertToArray($exception, $context); + + $document = $visitor->getDocument(); + + if (!$visitor->getCurrentNode()) { + $visitor->createRoot(null, $this->rfc7807 ? 'response' : 'result'); + } + + foreach ($data as $key => $value) { + $entryNode = $document->createElement($key); + $visitor->getCurrentNode()->appendChild($entryNode); + $visitor->setCurrentNode($entryNode); + + $node = $context->getNavigator()->accept($value); + if (null !== $node) { + $visitor->getCurrentNode()->appendChild($node); + } + + $visitor->revertCurrentNode(); + } + } + + private function convertToArray(FlattenException $exception, Context $context): array + { + if ($context->hasAttribute('status_code')) { + $statusCode = $context->getAttribute('status_code'); + } else { + $statusCode = $exception->getStatusCode(); + } + + $showMessage = $this->messagesMap->resolveFromClassName($exception->getClass()); + + if ($showMessage || $this->debug) { + $message = $exception->getMessage(); + } else { + $message = Response::$statusTexts[$statusCode] ?? 'error'; + } + + if ($this->rfc7807) { + return [ + 'type' => $context->hasAttribute('type') ? $context->getAttribute('type') : 'https://tools.ietf.org/html/rfc2616#section-10', + 'title' => $context->hasAttribute('title') ? $context->getAttribute('title') : 'An error occurred', + 'status' => $statusCode, + 'detail' => $message, + ]; + } else { + return [ + 'code' => $statusCode, + 'message' => $message, + ]; + } + } +} diff --git a/Serializer/Normalizer/FlattenExceptionNormalizer.php b/Serializer/Normalizer/FlattenExceptionNormalizer.php new file mode 100644 index 000000000..d3433ed1f --- /dev/null +++ b/Serializer/Normalizer/FlattenExceptionNormalizer.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FOS\RestBundle\Serializer\Normalizer; + +use FOS\RestBundle\Util\ExceptionValueMap; +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; + +/** + * @author Christian Flothmann + * + * @internal + */ +final class FlattenExceptionNormalizer implements CacheableSupportsMethodInterface, NormalizerInterface +{ + private $messagesMap; + private $debug; + private $rfc7807; + + public function __construct(ExceptionValueMap $messagesMap, bool $debug, bool $rfc7807) + { + $this->messagesMap = $messagesMap; + $this->debug = $debug; + $this->rfc7807 = $rfc7807; + } + + public function normalize($exception, $format = null, array $context = []) + { + if (isset($context['status_code'])) { + $statusCode = $context['status_code']; + } else { + $statusCode = $exception->getStatusCode(); + } + + $showMessage = $this->messagesMap->resolveFromClassName($exception->getClass()); + + if ($showMessage || $this->debug) { + $message = $exception->getMessage(); + } else { + $message = Response::$statusTexts[$statusCode] ?? 'error'; + } + + if ($this->rfc7807) { + return [ + 'type' => $context['type'] ?? 'https://tools.ietf.org/html/rfc2616#section-10', + 'title' => $context['title'] ?? 'An error occurred', + 'status' => $statusCode, + 'detail' => $message, + ]; + } else { + return [ + 'code' => $statusCode, + 'message' => $message, + ]; + } + } + + public function supportsNormalization($data, $format = null) + { + return $data instanceof FlattenException; + } + + public function hasCacheableSupportsMethod(): bool + { + return true; + } +} diff --git a/Tests/Controller/ExceptionControllerTest.php b/Tests/Controller/ExceptionControllerTest.php index 4f557d191..785c87e64 100644 --- a/Tests/Controller/ExceptionControllerTest.php +++ b/Tests/Controller/ExceptionControllerTest.php @@ -19,6 +19,9 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +/** + * @group legacy + */ class ExceptionControllerTest extends TestCase { /** diff --git a/Tests/DependencyInjection/ConfigurationTest.php b/Tests/DependencyInjection/ConfigurationTest.php index 9bc2e1d05..d65e60b27 100644 --- a/Tests/DependencyInjection/ConfigurationTest.php +++ b/Tests/DependencyInjection/ConfigurationTest.php @@ -55,7 +55,8 @@ public function testExceptionCodesAcceptsIntegers() [ 'exception' => [ 'codes' => $expectedConfig, - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -86,7 +87,8 @@ public function testThatResponseConstantsConvertedToCodes() NotFoundHttpException::class => 'HTTP_NOT_FOUND', MethodNotAllowedException::class => 'HTTP_METHOD_NOT_ALLOWED', ], - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -123,7 +125,8 @@ public function testThatIfExceptionCodeIncorrectExceptionIsThrown($value) 'codes' => [ \RuntimeException::class => $value, ], - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -146,10 +149,11 @@ public function testLoadBadMessagesClassThrowsException() [ [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, 'messages' => [ 'UnknownException' => true, ], + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -176,7 +180,8 @@ public function testLoadBadCodesClassThrowsException() 'codes' => [ 'UnknownException' => 404, ], - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -197,7 +202,8 @@ public function testOverwriteFormatListenerRulesDoesNotMerge() [ [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'format_listener' => [ 'rules' => [ diff --git a/Tests/DependencyInjection/FOSRestExtensionTest.php b/Tests/DependencyInjection/FOSRestExtensionTest.php index 786e97e5c..950690f09 100644 --- a/Tests/DependencyInjection/FOSRestExtensionTest.php +++ b/Tests/DependencyInjection/FOSRestExtensionTest.php @@ -82,7 +82,8 @@ public function testDisableBodyListener() 'fos_rest' => [ 'body_listener' => false, 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -104,7 +105,8 @@ public function testLoadBodyListenerWithDefaults() $this->extension->load([ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -135,7 +137,8 @@ public function testLoadBodyListenerWithNormalizerString() 'array_normalizer' => 'fos_rest.normalizer.camel_keys', ], 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -165,7 +168,8 @@ public function testLoadBodyListenerWithNormalizerArray() ], ], 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -200,7 +204,8 @@ public function testLoadBodyListenerWithNormalizerArrayAndForms() ], ], 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -229,7 +234,8 @@ public function testDisableFormatListener() $config = [ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'format_listener' => false, 'routing_loader' => false, @@ -252,7 +258,8 @@ public function testLoadFormatListenerWithDefaults() $this->extension->load([ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -273,7 +280,8 @@ public function testLoadFormatListenerWithSingleRule() $config = [ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'format_listener' => [ 'rules' => ['path' => '/'], @@ -298,7 +306,8 @@ public function testLoadParamFetcherListener() $config = [ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'param_fetcher_listener' => true, 'routing_loader' => false, @@ -322,7 +331,8 @@ public function testLoadParamFetcherListenerForce() $config = [ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'param_fetcher_listener' => 'force', 'routing_loader' => false, @@ -346,7 +356,8 @@ public function testLoadFormatListenerWithMultipleRule() $config = [ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'format_listener' => [ 'rules' => [ @@ -376,7 +387,8 @@ public function testLoadFormatListenerMediaTypeNoRules() $config = [ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'format_listener' => [ 'media_type' => true, @@ -399,7 +411,8 @@ public function testLoadServicesWithDefaults() $this->extension->load([ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -428,7 +441,8 @@ public function testDisableViewResponseListener() $config = [ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -451,7 +465,8 @@ public function testLoadViewResponseListener() $config = [ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -475,7 +490,8 @@ public function testLoadViewResponseListenerForce() $config = [ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -499,7 +515,8 @@ public function testForceEmptyContentDefault() $this->extension->load([ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -519,7 +536,8 @@ public function testForceEmptyContentIs200() $config = [ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -541,7 +559,8 @@ public function testViewSerializeNullDefault() $this->extension->load([ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -561,7 +580,8 @@ public function testViewSerializeNullIsTrue() $config = [ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -584,7 +604,8 @@ public function testValidatorAliasWhenEnabled() 'fos_rest' => [ 'body_converter' => ['validate' => true], 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -606,7 +627,8 @@ public function testValidatorAliasWhenDisabled() 'fos_rest' => [ 'body_converter' => ['validate' => false], 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -668,7 +690,8 @@ public function testIncludeFormatDisabled() [ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => [ 'include_format' => false, @@ -711,7 +734,8 @@ public function testDefaultFormat() [ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => [ 'default_format' => 'xml', @@ -754,7 +778,8 @@ public function testFormats() [ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'service' => [ 'templating' => null, @@ -803,7 +828,8 @@ public function testLoadOkMessagesClass() 'codes' => [ 'Exception' => 404, ], - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -832,7 +858,8 @@ public function testLoadBadCodeValueThrowsException($value) 'codes' => [ 'Exception' => $value, ], - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -861,6 +888,8 @@ public function getLoadBadCodeValueThrowsExceptionData() } /** + * @group legacy + * * Test exception.debug config value uses kernel.debug value by default or provided value. * * @dataProvider getShowExceptionData @@ -905,6 +934,7 @@ public static function getShowExceptionData() true, [ 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'serialize_exceptions' => true, ], true, ), @@ -912,6 +942,7 @@ public static function getShowExceptionData() false, [ 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'serialize_exceptions' => true, ], false, ), @@ -920,6 +951,7 @@ public static function getShowExceptionData() [ 'debug' => true, 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'serialize_exceptions' => true, ], true, ), @@ -928,6 +960,7 @@ public static function getShowExceptionData() [ 'debug' => false, 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'serialize_exceptions' => true, ], false, ), @@ -936,6 +969,7 @@ public static function getShowExceptionData() [ 'debug' => null, 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'serialize_exceptions' => true, ], true, ), @@ -944,12 +978,108 @@ public static function getShowExceptionData() [ 'debug' => null, 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'serialize_exceptions' => true, ], true, ), ); } + public function testResponseStatusCodeListenerEnabled() + { + $extension = new FOSRestExtension(); + $extension->load([ + [ + 'exception' => [ + 'exception_listener' => false, + 'serialize_exceptions' => false, + 'map_exception_codes' => true, + ], + 'service' => [ + 'templating' => null, + ], + 'view' => [ + 'default_engine' => null, + 'force_redirects' => [], + ], + ], + ], $this->container); + + $this->assertTrue($this->container->hasDefinition('fos_rest.exception.response_status_code_listener')); + } + + public function testExceptionListenerDisabled() + { + $extension = new FOSRestExtension(); + $extension->load([ + [ + 'exception' => [ + 'exception_listener' => false, + 'serialize_exceptions' => false, + ], + 'service' => [ + 'templating' => null, + ], + 'view' => [ + 'default_engine' => null, + 'force_redirects' => [], + ], + ], + ], $this->container); + + $this->assertFalse($this->container->hasDefinition('fos_rest.exception_listener')); + $this->assertFalse($this->container->hasDefinition('fos_rest.fos_rest.error_listener')); + } + + /** + * @group legacy + */ + public function testExceptionSerializerEnabled() + { + $extension = new FOSRestExtension(); + $extension->load([ + [ + 'exception' => [ + 'exception_listener' => false, + 'serialize_exceptions' => true, + ], + 'service' => [ + 'templating' => null, + ], + 'view' => [ + 'default_engine' => null, + 'force_redirects' => [], + ], + ], + ], $this->container); + + $this->assertTrue($this->container->hasDefinition('fos_rest.serializer.exception_normalizer.jms')); + $this->assertTrue($this->container->hasDefinition('fos_rest.serializer.exception_normalizer.symfony')); + } + + public function testExceptionSerializerDisabled() + { + $extension = new FOSRestExtension(); + $extension->load([ + [ + 'exception' => [ + 'exception_listener' => false, + 'serialize_exceptions' => false, + ], + 'service' => [ + 'templating' => null, + ], + 'view' => [ + 'default_engine' => null, + 'force_redirects' => [], + ], + ], + ], $this->container); + + $this->assertFalse($this->container->hasDefinition('fos_rest.serializer.exception_normalizer.jms')); + $this->assertFalse($this->container->hasDefinition('fos_rest.serializer.exception_normalizer.symfony')); + } + public function testGetConfiguration() { $configuration = $this->extension->getConfiguration(array(), $this->container); @@ -994,7 +1124,8 @@ public function testCheckViewHandlerWithJsonp() $this->extension->load([ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -1016,12 +1147,16 @@ public function testCheckViewHandlerWithJsonp() $this->assertInstanceOf($childDefinitionClass, $viewHandler); } + /** + * @group legacy + */ public function testSerializerExceptionNormalizer() { $this->extension->load([ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => true, ], 'routing_loader' => false, 'service' => [ @@ -1042,7 +1177,8 @@ public function testZoneMatcherListenerDefault() $this->extension->load([ 'fos_rest' => [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -1062,7 +1198,8 @@ public function testZoneMatcherListener() { $config = array('fos_rest' => array( 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ @@ -1110,7 +1247,8 @@ public function testMimeTypesArePassedArrays() $config = array( 'fos_rest' => array( 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ diff --git a/Tests/EventListener/ResponseStatusCodeListenerTest.php b/Tests/EventListener/ResponseStatusCodeListenerTest.php new file mode 100644 index 000000000..d7a208081 --- /dev/null +++ b/Tests/EventListener/ResponseStatusCodeListenerTest.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EventListener; + +use FOS\RestBundle\EventListener\ResponseStatusCodeListener; +use FOS\RestBundle\FOSRestBundle; +use FOS\RestBundle\Util\ExceptionValueMap; +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\ExceptionEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +class ResponseStatusCodeListenerTest extends TestCase +{ + private $eventListener; + + protected function setUp(): void + { + $this->eventListener = new ResponseStatusCodeListener(new ExceptionValueMap([ + \DomainException::class => 400, + NotFoundHttpException::class => 404, + \ParseError::class => 500, + ])); + } + + public function testResponseStatusCodeIsNotSetWhenRequestNotInRestZone() + { + $request = new Request(); + $request->attributes->set(FOSRestBundle::ZONE_ATTRIBUTE, false); + + if (class_exists(ExceptionEvent::class)) { + $exceptionEvent = new ExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, new \DomainException()); + } else { + $exceptionEvent = new GetResponseForExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, new \DomainException()); + } + + $this->eventListener->getResponseStatusCodeFromThrowable($exceptionEvent); + + $response = new Response(); + + if (class_exists(ResponseEvent::class)) { + $responseEvent = new ResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $response); + } else { + $responseEvent = new FilterResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $response); + } + + $this->eventListener->setResponseStatusCode($responseEvent); + + $this->assertSame(200, $response->getStatusCode()); + } + + public function testResponseStatusCodeIsNotSetWhenExceptionIsNotMapped() + { + $request = new Request(); + + if (class_exists(ExceptionEvent::class)) { + $exceptionEvent = new ExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, new \LogicException()); + } else { + $exceptionEvent = new GetResponseForExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, new \LogicException()); + } + + $this->eventListener->getResponseStatusCodeFromThrowable($exceptionEvent); + + $response = new Response(); + + if (class_exists(ResponseEvent::class)) { + $responseEvent = new ResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $response); + } else { + $responseEvent = new FilterResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $response); + } + + $this->eventListener->setResponseStatusCode($responseEvent); + + $this->assertSame(200, $response->getStatusCode()); + } + + public function testResponseStatusCodeIsSetWhenExceptionTypeIsConfigured() + { + $request = new Request(); + $exception = new \DomainException(); + + if (class_exists(ExceptionEvent::class)) { + $exceptionEvent = new ExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $exception); + } else { + $exceptionEvent = new GetResponseForExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $exception); + } + + $this->eventListener->getResponseStatusCodeFromThrowable($exceptionEvent); + + $response = new Response(); + + if (class_exists(ResponseEvent::class)) { + $responseEvent = new ResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $response); + } else { + $responseEvent = new FilterResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $response); + } + + $this->eventListener->setResponseStatusCode($responseEvent); + + $this->assertSame(400, $response->getStatusCode()); + } + + public function testResponseStatusCodeIsSetWhenErrorTypeIsConfigured() + { + if (!method_exists(ExceptionEvent::class, 'getThrowable')) { + $this->markTestSkipped(); + } + + $request = new Request(); + + $this->eventListener->getResponseStatusCodeFromThrowable(new ExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, new \ParseError())); + + $response = new Response(); + + if (class_exists(ResponseEvent::class)) { + $responseEvent = new ResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $response); + } else { + $responseEvent = new FilterResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $response); + } + + $this->eventListener->setResponseStatusCode($responseEvent); + + $this->assertSame(500, $response->getStatusCode()); + } + + public function testResponseStatusCodeIsNotOverriddenInSubRequests() + { + $masterRequest = new Request(); + $exception = new NotFoundHttpException(); + + if (class_exists(ExceptionEvent::class)) { + $masterRequestExceptionEvent = new ExceptionEvent($this->createMock(HttpKernelInterface::class), $masterRequest, HttpKernelInterface::MASTER_REQUEST, $exception); + } else { + $masterRequestExceptionEvent = new GetResponseForExceptionEvent($this->createMock(HttpKernelInterface::class), $masterRequest, HttpKernelInterface::MASTER_REQUEST, $exception); + } + + $this->eventListener->getResponseStatusCodeFromThrowable($masterRequestExceptionEvent); + + $subRequest = new Request(); + $exception = new \DomainException(); + + if (class_exists(ExceptionEvent::class)) { + $subRequestExceptionEvent = new ExceptionEvent($this->createMock(HttpKernelInterface::class), $subRequest, HttpKernelInterface::SUB_REQUEST, $exception); + } else { + $subRequestExceptionEvent = new GetResponseForExceptionEvent($this->createMock(HttpKernelInterface::class), $subRequest, HttpKernelInterface::SUB_REQUEST, $exception); + } + + $this->eventListener->getResponseStatusCodeFromThrowable($subRequestExceptionEvent); + + $subRequestResponse = new Response(); + + if (class_exists(ResponseEvent::class)) { + $subRequestResponseEvent = new ResponseEvent($this->createMock(HttpKernelInterface::class), $subRequest, HttpKernelInterface::SUB_REQUEST, $subRequestResponse); + } else { + $subRequestResponseEvent = new FilterResponseEvent($this->createMock(HttpKernelInterface::class), $subRequest, HttpKernelInterface::SUB_REQUEST, $subRequestResponse); + } + + $this->eventListener->setResponseStatusCode($subRequestResponseEvent); + + $masterRequestResponse = new Response(); + + if (class_exists(ResponseEvent::class)) { + $masterRequestResponseEvent = new ResponseEvent($this->createMock(HttpKernelInterface::class), $masterRequest, HttpKernelInterface::MASTER_REQUEST, $masterRequestResponse); + } else { + $masterRequestResponseEvent = new FilterResponseEvent($this->createMock(HttpKernelInterface::class), $masterRequest, HttpKernelInterface::MASTER_REQUEST, $masterRequestResponse); + } + + $this->eventListener->setResponseStatusCode($masterRequestResponseEvent); + + $this->assertSame(404, $masterRequestResponseEvent->getResponse()->getStatusCode()); + } +} diff --git a/Tests/Functional/Bundle/TestBundle/ErrorRenderer/JmsSerializerErrorRenderer.php b/Tests/Functional/Bundle/TestBundle/ErrorRenderer/JmsSerializerErrorRenderer.php new file mode 100644 index 000000000..9e31fa7ea --- /dev/null +++ b/Tests/Functional/Bundle/TestBundle/ErrorRenderer/JmsSerializerErrorRenderer.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FOS\RestBundle\Tests\Functional\Bundle\TestBundle\ErrorRenderer; + +use JMS\Serializer\SerializerInterface; +use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface; +use Symfony\Component\ErrorHandler\ErrorRenderer\SerializerErrorRenderer; +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\HttpFoundation\RequestStack; + +class JmsSerializerErrorRenderer implements ErrorRendererInterface +{ + private $serializer; + private $requestStack; + + public function __construct(SerializerInterface $serializer, RequestStack $requestStack) + { + $this->serializer = $serializer; + $this->requestStack = $requestStack; + } + + public function render(\Throwable $exception): FlattenException + { + $flattenException = FlattenException::createFromThrowable($exception); + + return $flattenException->setAsString($this->serializer->serialize($flattenException, SerializerErrorRenderer::getPreferredFormat($this->requestStack)($flattenException))); + } +} diff --git a/Tests/Functional/DependencyInjectionTest.php b/Tests/Functional/DependencyInjectionTest.php index 3d5ce6136..9c1280867 100644 --- a/Tests/Functional/DependencyInjectionTest.php +++ b/Tests/Functional/DependencyInjectionTest.php @@ -67,7 +67,8 @@ public function registerContainerConfiguration(LoaderInterface $loader) ]); $container->loadFromExtension('fos_rest', [ 'exception' => [ - 'exception_controller' => 'fos_rest.exception.controller::showAction', + 'exception_listener' => false, + 'serialize_exceptions' => false, ], 'routing_loader' => false, 'service' => [ diff --git a/Tests/Functional/ParamFetcherTest.php b/Tests/Functional/ParamFetcherTest.php index 2b8b055e6..c2dd7eb57 100644 --- a/Tests/Functional/ParamFetcherTest.php +++ b/Tests/Functional/ParamFetcherTest.php @@ -212,12 +212,12 @@ public function testIncompatibleQueryParameter() try { $this->client->request('POST', '/params?foz=val1&baz=val2'); - // SF >= 3.0 + // SF >= 4.4 $this->assertEquals(400, $this->client->getResponse()->getStatusCode()); $this->assertContains('\\"baz\\" param is incompatible with foz param.', $this->client->getResponse()->getContent()); } catch (BadRequestHttpException $e) { - // SF 2.x - $this->assertEquals('\\"baz\\" param is incompatible with foz param.', $e->getMessage()); + // SF < 4.4 + $this->assertEquals('"baz" param is incompatible with foz param.', $e->getMessage()); } } diff --git a/Tests/Functional/SerializerErrorTest.php b/Tests/Functional/SerializerErrorTest.php index 55e0df7f3..42d502517 100644 --- a/Tests/Functional/SerializerErrorTest.php +++ b/Tests/Functional/SerializerErrorTest.php @@ -11,6 +11,8 @@ namespace FOS\RestBundle\Tests\Functional; +use Symfony\Component\ErrorHandler\ErrorRenderer\SerializerErrorRenderer; + /** * Test class for serialization errors and exceptions. * @@ -20,13 +22,22 @@ class SerializerErrorTest extends WebTestCase { public static function tearDownAfterClass() { + self::deleteTmpDir('FlattenExceptionHandlerLegacyFormat'); + self::deleteTmpDir('FlattenExceptionHandlerRfc7807Format'); + self::deleteTmpDir('FlattenExceptionNormalizerLegacyFormat'); + self::deleteTmpDir('FlattenExceptionNormalizerLegacyFormatDebug'); + self::deleteTmpDir('FlattenExceptionNormalizerRfc7807Format'); + self::deleteTmpDir('FormErrorHandler'); + self::deleteTmpDir('FormErrorNormalizer'); self::deleteTmpDir('Serializer'); self::deleteTmpDir('JMSSerializer'); parent::tearDownAfterClass(); } /** - * @dataProvider invalidFormJsonProvider + * @group legacy + * + * @dataProvider serializeExceptionJsonProvider */ public function testSerializeExceptionJson($testCase) { @@ -38,6 +49,60 @@ public function testSerializeExceptionJson($testCase) $this->assertEquals('{"code":500,"message":"Something bad happened."}', $client->getResponse()->getContent()); } + public function serializeExceptionJsonProvider() + { + return [ + ['Serializer'], + ['JMSSerializer'], + ]; + } + + /** + * @dataProvider serializeExceptionJsonUsingErrorRendererProvider + */ + public function testSerializeExceptionJsonUsingErrorRenderer(string $testCase, array $expectedJson) + { + if (!class_exists(SerializerErrorRenderer::class)) { + $this->markTestSkipped(); + } + + $this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul'); + + $client = $this->createClient(['test_case' => $testCase, 'debug' => false]); + $client->request('GET', '/serializer-error/exception.json'); + + $this->assertEquals(json_encode($expectedJson), $client->getResponse()->getContent()); + } + + public function serializeExceptionJsonUsingErrorRendererProvider(): array + { + return [ + ['FlattenExceptionNormalizerLegacyFormat', [ + 'code' => 500, + 'message' => 'Something bad happened.', + ]], + ['FlattenExceptionNormalizerRfc7807Format', [ + 'type' => 'https://tools.ietf.org/html/rfc2616#section-10', + 'title' => 'An error occurred', + 'status' => 500, + 'detail' => 'Something bad happened.', + ]], + ['FlattenExceptionHandlerLegacyFormat', [ + 'code' => 500, + 'message' => 'Something bad happened.', + ]], + ['FlattenExceptionHandlerRfc7807Format', [ + 'type' => 'https://tools.ietf.org/html/rfc2616#section-10', + 'title' => 'An error occurred', + 'status' => 500, + 'detail' => 'Something bad happened.', + ]], + ]; + } + + /** + * @group legacy + */ public function testSerializeExceptionJsonWithDebug() { $this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul'); @@ -48,6 +113,23 @@ public function testSerializeExceptionJsonWithDebug() $this->assertEquals('{"code":500,"message":"Unknown exception message."}', $client->getResponse()->getContent()); } + public function testSerializeUnknownExceptionJsonWithDebugUsingErrorRenderer() + { + if (!class_exists(SerializerErrorRenderer::class)) { + $this->markTestSkipped(); + } + + $this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul'); + + $client = $this->createClient(array('test_case' => 'FlattenExceptionNormalizerLegacyFormatDebug', 'debug' => false)); + $client->request('GET', '/serializer-error/unknown_exception.json'); + + $this->assertEquals('{"code":500,"message":"Unknown exception message."}', $client->getResponse()->getContent()); + } + + /** + * @group legacy + */ public function testSerializeExceptionJsonWithoutDebug() { $this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul'); @@ -58,7 +140,23 @@ public function testSerializeExceptionJsonWithoutDebug() $this->assertEquals('{"code":500,"message":"Internal Server Error"}', $client->getResponse()->getContent()); } + public function testSerializeUnknownExceptionJsonWithoutDebugUsingErrorRenderer() + { + if (!class_exists(SerializerErrorRenderer::class)) { + $this->markTestSkipped(); + } + + $this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul'); + + $client = $this->createClient(array('test_case' => 'FlattenExceptionNormalizerLegacyFormat', 'debug' => false)); + $client->request('GET', '/serializer-error/unknown_exception.json'); + + $this->assertEquals('{"code":500,"message":"Internal Server Error"}', $client->getResponse()->getContent()); + } + /** + * @group legacy + * * @dataProvider serializeExceptionXmlProvider */ public function testSerializeExceptionXml($testCase, $expectedContent) @@ -94,6 +192,57 @@ public function serializeExceptionXmlProvider() ]; } + /** + * @dataProvider serializeExceptionXmlUsingErrorRendererProvider + */ + public function testSerializeExceptionXmlUsingErrorRenderer(string $testCase, string $expectedContent) + { + if (!class_exists(SerializerErrorRenderer::class)) { + $this->markTestSkipped(); + } + + $this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul'); + + $client = $this->createClient(['test_case' => $testCase, 'debug' => false]); + $client->request('GET', '/serializer-error/exception.xml'); + + $this->assertXmlStringEqualsXmlString($expectedContent, $client->getResponse()->getContent()); + } + + public function serializeExceptionXmlUsingErrorRendererProvider(): array + { + $expectedLegacyContent = <<<'XML' + +500Something bad happened. + +XML; + $expectedLegacyJmsContent = <<<'XML' + + + 500 + + + +XML; + $expectedRfc7807Content = <<<'XML' + + + https://tools.ietf.org/html/rfc2616#section-10 + An error occurred + 500 + Something bad happened. + + +XML; + + return [ + ['FlattenExceptionNormalizerLegacyFormat', $expectedLegacyContent], + ['FlattenExceptionNormalizerRfc7807Format', $expectedRfc7807Content], + ['FlattenExceptionHandlerLegacyFormat', $expectedLegacyJmsContent], + ['FlattenExceptionHandlerRfc7807Format', $expectedRfc7807Content], + ]; + } + /** * @dataProvider invalidFormJsonProvider */ @@ -108,8 +257,8 @@ public function testSerializeInvalidFormJson($testCase) public function invalidFormJsonProvider() { return [ - ['Serializer'], - ['JMSSerializer'], + ['FormErrorHandler'], + ['FormErrorNormalizer'], ]; } @@ -152,8 +301,8 @@ public function serializeInvalidFormXmlProvider() XML; return [ - ['Serializer', $expectedSerializerContent], - ['JMSSerializer', $expectedJMSContent], + ['FormErrorNormalizer', $expectedSerializerContent], + ['FormErrorHandler', $expectedJMSContent], ]; } } diff --git a/Tests/Functional/app/AllowedMethodsListener/config.yml b/Tests/Functional/app/AllowedMethodsListener/config.yml index 2cec3b550..531eeb31d 100644 --- a/Tests/Functional/app/AllowedMethodsListener/config.yml +++ b/Tests/Functional/app/AllowedMethodsListener/config.yml @@ -1,6 +1,5 @@ imports: - { resource: ../config/default.yml } - - { resource: ../config/default.php } - { resource: ../config/sensio_framework_extra.yml } framework: @@ -9,6 +8,9 @@ framework: fos_rest: allowed_methods_listener: true + exception: + exception_listener: false + serialize_exceptions: false routing_loader: false service: templating: ~ diff --git a/Tests/Functional/app/Configuration/config.yml b/Tests/Functional/app/Configuration/config.yml index 46738a8b7..e54847064 100644 --- a/Tests/Functional/app/Configuration/config.yml +++ b/Tests/Functional/app/Configuration/config.yml @@ -1,6 +1,5 @@ imports: - { resource: ../config/default.yml } - - { resource: ../config/default.php } - { resource: ../config/sensio_framework_extra.yml } - { resource: security.php } diff --git a/Tests/Functional/app/FlattenExceptionHandlerLegacyFormat/bundles.php b/Tests/Functional/app/FlattenExceptionHandlerLegacyFormat/bundles.php new file mode 100644 index 000000000..17a91ff56 --- /dev/null +++ b/Tests/Functional/app/FlattenExceptionHandlerLegacyFormat/bundles.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), + new \FOS\RestBundle\FOSRestBundle(), + new \JMS\SerializerBundle\JMSSerializerBundle(), + new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(), +]; diff --git a/Tests/Functional/app/FlattenExceptionHandlerLegacyFormat/config.yml b/Tests/Functional/app/FlattenExceptionHandlerLegacyFormat/config.yml new file mode 100644 index 000000000..e81a9f478 --- /dev/null +++ b/Tests/Functional/app/FlattenExceptionHandlerLegacyFormat/config.yml @@ -0,0 +1,24 @@ +imports: + - { resource: ../config/default.yml } + - { resource: ../config/exception_listener.yml } + +services: + error_renderer.serializer: '@fos_rest.error_renderer.jms_serializer_error_renderer' + + fos_rest.error_renderer.jms_serializer_error_renderer: + class: 'FOS\RestBundle\Tests\Functional\Bundle\TestBundle\ErrorRenderer\JmsSerializerErrorRenderer' + arguments: + - '@jms_serializer.serializer' + - '@request_stack' + +fos_rest: + exception: + exception_listener: false + serialize_exceptions: false + flatten_exception_format: 'legacy' + routing_loader: false + service: + templating: ~ + view: + default_engine: ~ + force_redirects: [] diff --git a/Tests/Functional/app/FlattenExceptionHandlerRfc7807Format/bundles.php b/Tests/Functional/app/FlattenExceptionHandlerRfc7807Format/bundles.php new file mode 100644 index 000000000..17a91ff56 --- /dev/null +++ b/Tests/Functional/app/FlattenExceptionHandlerRfc7807Format/bundles.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), + new \FOS\RestBundle\FOSRestBundle(), + new \JMS\SerializerBundle\JMSSerializerBundle(), + new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(), +]; diff --git a/Tests/Functional/app/FlattenExceptionHandlerRfc7807Format/config.yml b/Tests/Functional/app/FlattenExceptionHandlerRfc7807Format/config.yml new file mode 100644 index 000000000..1e01e6f24 --- /dev/null +++ b/Tests/Functional/app/FlattenExceptionHandlerRfc7807Format/config.yml @@ -0,0 +1,24 @@ +imports: + - { resource: ../config/default.yml } + - { resource: ../config/exception_listener.yml } + +services: + error_renderer.serializer: '@fos_rest.error_renderer.jms_serializer_error_renderer' + + fos_rest.error_renderer.jms_serializer_error_renderer: + class: 'FOS\RestBundle\Tests\Functional\Bundle\TestBundle\ErrorRenderer\JmsSerializerErrorRenderer' + arguments: + - '@jms_serializer.serializer' + - '@request_stack' + +fos_rest: + exception: + exception_listener: false + serialize_exceptions: false + flatten_exception_format: 'rfc7807' + routing_loader: false + service: + templating: ~ + view: + default_engine: ~ + force_redirects: [] diff --git a/Tests/Functional/app/FlattenExceptionNormalizerLegacyFormat/bundles.php b/Tests/Functional/app/FlattenExceptionNormalizerLegacyFormat/bundles.php new file mode 100644 index 000000000..0d1632c6d --- /dev/null +++ b/Tests/Functional/app/FlattenExceptionNormalizerLegacyFormat/bundles.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), + new \FOS\RestBundle\FOSRestBundle(), + new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(), +]; diff --git a/Tests/Functional/app/FlattenExceptionNormalizerLegacyFormat/config.yml b/Tests/Functional/app/FlattenExceptionNormalizerLegacyFormat/config.yml new file mode 100644 index 000000000..cfe1b86cc --- /dev/null +++ b/Tests/Functional/app/FlattenExceptionNormalizerLegacyFormat/config.yml @@ -0,0 +1,19 @@ +imports: + - { resource: ../config/default.yml } + - { resource: ../config/exception_listener.yml } + +framework: + serializer: + enabled: true + +fos_rest: + exception: + exception_listener: false + serialize_exceptions: false + flatten_exception_format: 'legacy' + routing_loader: false + service: + templating: ~ + view: + default_engine: ~ + force_redirects: [] diff --git a/Tests/Functional/app/FlattenExceptionNormalizerLegacyFormatDebug/bundles.php b/Tests/Functional/app/FlattenExceptionNormalizerLegacyFormatDebug/bundles.php new file mode 100644 index 000000000..0d1632c6d --- /dev/null +++ b/Tests/Functional/app/FlattenExceptionNormalizerLegacyFormatDebug/bundles.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), + new \FOS\RestBundle\FOSRestBundle(), + new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(), +]; diff --git a/Tests/Functional/app/FlattenExceptionNormalizerLegacyFormatDebug/config.yml b/Tests/Functional/app/FlattenExceptionNormalizerLegacyFormatDebug/config.yml new file mode 100644 index 000000000..2fb4c5370 --- /dev/null +++ b/Tests/Functional/app/FlattenExceptionNormalizerLegacyFormatDebug/config.yml @@ -0,0 +1,20 @@ +imports: + - { resource: ../config/default.yml } + - { resource: ../config/exception_listener.yml } + +framework: + serializer: + enabled: true + +fos_rest: + exception: + debug: true + exception_listener: false + serialize_exceptions: false + flatten_exception_format: 'legacy' + routing_loader: false + service: + templating: ~ + view: + default_engine: ~ + force_redirects: [] diff --git a/Tests/Functional/app/FlattenExceptionNormalizerRfc7807Format/bundles.php b/Tests/Functional/app/FlattenExceptionNormalizerRfc7807Format/bundles.php new file mode 100644 index 000000000..0d1632c6d --- /dev/null +++ b/Tests/Functional/app/FlattenExceptionNormalizerRfc7807Format/bundles.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), + new \FOS\RestBundle\FOSRestBundle(), + new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(), +]; diff --git a/Tests/Functional/app/FlattenExceptionNormalizerRfc7807Format/config.yml b/Tests/Functional/app/FlattenExceptionNormalizerRfc7807Format/config.yml new file mode 100644 index 000000000..ff312d92d --- /dev/null +++ b/Tests/Functional/app/FlattenExceptionNormalizerRfc7807Format/config.yml @@ -0,0 +1,19 @@ +imports: + - { resource: ../config/default.yml } + - { resource: ../config/exception_listener.yml } + +framework: + serializer: + enabled: true + +fos_rest: + exception: + exception_listener: false + serialize_exceptions: false + flatten_exception_format: 'rfc7807' + routing_loader: false + service: + templating: ~ + view: + default_engine: ~ + force_redirects: [] diff --git a/Tests/Functional/app/FormErrorHandler/bundles.php b/Tests/Functional/app/FormErrorHandler/bundles.php new file mode 100644 index 000000000..17a91ff56 --- /dev/null +++ b/Tests/Functional/app/FormErrorHandler/bundles.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), + new \FOS\RestBundle\FOSRestBundle(), + new \JMS\SerializerBundle\JMSSerializerBundle(), + new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(), +]; diff --git a/Tests/Functional/app/FormErrorHandler/config.yml b/Tests/Functional/app/FormErrorHandler/config.yml new file mode 100644 index 000000000..78a7dd1e3 --- /dev/null +++ b/Tests/Functional/app/FormErrorHandler/config.yml @@ -0,0 +1,15 @@ +imports: + - { resource: ../config/default.yml } + - { resource: ../config/exception_listener.yml } + +fos_rest: + exception: + exception_listener: false + serialize_exceptions: false + routing_loader: false + service: + templating: ~ + view: + default_engine: ~ + force_redirects: [] + view_response_listener: 'force' diff --git a/Tests/Functional/app/FormErrorNormalizer/bundles.php b/Tests/Functional/app/FormErrorNormalizer/bundles.php new file mode 100644 index 000000000..0d1632c6d --- /dev/null +++ b/Tests/Functional/app/FormErrorNormalizer/bundles.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), + new \FOS\RestBundle\FOSRestBundle(), + new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(), +]; diff --git a/Tests/Functional/app/FormErrorNormalizer/config.yml b/Tests/Functional/app/FormErrorNormalizer/config.yml new file mode 100644 index 000000000..7f05bc39b --- /dev/null +++ b/Tests/Functional/app/FormErrorNormalizer/config.yml @@ -0,0 +1,19 @@ +imports: + - { resource: ../config/default.yml } + - { resource: ../config/exception_listener.yml } + +framework: + serializer: + enabled: true + +fos_rest: + exception: + exception_listener: false + serialize_exceptions: false + routing_loader: false + service: + templating: ~ + view: + default_engine: ~ + force_redirects: [] + view_response_listener: 'force' diff --git a/Tests/Functional/app/ParamFetcher/config.yml b/Tests/Functional/app/ParamFetcher/config.yml index df625c4b0..14d8411a7 100644 --- a/Tests/Functional/app/ParamFetcher/config.yml +++ b/Tests/Functional/app/ParamFetcher/config.yml @@ -1,6 +1,5 @@ imports: - { resource: ../config/default.yml } - - { resource: ../config/default.php } - { resource: ../config/sensio_framework_extra.yml } parameters: @@ -13,6 +12,11 @@ framework: enabled: true fos_rest: + exception: + exception_listener: false + messages: + Symfony\Component\HttpKernel\Exception\BadRequestHttpException: true + serialize_exceptions: false param_fetcher_listener: true routing_loader: false service: diff --git a/Tests/Functional/app/RequestBodyParamConverter/config.yml b/Tests/Functional/app/RequestBodyParamConverter/config.yml index 8d8bae341..684408b74 100644 --- a/Tests/Functional/app/RequestBodyParamConverter/config.yml +++ b/Tests/Functional/app/RequestBodyParamConverter/config.yml @@ -1,6 +1,5 @@ imports: - { resource: ../config/default.yml } - - { resource: ../config/default.php } - { resource: ../config/sensio_framework_extra.yml } framework: @@ -12,6 +11,9 @@ framework: fos_rest: body_converter: enabled: true + exception: + exception_listener: false + serialize_exceptions: false routing_loader: false service: templating: ~ diff --git a/Tests/Functional/app/RequestBodyParamConverterFrameworkBundle/config.yml b/Tests/Functional/app/RequestBodyParamConverterFrameworkBundle/config.yml index 0b086d4bd..9d3a285d0 100644 --- a/Tests/Functional/app/RequestBodyParamConverterFrameworkBundle/config.yml +++ b/Tests/Functional/app/RequestBodyParamConverterFrameworkBundle/config.yml @@ -1,6 +1,5 @@ imports: - { resource: ../config/default.yml } - - { resource: ../config/default.php } - { resource: ../config/sensio_framework_extra.yml } framework: @@ -12,6 +11,9 @@ framework: fos_rest: body_converter: enabled: true + exception: + exception_listener: false + serialize_exceptions: false routing_loader: false service: templating: ~ diff --git a/Tests/Functional/app/RequestBodyParamConverterTwigBundle/config.yml b/Tests/Functional/app/RequestBodyParamConverterTwigBundle/config.yml index b47be5fc1..d6f73261b 100644 --- a/Tests/Functional/app/RequestBodyParamConverterTwigBundle/config.yml +++ b/Tests/Functional/app/RequestBodyParamConverterTwigBundle/config.yml @@ -11,6 +11,8 @@ framework: fos_rest: body_converter: enabled: true + exception: + exception_listener: false routing_loader: false sensio_framework_extra: diff --git a/Tests/Functional/app/Version/config.yml b/Tests/Functional/app/Version/config.yml index e423ba4f4..4bc8e2271 100644 --- a/Tests/Functional/app/Version/config.yml +++ b/Tests/Functional/app/Version/config.yml @@ -1,6 +1,5 @@ imports: - { resource: ../config/default.yml } - - { resource: ../config/default.php } - { resource: ../config/sensio_framework_extra.yml } framework: @@ -8,6 +7,9 @@ framework: enabled: true fos_rest: + exception: + exception_listener: false + serialize_exceptions: false format_listener: rules: - { path: '^/', priorities: ['json', 'html'], fallback_format: json } diff --git a/Tests/Functional/app/ViewResponseListener/config.yml b/Tests/Functional/app/ViewResponseListener/config.yml index 79803c958..3054d0f60 100644 --- a/Tests/Functional/app/ViewResponseListener/config.yml +++ b/Tests/Functional/app/ViewResponseListener/config.yml @@ -1,6 +1,5 @@ imports: - { resource: ../config/default.yml } - - { resource: ../config/default.php } - { resource: ../config/sensio_framework_extra.yml } framework: @@ -15,6 +14,9 @@ fos_rest: xml: true json: true force_redirects: [] + exception: + exception_listener: false + serialize_exceptions: false format_listener: rules: - { path: ^/, priorities: [ json, xml ], fallback_format: ~, prefer_extension: true } diff --git a/Tests/Functional/app/ViewResponseListenerForceRedirect/config.yml b/Tests/Functional/app/ViewResponseListenerForceRedirect/config.yml index aee01a794..76a74d44c 100644 --- a/Tests/Functional/app/ViewResponseListenerForceRedirect/config.yml +++ b/Tests/Functional/app/ViewResponseListenerForceRedirect/config.yml @@ -1,6 +1,5 @@ imports: - { resource: ../config/default.yml } - - { resource: ../config/default.php } - { resource: ../config/sensio_framework_extra.yml } framework: @@ -14,6 +13,8 @@ fos_rest: formats: xml: true json: true + exception: + exception_listener: false format_listener: rules: - { path: ^/, priorities: [ html, json, xml ], fallback_format: ~, prefer_extension: true } diff --git a/UPGRADING-2.8.md b/UPGRADING-2.8.md index 84f113289..9a7075d41 100644 --- a/UPGRADING-2.8.md +++ b/UPGRADING-2.8.md @@ -8,6 +8,22 @@ Upgrading From 2.7 To 2.8 routing_loader: false ``` + * The `fos_rest.exception.exception_controller`, `fos_rest.exception.exception_listener`, and + `fos_rest.exception.service` options are deprecated. + + * Support for serializing exceptions has been deprecated. Disable it by setting the + `fos_rest.exception.serialize_exceptions` option to `false` and use the ErrorRenderer component + instead. + + You can use the `flatten_exception_format` option to serialize exceptions according to the API + Problem spec (RFC 7807): + + ```yaml + fos_rest: + exception: + flatten_exception_format: 'rfc7807' + ``` + * Deprecated returning anything other than `string` or `null` from `resolve()` when implementing the `VersionResolverInterface`. @@ -18,6 +34,8 @@ Upgrading From 2.7 To 2.8 * `FOS\RestBundle\Controller\Annotations\RouteResource` * `FOS\RestBundle\Controller\Annotations\Version` + * `FOS\RestBundle\Controller\ExceptionController` + * `FOS\RestBundle\EventListener\ExceptionListener` * `FOS\RestBundle\Routing\Loader\DirectoryRouteLoader` * `FOS\RestBundle\Routing\Loader\Reader\RestActionReader` * `FOS\RestBundle\Routing\Loader\Reader\RestControllerReader` @@ -27,6 +45,8 @@ Upgrading From 2.7 To 2.8 * `FOS\RestBundle\Routing\Loader\RestYamlCollectionLoader` * `FOS\RestBundle\Routing\ClassResourceInterface` * `FOS\RestBundle\Routing\RestRouteCollection` + * `FOS\RestBundle\Serializer\Normalizer\ExceptionHandler` + * `FOS\RestBundle\Serializer\Normalizer\ExceptionNormalizer` * The following classes are marked as `internal`: @@ -36,15 +56,12 @@ Upgrading From 2.7 To 2.8 * `FOS\RestBundle\Form\Transformer\EntityToIdObjectTransformer` * `FOS\RestBundle\Normalizer\CamelKeysNormalizer` * `FOS\RestBundle\Normalizer\CamelKeysNormalizerWithLeadingUnderscore` - * `FOS\RestBundle\Serializer\Normalizer\ExceptionHandler` - * `FOS\RestBundle\Serializer\Normalizer\ExceptionNormalizer` * `FOS\RestBundle\Serializer\Normalizer\FormErrorHandler` * `FOS\RestBundle\Serializer\Normalizer\FormErrorNormalizer` * `FOS\RestBundle\Util\ExceptionValueMap` * The following classes are marked as `final`. Extending them will not be supported as of 3.0: - * `FOS\RestBundle\Controller\ExceptionController` * `FOS\RestBundle\Decoder\ContainerDecoderProvider` * `FOS\RestBundle\Decoder\JsonDecoder` * `FOS\RestBundle\Decoder\JsonToFormDecoder` @@ -90,9 +107,6 @@ Upgrading From 2.7 To 2.8 * Not setting the `fos_rest.view.force_redirects` option to the empty array has been deprecated. - * Not configuring the `fos_rest.exception.exception_controller` option is deprecated. Its default - value will be changed to `fos_rest.exception.controller::showAction` in 3.0. - * The `TemplatingExceptionController` and the `TwigExceptionController` classes have been deprecated. * The `fos_rest.exception.twig_controller` service has been deprecated. diff --git a/UPGRADING-3.0.md b/UPGRADING-3.0.md index 45764b6c6..9f2d7b273 100644 --- a/UPGRADING-3.0.md +++ b/UPGRADING-3.0.md @@ -4,6 +4,9 @@ Upgrading From 2.x To 3.0 * Enabling the `fos_rest.routing_loader` option is not supported anymore. Setting it to another value than `false` leads to an exception. + * The `fos_rest.exception.exception_controller`, `fos_rest.exception.exception_listener`, and + `fos_rest.exception.service` options have been removed. + * `Context::setVersion()` does not accept integers anymore. * The `ExceptionValueMap` class is `final`. Extending it is no longer supported. The `resolveThrowable()` @@ -32,9 +35,6 @@ Upgrading From 2.x To 3.0 * The default value of the `fos_rest.view.force_redirects` option has been changed to the empty array. Setting it to another value leads to an exception. - * The default value of the `fos_rest.exception.exception_controller` option has - been changed to `fos_rest.exception.controller::showAction`. - * The `TemplatingExceptionController` and the `TwigExceptionController` classes have been removed. diff --git a/Util/ExceptionValueMap.php b/Util/ExceptionValueMap.php index fedd523c7..0656e11ea 100644 --- a/Util/ExceptionValueMap.php +++ b/Util/ExceptionValueMap.php @@ -58,6 +58,14 @@ public function resolveThrowable(\Throwable $exception) return $this->doResolveClass(get_class($exception)); } + /** + * @internal + */ + public function resolveFromClassName(string $className) + { + return $this->doResolveClass($className); + } + /** * @return bool|int|false if not found */