diff --git a/src/Metadata/Operation.php b/src/Metadata/Operation.php index d1f56e98da..c6b933563a 100644 --- a/src/Metadata/Operation.php +++ b/src/Metadata/Operation.php @@ -794,6 +794,12 @@ public function __construct( protected ?bool $serialize = null, protected ?bool $fetchPartial = null, protected ?bool $forceEager = null, + /** + * The priority helps with the order of operations when looping over a resource's operations. + * It can be usefull when we loop over operations to find a matching IRI, although most of the use cases + * should be covered by the HttpOperation::itemUriTemplate or the ApiProperty::uriTemplate functionalities. + * Sort is ascendant: a lower priority comes first in the list. + */ protected ?int $priority = null, protected ?string $name = null, protected $provider = null, diff --git a/src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php index acf8beb0eb..cd391d0cb1 100644 --- a/src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php @@ -119,7 +119,9 @@ private function buildResourceOperations(array $attributes, string $resourceClas } [$key, $operation] = $this->getOperationWithDefaults($resources[$index], $operationAttribute); - $operation = $operation->withPriority(++$operationPriority); + if (null === $operation->getPriority()) { + $operation = $operation->withPriority(++$operationPriority); + } $operations = $resources[$index]->getOperations() ?? new Operations(); $resources[$index] = $resources[$index]->withOperations($operations->add($key, $operation)); } diff --git a/src/Metadata/Tests/OperationsTest.php b/src/Metadata/Tests/OperationsTest.php new file mode 100644 index 0000000000..74cf8b3e19 --- /dev/null +++ b/src/Metadata/Tests/OperationsTest.php @@ -0,0 +1,36 @@ + + * + * 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\Metadata\Tests; + +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\Operations; +use PHPUnit\Framework\TestCase; + +final class OperationsTest extends TestCase +{ + public function testOperationsHaveNameIfNotSet(): void + { + $operations = new Operations([new Get(name: 'a'), new Get(name: 'b')]); + + foreach ($operations as $name => $operation) { + $this->assertEquals($name, $operation->getName()); + } + } + + public function testOperationAreSorted(): void + { + $operations = new Operations(['a' => new Get(priority: 0), 'b' => new Get(priority: -1)]); + $this->assertEquals(['b', 'a'], array_keys(iterator_to_array($operations))); + } +} diff --git a/tests/Fixtures/TestBundle/ApiResource/OperationPriorities.php b/tests/Fixtures/TestBundle/ApiResource/OperationPriorities.php new file mode 100644 index 0000000000..0d032e63f3 --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/OperationPriorities.php @@ -0,0 +1,33 @@ + + * + * 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; + +use ApiPlatform\Metadata\Get; + +#[Get(name: 'a', priority: 1, uriTemplate: 'operation_priority/{id}', provider: [self::class, 'shouldNotBeCalled'])] +#[Get(name: 'b', priority: -1, uriTemplate: 'operation_priority/{id}', provider: [self::class, 'shouldBeCalled'])] +class OperationPriorities +{ + public int $id = 1; + + public static function shouldBeCalled(): self + { + return new self(); + } + + public static function shouldNotBeCalled(): self + { + throw new \Exception('fail'); + } +} diff --git a/tests/Symfony/Bundle/Test/ApiTestCaseTest.php b/tests/Symfony/Bundle/Test/ApiTestCaseTest.php index b236a1d03d..86f40e65a3 100644 --- a/tests/Symfony/Bundle/Test/ApiTestCaseTest.php +++ b/tests/Symfony/Bundle/Test/ApiTestCaseTest.php @@ -267,6 +267,16 @@ public function testFindIriBy(): void $this->assertNull(self::findIriBy($resource, ['name' => 'not-exist'])); } + public function testGetPrioritizedOperation(): void + { + $r = self::createClient()->request('GET', '/operation_priority/1', [ + 'headers' => [ + 'accept' => 'application/ld+json', + ], + ]); + $this->assertResponseIsSuccessful(); + } + /** * @group mercure */