diff --git a/src/Metadata/Util/ContentNegotiationTrait.php b/src/Metadata/Util/ContentNegotiationTrait.php
index 6690cca704..9f951261a4 100644
--- a/src/Metadata/Util/ContentNegotiationTrait.php
+++ b/src/Metadata/Util/ContentNegotiationTrait.php
@@ -97,7 +97,15 @@ private function getRequestFormat(Request $request, array $formats, bool $throw
/** @var string|null $accept */
$accept = $request->headers->get('Accept');
if (null !== $accept) {
- if ($mediaType = $this->negotiator->getBest($accept, $mimeTypes)) {
+ $mediaType = $this->negotiator->getBest($accept, $mimeTypes);
+ // willdurand/negotiation treats media-range parameters as match
+ // constraints, so a wildcard carrying informational params
+ // (e.g. `*\/*; charset=utf-8` from PhpStorm) fails negotiation.
+ // Retry on a bare wildcard. See #1532.
+ if (!$mediaType && str_contains($accept, '*/*')) {
+ $mediaType = $this->negotiator->getBest('*/*', $mimeTypes);
+ }
+ if ($mediaType) {
return $this->getMimeTypeFormat($mediaType->getType(), $formats);
}
diff --git a/tests/Fixtures/TestBundle/ApiResource/WildcardAcceptFormat/WildcardAcceptFormat.php b/tests/Fixtures/TestBundle/ApiResource/WildcardAcceptFormat/WildcardAcceptFormat.php
new file mode 100644
index 0000000000..9e84819426
--- /dev/null
+++ b/tests/Fixtures/TestBundle/ApiResource/WildcardAcceptFormat/WildcardAcceptFormat.php
@@ -0,0 +1,43 @@
+
+ *
+ * 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\Tests\Fixtures\TestBundle\ApiResource\WildcardAcceptFormat;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\Operation;
+use Symfony\Component\HttpFoundation\Response;
+
+#[Get(
+ shortName: 'WildcardAcceptFormat',
+ uriTemplate: '/wildcard_accept_format/{id}',
+ outputFormats: ['jsonld' => ['application/ld+json'], 'html' => ['text/html']],
+ provider: [self::class, 'provide'],
+ extraProperties: ['_api_disable_swagger_provider' => true]
+)]
+class WildcardAcceptFormat
+{
+ #[ApiProperty(identifier: true)]
+ public int $id = 1;
+
+ public string $name = 'hello';
+
+ public static function provide(Operation $operation, array $uriVariables = [], array $context = []): self|Response
+ {
+ if ('html' === $context['request']?->getRequestFormat()) {
+ return new Response('
hello
', 200, ['Content-Type' => 'text/html']);
+ }
+
+ return new self();
+ }
+}
diff --git a/tests/Functional/FormatTest.php b/tests/Functional/FormatTest.php
index 80aab72cf9..4fb21227b9 100644
--- a/tests/Functional/FormatTest.php
+++ b/tests/Functional/FormatTest.php
@@ -15,6 +15,7 @@
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue6384\AcceptHtml;
+use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\WildcardAcceptFormat\WildcardAcceptFormat;
use ApiPlatform\Tests\SetupClassResourcesTrait;
final class FormatTest extends ApiTestCase
@@ -28,7 +29,7 @@ final class FormatTest extends ApiTestCase
*/
public static function getResources(): array
{
- return [AcceptHtml::class];
+ return [AcceptHtml::class, WildcardAcceptFormat::class];
}
public function testShouldReturnHtml(): void
@@ -37,4 +38,37 @@ public function testShouldReturnHtml(): void
$this->assertResponseIsSuccessful();
$this->assertEquals($r->getContent(), 'hello
');
}
+
+ /**
+ * @see https://github.com/api-platform/core/issues/1532
+ */
+ public function testWildcardAcceptHeaderPicksFirstConfiguredFormat(): void
+ {
+ $response = self::createClient()->request('GET', '/wildcard_accept_format/1', ['headers' => ['Accept' => '*/*']]);
+ $this->assertResponseIsSuccessful();
+ $this->assertStringStartsWith('application/ld+json', $response->getHeaders()['content-type'][0]);
+ }
+
+ /**
+ * @see https://github.com/api-platform/core/issues/1532
+ */
+ public function testWildcardAcceptHeaderWithParametersPicksFirstConfiguredFormat(): void
+ {
+ $response = self::createClient()->request('GET', '/wildcard_accept_format/1', ['headers' => ['Accept' => '*/*; charset=utf-8']]);
+ $this->assertResponseIsSuccessful();
+ $this->assertStringStartsWith('application/ld+json', $response->getHeaders()['content-type'][0]);
+ }
+
+ public function testWildcardAcceptHeaderRespectsQualityOfConcreteType(): void
+ {
+ $response = self::createClient()->request('GET', '/wildcard_accept_format/1', ['headers' => ['Accept' => '*/*; charset=utf-8; q=0.1, text/html; q=0.9']]);
+ $this->assertResponseIsSuccessful();
+ $this->assertStringStartsWith('text/html', $response->getHeaders()['content-type'][0]);
+ }
+
+ public function testConcreteAcceptHeaderWithUnsupportedTypeReturnsNotAcceptable(): void
+ {
+ self::createClient()->request('GET', '/wildcard_accept_format/1', ['headers' => ['Accept' => 'application/xml']]);
+ $this->assertResponseStatusCodeSame(406);
+ }
}