Skip to content

Commit 6a30bd5

Browse files
authored
Merge pull request #2437 from teohhanhui/fix/content-negotiation-format
Fix content negotiation format matching
2 parents b14e1b2 + 95c25be commit 6a30bd5

File tree

4 files changed

+149
-66
lines changed

4 files changed

+149
-66
lines changed

src/Api/FormatMatcher.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\Api;
15+
16+
/**
17+
* Matches a mime type to a format.
18+
*
19+
* @internal
20+
*/
21+
final class FormatMatcher
22+
{
23+
private $formats;
24+
25+
public function __construct(array $formats)
26+
{
27+
$normalizedFormats = [];
28+
foreach ($formats as $format => $mimeTypes) {
29+
$normalizedFormats[$format] = (array) $mimeTypes;
30+
}
31+
$this->formats = $normalizedFormats;
32+
}
33+
34+
/**
35+
* Gets the format associated with the mime type.
36+
*
37+
* Adapted from {@see \Symfony\Component\HttpFoundation\Request::getFormat}.
38+
*/
39+
public function getFormat(string $mimeType): ?string
40+
{
41+
$canonicalMimeType = null;
42+
$pos = strpos($mimeType, ';');
43+
if (false !== $pos) {
44+
$canonicalMimeType = trim(substr($mimeType, 0, $pos));
45+
}
46+
47+
foreach ($this->formats as $format => $mimeTypes) {
48+
if (\in_array($mimeType, $mimeTypes, true)) {
49+
return $format;
50+
}
51+
if (null !== $canonicalMimeType && \in_array($canonicalMimeType, $mimeTypes, true)) {
52+
return $format;
53+
}
54+
}
55+
56+
return null;
57+
}
58+
}

src/EventListener/AddFormatListener.php

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace ApiPlatform\Core\EventListener;
1515

16+
use ApiPlatform\Core\Api\FormatMatcher;
1617
use ApiPlatform\Core\Api\FormatsProviderInterface;
1718
use ApiPlatform\Core\Exception\InvalidArgumentException;
1819
use ApiPlatform\Core\Util\RequestAttributesExtractor;
@@ -23,7 +24,7 @@
2324
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
2425

2526
/**
26-
* Chooses the format to user according to the Accept header and supported formats.
27+
* Chooses the format to use according to the Accept header and supported formats.
2728
*
2829
* @author Kévin Dunglas <[email protected]>
2930
*/
@@ -33,6 +34,7 @@ final class AddFormatListener
3334
private $formats = [];
3435
private $mimeTypes;
3536
private $formatsProvider;
37+
private $formatMatcher;
3638

3739
/**
3840
* @throws InvalidArgumentException
@@ -43,14 +45,13 @@ public function __construct(Negotiator $negotiator, /* FormatsProviderInterface
4345
if (\is_array($formatsProvider)) {
4446
@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);
4547
$this->formats = $formatsProvider;
48+
} else {
49+
if (!$formatsProvider instanceof FormatsProviderInterface) {
50+
throw new InvalidArgumentException(sprintf('The "$formatsProvider" argument is expected to be an implementation of the "%s" interface.', FormatsProviderInterface::class));
51+
}
4652

47-
return;
53+
$this->formatsProvider = $formatsProvider;
4854
}
49-
if (!$formatsProvider instanceof FormatsProviderInterface) {
50-
throw new InvalidArgumentException(sprintf('The "$formatsProvider" argument is expected to be an implementation of the "%s" interface.', FormatsProviderInterface::class));
51-
}
52-
53-
$this->formatsProvider = $formatsProvider;
5455
}
5556

5657
/**
@@ -69,6 +70,7 @@ public function onKernelRequest(GetResponseEvent $event)
6970
if (null !== $this->formatsProvider) {
7071
$this->formats = $this->formatsProvider->getFormatsFromAttributes(RequestAttributesExtractor::extractAttributes($request));
7172
}
73+
$this->formatMatcher = new FormatMatcher($this->formats);
7274

7375
$this->populateMimeTypes();
7476
$this->addRequestFormats($request, $this->formats);
@@ -86,11 +88,11 @@ public function onKernelRequest(GetResponseEvent $event)
8688
/** @var string|null $accept */
8789
$accept = $request->headers->get('Accept');
8890
if (null !== $accept) {
89-
if (null === $acceptHeader = $this->negotiator->getBest($accept, $mimeTypes)) {
91+
if (null === $mediaType = $this->negotiator->getBest($accept, $mimeTypes)) {
9092
throw $this->getNotAcceptableHttpException($accept, $mimeTypes);
9193
}
9294

93-
$request->setRequestFormat($request->getFormat($acceptHeader->getType()));
95+
$request->setRequestFormat($this->formatMatcher->getFormat($mediaType->getType()));
9496

9597
return;
9698
}
@@ -116,12 +118,14 @@ public function onKernelRequest(GetResponseEvent $event)
116118
}
117119

