From b1a3e4318e681ff83d793aff0404fc0f00b5940c Mon Sep 17 00:00:00 2001 From: soyuka Date: Fri, 16 Oct 2026 11:04:17 +0200 Subject: [PATCH] fix(metadata): match parameter subclasses in Parameters lookups --- src/Metadata/Parameters.php | 42 +++++++++++++++++---------- src/Metadata/Tests/ParametersTest.php | 28 ++++++++++++++++++ 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/Metadata/Parameters.php b/src/Metadata/Parameters.php index c576a113af7..83add5323f5 100644 --- a/src/Metadata/Parameters.php +++ b/src/Metadata/Parameters.php @@ -105,15 +105,13 @@ private static function propertyDiscriminator(Parameter $parameter): string */ public function remove(string $key, string $parameterClass = QueryParameter::class): self { - foreach ($this->parameters as $i => [$parameterName, $parameter]) { - if ($parameterName === $key && $parameterClass === $parameter::class) { - unset($this->parameters[$i]); - - return $this; - } + if (null === $i = $this->indexOf($key, $parameterClass)) { + throw new RuntimeException(\sprintf('Could not remove parameter "%s".', $key)); } - throw new RuntimeException(\sprintf('Could not remove parameter "%s".', $key)); + unset($this->parameters[$i]); + + return $this; } /** @@ -125,13 +123,11 @@ public function remove(string $key, string $parameterClass = QueryParameter::cla */ public function get(string $key, string $parameterClass = QueryParameter::class): ?Parameter { - foreach ($this->parameters as [$parameterName, $parameter]) { - if ($parameterName === $key && $parameterClass === $parameter::class) { - return $parameter; - } + if (null === $i = $this->indexOf($key, $parameterClass)) { + return null; } - return null; + return $this->parameters[$i][1]; } /** @@ -141,13 +137,27 @@ public function get(string $key, string $parameterClass = QueryParameter::class) */ public function has(string $key, string $parameterClass = QueryParameter::class): bool { - foreach ($this->parameters as [$parameterName, $parameter]) { - if ($parameterName === $key && $parameterClass === $parameter::class) { - return true; + return null !== $this->indexOf($key, $parameterClass); + } + + private function indexOf(string $key, string $parameterClass): ?int + { + $subclassMatch = null; + foreach ($this->parameters as $i => [$parameterName, $parameter]) { + if ($parameterName !== $key) { + continue; + } + + if ($parameterClass === $parameter::class) { + return $i; + } + + if (null === $subclassMatch && $parameter instanceof $parameterClass) { + $subclassMatch = $i; } } - return false; + return $subclassMatch; } public function count(): int diff --git a/src/Metadata/Tests/ParametersTest.php b/src/Metadata/Tests/ParametersTest.php index 966212e6db0..ca615645612 100644 --- a/src/Metadata/Tests/ParametersTest.php +++ b/src/Metadata/Tests/ParametersTest.php @@ -16,6 +16,7 @@ use ApiPlatform\Metadata\HeaderParameter; use ApiPlatform\Metadata\Parameters; use ApiPlatform\Metadata\QueryParameter; +use ApiPlatform\Metadata\ScalarHeaderParameter; use PHPUnit\Framework\TestCase; class ParametersTest extends TestCase @@ -77,4 +78,31 @@ public function testPropertyPlaceholderKeysAreNotDeduplicatedViaAdd(): void $this->assertCount(2, $parameters); } + + public function testLookupMatchesSubclass(): void + { + $scalar = new ScalarHeaderParameter(key: 'Foo'); + $parameters = new Parameters([$scalar]); + + $this->assertSame($scalar, $parameters->get('Foo', HeaderParameter::class)); + $this->assertTrue($parameters->has('Foo', HeaderParameter::class)); + $this->assertFalse($parameters->has('Foo')); + + $parameters->remove('Foo', HeaderParameter::class); + $this->assertCount(0, $parameters); + } + + public function testLookupPrefersExactClassOverSubclass(): void + { + $scalar = new ScalarHeaderParameter(key: 'Foo'); + $header = new HeaderParameter(key: 'Foo'); + $parameters = new Parameters([$scalar, $header]); + + $this->assertSame($header, $parameters->get('Foo', HeaderParameter::class)); + $this->assertSame($scalar, $parameters->get('Foo', ScalarHeaderParameter::class)); + + $parameters->remove('Foo', HeaderParameter::class); + $this->assertCount(1, $parameters); + $this->assertSame($scalar, $parameters->get('Foo', HeaderParameter::class)); + } }