From 80dc871d9a6840e70a5131db907d1f09b99ed7dc Mon Sep 17 00:00:00 2001 From: soyuka Date: Sat, 21 Feb 2026 08:24:50 +0100 Subject: [PATCH] fix(metadata): sort parameters by priority after pattern expansion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit | Q | A | ------------- | --- | Branch? | 4.2 | Tickets | Closes #7762 | License | MIT | Doc PR | ∅ Parameters expanded from `:property` placeholders were not re-sorted after priorities were assigned, causing iteration order to depend on insertion order rather than declared priority. Co-Authored-By: Claude Sonnet 4.6 --- ...meterResourceMetadataCollectionFactory.php | 2 + ...rResourceMetadataCollectionFactoryTest.php | 64 +++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php index da1fbd5c52..127524cbfb 100644 --- a/src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php @@ -223,6 +223,8 @@ private function getDefaultParameters(Operation $operation, string $resourceClas $parameters->add($key, $parameter->withPriority($priority)); } + $parameters->sort(); + return $parameters; } diff --git a/src/Metadata/Tests/Resource/Factory/ParameterResourceMetadataCollectionFactoryTest.php b/src/Metadata/Tests/Resource/Factory/ParameterResourceMetadataCollectionFactoryTest.php index 615dc1ce8f..462bbe5b83 100644 --- a/src/Metadata/Tests/Resource/Factory/ParameterResourceMetadataCollectionFactoryTest.php +++ b/src/Metadata/Tests/Resource/Factory/ParameterResourceMetadataCollectionFactoryTest.php @@ -161,6 +161,49 @@ public function testParameterFactoryNoFilter(): void $this->assertInstanceOf(Parameters::class, $parameters = $operation->getParameters()); } + public function testPatternParameterPriorityIsPreserved(): void + { + $nameCollection = $this->createStub(PropertyNameCollectionFactoryInterface::class); + $nameCollection->method('create')->willReturn(new PropertyNameCollection(['id', 'name', 'description'])); + + $propertyMetadata = $this->createStub(PropertyMetadataFactoryInterface::class); + $propertyMetadata->method('create')->willReturn(new ApiProperty(readable: true)); + + $filterLocator = $this->createStub(ContainerInterface::class); + $filterLocator->method('has')->willReturn(false); + + $parameterFactory = new ParameterResourceMetadataCollectionFactory( + $nameCollection, + $propertyMetadata, + new AttributesResourceMetadataCollectionFactory(), + $filterLocator + ); + + $resourceMetadataCollection = $parameterFactory->create(HasPatternParameterWithPriority::class); + $operation = $resourceMetadataCollection->getOperation(forceCollection: true); + $parameters = $operation->getParameters(); + + $this->assertInstanceOf(Parameters::class, $parameters); + + $expandedParam = $parameters->get('order[name]'); + $this->assertNotNull($expandedParam); + $this->assertSame(10, $expandedParam->getPriority(), 'Expanded pattern parameter must inherit parent priority'); + + $qParam = $parameters->get('q'); + $this->assertNotNull($qParam); + $this->assertSame(0, $qParam->getPriority()); + + // Parameters must be iterated in priority order (highest first) + $iteratedKeys = []; + foreach ($parameters as $key => $parameter) { + $iteratedKeys[] = $key; + } + + $qIndex = array_search('q', $iteratedKeys, true); + $orderNameIndex = array_search('order[name]', $iteratedKeys, true); + $this->assertLessThan($qIndex, $orderNameIndex, 'Pattern parameter with priority 10 must be iterated before parameter with priority 0'); + } + public function testParameterFactoryWithLimitedProperties(): void { $nameCollection = $this->createMock(PropertyNameCollectionFactoryInterface::class); @@ -220,3 +263,24 @@ class HasParameterAttribute public $name; public $description; } + +#[ApiResource( + operations: [ + new GetCollection( + parameters: [ + 'order[:property]' => new QueryParameter( + properties: ['name'], + priority: 10, + ), + 'q' => new QueryParameter( + priority: 0, + ), + ] + ), + ] +)] +class HasPatternParameterWithPriority +{ + public $id; + public $name; +}