118120
/**
119-
* Adds API formats to the HttpFoundation Request.
121+
* Adds the supported formats to the request.
122+
*
123+
* This is necessary for {@see Request::getMimeType} and {@see Request::getMimeTypes} to work.
120124
*/
121125
private function addRequestFormats(Request $request, array $formats)
122126
{
123127
foreach ($formats as $format => $mimeTypes) {
124-
$request->setFormat($format, $mimeTypes);
128+
$request->setFormat($format, (array) $mimeTypes);
125129
}
126130
}
127131

src/EventListener/DeserializeListener.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace ApiPlatform\Core\EventListener;
1515

16+
use ApiPlatform\Core\Api\FormatMatcher;
1617
use ApiPlatform\Core\Api\FormatsProviderInterface;
1718
use ApiPlatform\Core\Exception\InvalidArgumentException;
1819
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
@@ -34,6 +35,7 @@ final class DeserializeListener
3435
private $serializerContextBuilder;
3536
private $formats = [];
3637
private $formatsProvider;
38+
private $formatMatcher;
3739

3840
/**
3941
* @throws InvalidArgumentException
@@ -45,14 +47,13 @@ public function __construct(SerializerInterface $serializer, SerializerContextBu
4547
if (\is_array($formatsProvider)) {
4648
@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);
4749
$this->formats = $formatsProvider;
50+
} else {
51+
if (!$formatsProvider instanceof FormatsProviderInterface) {
52+
throw new InvalidArgumentException(sprintf('The "$formatsProvider" argument is expected to be an implementation of the "%s" interface.', FormatsProviderInterface::class));
53+
}
4854

49-
return;
55+
$this->formatsProvider = $formatsProvider;
5056
}
51-
if (!$formatsProvider instanceof FormatsProviderInterface) {
52-
throw new InvalidArgumentException(sprintf('The "$formatsProvider" argument is expected to be an implementation of the "%s" interface.', FormatsProviderInterface::class));
53-
}
54-
55-
$this->formatsProvider = $formatsProvider;
5657
}
5758

5859
/**
@@ -78,6 +79,7 @@ public function onKernelRequest(GetResponseEvent $event)
7879
if (null !== $this->formatsProvider) {
7980
$this->formats = $this->formatsProvider->getFormatsFromAttributes($attributes);
8081
}
82+
$this->formatMatcher = new FormatMatcher($this->formats);
8183

8284
$format = $this->getFormat($request);
8385
$context = $this->serializerContextBuilder->createFromRequest($request, false, $attributes);
@@ -110,7 +112,7 @@ private function getFormat(Request $request): string
110112
throw new NotAcceptableHttpException('The "Content-Type" header must exist.');
111113
}
112114

113-
$format = $request->getFormat($contentType);
115+
$format = $this->formatMatcher->getFormat($contentType);
114116
if (null === $format || !isset($this->formats[$format])) {
115117
$supportedMimeTypes = [];
116118
foreach ($this->formats as $mimeTypes) {

0 commit comments

Comments
 (0)