vendor/api-platform/core/src/Action/ExceptionAction.php line 50

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the API Platform project.
  4. *
  5. * (c) Kévin Dunglas <dunglas@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. declare(strict_types=1);
  11. namespace ApiPlatform\Action;
  12. use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
  13. use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
  14. use ApiPlatform\Util\ErrorFormatGuesser;
  15. use ApiPlatform\Util\OperationRequestInitiatorTrait;
  16. use ApiPlatform\Util\RequestAttributesExtractor;
  17. use Symfony\Component\Debug\Exception\FlattenException as LegacyFlattenException;
  18. use Symfony\Component\ErrorHandler\Exception\FlattenException;
  19. use Symfony\Component\HttpFoundation\Request;
  20. use Symfony\Component\HttpFoundation\Response;
  21. use Symfony\Component\Serializer\SerializerInterface;
  22. /**
  23. * Renders a normalized exception for a given {@see FlattenException} or {@see LegacyFlattenException}.
  24. *
  25. * @author Baptiste Meyer <baptiste.meyer@gmail.com>
  26. * @author Kévin Dunglas <dunglas@gmail.com>
  27. */
  28. final class ExceptionAction
  29. {
  30. use OperationRequestInitiatorTrait;
  31. private $serializer;
  32. private $errorFormats;
  33. private $exceptionToStatus;
  34. /**
  35. * @var ResourceMetadataCollectionFactoryInterface|ResourceMetadataFactoryInterface|null
  36. */
  37. private $resourceMetadataFactory;
  38. /**
  39. * @param array $errorFormats A list of enabled error formats
  40. * @param array $exceptionToStatus A list of exceptions mapped to their HTTP status code
  41. * @param mixed|null $resourceMetadataFactory
  42. */
  43. public function __construct(SerializerInterface $serializer, array $errorFormats, array $exceptionToStatus = [], $resourceMetadataFactory = null)
  44. {
  45. $this->serializer = $serializer;
  46. $this->errorFormats = $errorFormats;
  47. $this->exceptionToStatus = $exceptionToStatus;
  48. $this->resourceMetadataFactory = $resourceMetadataFactory;
  49. if (null !== $resourceMetadataFactory && !$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) {
  50. trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class));
  51. } else {
  52. $this->resourceMetadataCollectionFactory = $resourceMetadataFactory;
  53. }
  54. }
  55. /**
  56. * Converts an exception to a JSON response.
  57. *
  58. * @param FlattenException|LegacyFlattenException $exception
  59. */
  60. public function __invoke($exception, Request $request): Response
  61. {
  62. $operation = $this->initializeOperation($request);
  63. $exceptionClass = $exception->getClass();
  64. $statusCode = $exception->getStatusCode();
  65. $exceptionToStatus = array_merge(
  66. $this->exceptionToStatus,
  67. $operation ? $operation->getExceptionToStatus() ?? [] : $this->getOperationExceptionToStatus($request)
  68. );
  69. foreach ($exceptionToStatus as $class => $status) {
  70. if (is_a($exceptionClass, $class, true)) {
  71. $statusCode = $status;
  72. break;
  73. }
  74. }
  75. $headers = $exception->getHeaders();
  76. $format = ErrorFormatGuesser::guessErrorFormat($request, $this->errorFormats);
  77. $headers['Content-Type'] = sprintf('%s; charset=utf-8', $format['value'][0]);
  78. $headers['X-Content-Type-Options'] = 'nosniff';
  79. $headers['X-Frame-Options'] = 'deny';
  80. return new Response($this->serializer->serialize($exception, $format['key'], ['statusCode' => $statusCode]), $statusCode, $headers);
  81. }
  82. private function getOperationExceptionToStatus(Request $request): array
  83. {
  84. // TODO: remove legacy layer in 3.0
  85. if ($request->attributes->has('_api_exception_to_status')) {
  86. return $request->attributes->get('_api_exception_to_status');
  87. }
  88. $attributes = RequestAttributesExtractor::extractAttributes($request);
  89. if ([] === $attributes || null === $this->resourceMetadataFactory) {
  90. return [];
  91. }
  92. $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']);
  93. $operationExceptionToStatus = $resourceMetadata->getOperationAttribute($attributes, 'exception_to_status', [], false);
  94. $resourceExceptionToStatus = $resourceMetadata->getAttribute('exception_to_status', []);
  95. if (!\is_array($operationExceptionToStatus) || !\is_array($resourceExceptionToStatus)) {
  96. throw new \LogicException('"exception_to_status" attribute should be an array.');
  97. }
  98. return array_merge(
  99. $resourceExceptionToStatus,
  100. $operationExceptionToStatus
  101. );
  102. }
  103. }
  104. class_alias(ExceptionAction::class, \ApiPlatform\Core\Action\ExceptionAction::class);