vendor/api-platform/core/src/EventListener/AddFormatListener.php line 63

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\Core\EventListener;
  12. use ApiPlatform\Core\Api\FormatMatcher;
  13. use ApiPlatform\Core\Api\FormatsProviderInterface;
  14. use ApiPlatform\Core\Exception\InvalidArgumentException;
  15. use ApiPlatform\Core\Util\RequestAttributesExtractor;
  16. use Negotiation\Negotiator;
  17. use Symfony\Component\HttpFoundation\Request;
  18. use Symfony\Component\HttpKernel\Event\GetResponseEvent;
  19. use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
  20. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  21. /**
  22.  * Chooses the format to use according to the Accept header and supported formats.
  23.  *
  24.  * @author Kévin Dunglas <dunglas@gmail.com>
  25.  */
  26. final class AddFormatListener
  27. {
  28.     private $negotiator;
  29.     private $formats = [];
  30.     private $mimeTypes;
  31.     private $formatsProvider;
  32.     private $formatMatcher;
  33.     /**
  34.      * @throws InvalidArgumentException
  35.      */
  36.     public function __construct(Negotiator $negotiator/* FormatsProviderInterface */ $formatsProvider)
  37.     {
  38.         $this->negotiator $negotiator;
  39.         if (\is_array($formatsProvider)) {
  40.             @trigger_error('Using an array as formats provider is deprecated since API Platform 2.3 and will not be possible anymore in API Platform 3'E_USER_DEPRECATED);
  41.             $this->formats $formatsProvider;
  42.         } else {
  43.             if (!$formatsProvider instanceof FormatsProviderInterface) {
  44.                 throw new InvalidArgumentException(sprintf('The "$formatsProvider" argument is expected to be an implementation of the "%s" interface.'FormatsProviderInterface::class));
  45.             }
  46.             $this->formatsProvider $formatsProvider;
  47.         }
  48.     }
  49.     /**
  50.      * Sets the applicable format to the HttpFoundation Request.
  51.      *
  52.      * @throws NotFoundHttpException
  53.      * @throws NotAcceptableHttpException
  54.      */
  55.     public function onKernelRequest(GetResponseEvent $event): void
  56.     {
  57.         $request $event->getRequest();
  58.         if (!($request->attributes->has('_api_resource_class') || $request->attributes->getBoolean('_api_respond'false) || $request->attributes->getBoolean('_graphql'false))) {
  59.             return;
  60.         }
  61.         // BC check to be removed in 3.0
  62.         if (null !== $this->formatsProvider) {
  63.             $this->formats $this->formatsProvider->getFormatsFromAttributes(RequestAttributesExtractor::extractAttributes($request));
  64.         }
  65.         $this->formatMatcher = new FormatMatcher($this->formats);
  66.         $this->populateMimeTypes();
  67.         $this->addRequestFormats($request$this->formats);
  68.         // Empty strings must be converted to null because the Symfony router doesn't support parameter typing before 3.2 (_format)
  69.         if (null === $routeFormat $request->attributes->get('_format') ?: null) {
  70.             $mimeTypes array_keys($this->mimeTypes);
  71.         } elseif (!isset($this->formats[$routeFormat])) {
  72.             throw new NotFoundHttpException(sprintf('Format "%s" is not supported'$routeFormat));
  73.         } else {
  74.             $mimeTypes Request::getMimeTypes($routeFormat);
  75.         }
  76.         // First, try to guess the format from the Accept header
  77.         /** @var string|null $accept */
  78.         $accept $request->headers->get('Accept');
  79.         if (null !== $accept) {
  80.             if (null === $mediaType $this->negotiator->getBest($accept$mimeTypes)) {
  81.                 throw $this->getNotAcceptableHttpException($accept$mimeTypes);
  82.             }
  83.             $request->setRequestFormat($this->formatMatcher->getFormat($mediaType->getType()));
  84.             return;
  85.         }
  86.         // Then use the Symfony request format if available and applicable
  87.         $requestFormat $request->getRequestFormat('') ?: null;
  88.         if (null !== $requestFormat) {
  89.             $mimeType $request->getMimeType($requestFormat);
  90.             if (isset($this->mimeTypes[$mimeType])) {
  91.                 return;
  92.             }
  93.             throw $this->getNotAcceptableHttpException($mimeType);
  94.         }
  95.         // Finally, if no Accept header nor Symfony request format is set, return the default format
  96.         foreach ($this->formats as $format => $mimeType) {
  97.             $request->setRequestFormat($format);
  98.             return;
  99.         }
  100.     }
  101.     /**
  102.      * Adds the supported formats to the request.
  103.      *
  104.      * This is necessary for {@see Request::getMimeType} and {@see Request::getMimeTypes} to work.
  105.      */
  106.     private function addRequestFormats(Request $request, array $formats): void
  107.     {
  108.         foreach ($formats as $format => $mimeTypes) {
  109.             $request->setFormat($format, (array) $mimeTypes);
  110.         }
  111.     }
  112.     /**
  113.      * Populates the $mimeTypes property.
  114.      */
  115.     private function populateMimeTypes(): void
  116.     {
  117.         if (null !== $this->mimeTypes) {
  118.             return;
  119.         }
  120.         $this->mimeTypes = [];
  121.         foreach ($this->formats as $format => $mimeTypes) {
  122.             foreach ($mimeTypes as $mimeType) {
  123.                 $this->mimeTypes[$mimeType] = $format;
  124.             }
  125.         }
  126.     }
  127.     /**
  128.      * Retrieves an instance of NotAcceptableHttpException.
  129.      *
  130.      * @param string[]|null $mimeTypes
  131.      */
  132.     private function getNotAcceptableHttpException(string $accept, array $mimeTypes null): NotAcceptableHttpException
  133.     {
  134.         if (null === $mimeTypes) {
  135.             $mimeTypes array_keys($this->mimeTypes);
  136.         }
  137.         return new NotAcceptableHttpException(sprintf(
  138.             'Requested format "%s" is not supported. Supported MIME types are "%s".',
  139.             $accept,
  140.             implode('", "'$mimeTypes)
  141.         ));
  142.     }
  143. }