Skip to content

Instantly share code, notes, and snippets.

@soyuka
Last active November 14, 2024 14:01
Show Gist options
  • Save soyuka/beac00bc527d2ea6e703ed27dd67532c to your computer and use it in GitHub Desktop.
Save soyuka/beac00bc527d2ea6e703ed27dd67532c to your computer and use it in GitHub Desktop.
<?php
/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace ApiPlatform\State\Provider;
use ApiPlatform\Metadata\Error as ErrorOperation;
use ApiPlatform\Metadata\HttpOperation;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Util\ContentNegotiationTrait;
use ApiPlatform\State\ProviderInterface;
use Negotiation\Negotiator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
final class ContentNegotiationProvider implements ProviderInterface
{
use ContentNegotiationTrait;
/**
* @param array<string, string[]> $formats
* @param array<string, string[]> $errorFormats
*/
public function __construct(private readonly ?ProviderInterface $decorated = null, ?Negotiator $negotiator = null, private readonly array $formats = [], private readonly array $errorFormats = [])
{
$this->negotiator = $negotiator ?? new Negotiator();
}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
if (!($request = $context['request'] ?? null) || !$operation instanceof HttpOperation) {
return $this->decorated?->provide($operation, $uriVariables, $context);
}
$isErrorOperation = $operation instanceof ErrorOperation;
$formats = $operation->getOutputFormats() ?? ($isErrorOperation ? $this->errorFormats : $this->formats);
$this->addRequestFormats($request, $formats);
$request->attributes->set('input_format', $this->getInputFormat($operation, $request));
if (!$isErrorOperation) {
$request->setRequestFormat($this->getRequestFormat($request, $formats));
} else {
$request->setRequestFormat($this->getRequestFormat($request, $formats, false));
}
return $this->decorated?->provide($operation, $uriVariables, $context);
}
/**
* Adds the supported formats to the request.
*
* This is necessary for {@see Request::getMimeType} and {@see Request::getMimeTypes} to work.
* Note that this replaces default mime types configured at {@see Request::initializeFormats}
*
* @param array<string, string|string[]> $formats
*/
private function addRequestFormats(Request $request, array $formats): void
{
foreach ($formats as $format => $mimeTypes) {
$request->setFormat($format, (array) $mimeTypes);
}
}
/**
* Flattened the list of MIME types.
*
* @param array<string, string|string[]> $formats
*
* @return array<string, string>
*/
private function flattenMimeTypes(array $formats): array
{
$flattenedMimeTypes = [];
foreach ($formats as $format => $mimeTypes) {
foreach ($mimeTypes as $mimeType) {
$flattenedMimeTypes[$mimeType] = $format;
}
}
return $flattenedMimeTypes;
}
/**
* Extracts the format from the Content-Type header and check that it is supported.
*
* @throws UnsupportedMediaTypeHttpException
*/
private function getInputFormat(HttpOperation $operation, Request $request): ?string
{
$contentType = $request->headers->get('CONTENT_TYPE');
if (null === $contentType || '' === $contentType) {
return null;
}
/** @var string $contentType */
$formats = $operation->getInputFormats() ?? [];
if ($format = $this->getMimeTypeFormat($contentType, $formats)) {
return $format;
}
if (!$request->isMethodSafe() && 'DELETE' !== $request->getMethod()) {
throw new UnsupportedMediaTypeHttpException(\sprintf('The content-type "%s" is not supported. Supported MIME types are "%s".', $contentType, implode('", "', array_keys($this->flattenMimeTypes($formats)))));
}
return null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment