diff --git a/src/Hal/JsonSchema/SchemaFactory.php b/src/Hal/JsonSchema/SchemaFactory.php index 8075bd40f81..cb7c4ecf880 100644 --- a/src/Hal/JsonSchema/SchemaFactory.php +++ b/src/Hal/JsonSchema/SchemaFactory.php @@ -177,6 +177,14 @@ public function buildSchema(string $className, string $format = 'jsonhal', strin ]; } + if (false === $operation->getPaginationEnabled()) { + unset($definitions[self::COLLECTION_BASE_SCHEMA_NAME]['properties']['itemsPerPage']); + unset($definitions[self::COLLECTION_BASE_SCHEMA_NAME]['properties']['_links']['properties']['first']); + unset($definitions[self::COLLECTION_BASE_SCHEMA_NAME]['properties']['_links']['properties']['last']); + unset($definitions[self::COLLECTION_BASE_SCHEMA_NAME]['properties']['_links']['properties']['next']); + unset($definitions[self::COLLECTION_BASE_SCHEMA_NAME]['properties']['_links']['properties']['previous']); + } + unset($schema['items']); unset($schema['type']); diff --git a/src/Hydra/JsonSchema/SchemaFactory.php b/src/Hydra/JsonSchema/SchemaFactory.php index 4dd3e78047c..41a753b9880 100644 --- a/src/Hydra/JsonSchema/SchemaFactory.php +++ b/src/Hydra/JsonSchema/SchemaFactory.php @@ -235,6 +235,10 @@ public function buildSchema(string $className, string $format = 'jsonld', string ]; } + if (false === $operation->getPaginationEnabled()) { + unset($definitions[self::COLLECTION_BASE_SCHEMA_NAME]['properties'][$hydraPrefix.'view']); + } + $definitionName = $this->definitionNameFactory->create($className, $format, $inputOrOutputClass, $operation, $serializerContext); $schema['type'] = 'object'; $schema['description'] = "$definitionName collection."; diff --git a/src/Hydra/Serializer/PartialCollectionViewNormalizer.php b/src/Hydra/Serializer/PartialCollectionViewNormalizer.php index c37586f7e95..5864b2a534f 100644 --- a/src/Hydra/Serializer/PartialCollectionViewNormalizer.php +++ b/src/Hydra/Serializer/PartialCollectionViewNormalizer.php @@ -95,6 +95,10 @@ public function normalize(mixed $object, ?string $format = null, array $context $partialCollectionView = $this->getPartialCollectionView($object, $context['uri'] ?? $context['request_uri'] ?? '/', $this->pageParameterName, $this->enabledParameterName, $operation?->getUrlGenerationStrategy() ?? $this->urlGenerationStrategy); + if (false === $operation?->getPaginationEnabled()) { + return $data; + } + $view = [ '@id' => $partialCollectionView->id, '@type' => $hydraPrefix.'PartialCollectionView', diff --git a/src/JsonApi/JsonSchema/SchemaFactory.php b/src/JsonApi/JsonSchema/SchemaFactory.php index 1189c34888d..7e6ba1a2996 100644 --- a/src/JsonApi/JsonSchema/SchemaFactory.php +++ b/src/JsonApi/JsonSchema/SchemaFactory.php @@ -231,6 +231,19 @@ public function buildSchema(string $className, string $format = 'jsonapi', strin ]; } + if (false === $operation->getPaginationEnabled()) { + unset($definitions[self::COLLECTION_BASE_SCHEMA_NAME]['properties']['links']['properties']['first']); + unset($definitions[self::COLLECTION_BASE_SCHEMA_NAME]['properties']['links']['properties']['last']); + unset($definitions[self::COLLECTION_BASE_SCHEMA_NAME]['properties']['links']['properties']['next']); + unset($definitions[self::COLLECTION_BASE_SCHEMA_NAME]['properties']['links']['properties']['prev']); + unset($definitions[self::COLLECTION_BASE_SCHEMA_NAME]['properties']['links']['example']['first']); + unset($definitions[self::COLLECTION_BASE_SCHEMA_NAME]['properties']['links']['example']['last']); + unset($definitions[self::COLLECTION_BASE_SCHEMA_NAME]['properties']['links']['example']['next']); + unset($definitions[self::COLLECTION_BASE_SCHEMA_NAME]['properties']['links']['example']['prev']); + unset($definitions[self::COLLECTION_BASE_SCHEMA_NAME]['properties']['meta']['properties']['itemsPerPage']); + unset($definitions[self::COLLECTION_BASE_SCHEMA_NAME]['properties']['meta']['properties']['currentPage']); + } + unset($schema['items']); unset($schema['type']); diff --git a/tests/Fixtures/TestBundle/Entity/PaginationDisabledEntity.php b/tests/Fixtures/TestBundle/Entity/PaginationDisabledEntity.php new file mode 100644 index 00000000000..4bd93e0ad92 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/PaginationDisabledEntity.php @@ -0,0 +1,37 @@ + + * + * 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\Entity; + +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\GetCollection; +use Doctrine\ORM\Mapping as ORM; + +#[ApiResource] +#[GetCollection( + paginationEnabled: false, + paginationItemsPerPage: 3, +)] +#[ORM\Entity] +class PaginationDisabledEntity +{ + #[ORM\Column(type: 'integer', nullable: true)] + #[ORM\Id] + #[ORM\GeneratedValue(strategy: 'AUTO')] + private ?int $id = null; + + public function getId(): ?int + { + return $this->id; + } +} diff --git a/tests/Functional/PaginationDisabledTest.php b/tests/Functional/PaginationDisabledTest.php new file mode 100644 index 00000000000..d7bb6d47bc0 --- /dev/null +++ b/tests/Functional/PaginationDisabledTest.php @@ -0,0 +1,317 @@ + + * + * 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\Functional; + +use ApiPlatform\JsonSchema\SchemaFactoryInterface; +use ApiPlatform\Metadata\Operation\Factory\OperationMetadataFactory; +use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\PaginationDisabledEntity; +use ApiPlatform\Tests\RecreateSchemaTrait; +use ApiPlatform\Tests\SetupClassResourcesTrait; +use Doctrine\ODM\MongoDB\MongoDBException; + +final class PaginationDisabledTest extends ApiTestCase +{ + use RecreateSchemaTrait; + use SetupClassResourcesTrait; + + protected SchemaFactoryInterface $schemaFactory; + private OperationMetadataFactory $operationMetadataFactory; + + protected static ?bool $alwaysBootKernel = false; + + /** + * @return class-string[] + */ + public static function getResources(): array + { + return [PaginationDisabledEntity::class]; + } + + protected function setUp(): void + { + parent::setUp(); + if ($this->isMongoDB()) { + return; + } + + $this->recreateSchema(static::getResources()); + $this->loadFixtures(); + + $this->schemaFactory = self::getContainer()->get('api_platform.json_schema.schema_factory'); + $this->operationMetadataFactory = self::getContainer()->get('api_platform.metadata.operation.metadata_factory'); + } + + /** + * @throws \Throwable + * @throws MongoDBException + */ + private function loadFixtures(): void + { + $manager = $this->getManager(); + + for ($i = 0; $i < 4; ++$i) { + $manager->persist(new PaginationDisabledEntity()); + } + + $manager->flush(); + } + + public function testCollectionJsonApi(): void + { + if ($this->isMongoDB()) { + $this->markTestSkipped('Not tested with mongodb.'); + } + + self::createClient()->request('GET', '/pagination_disabled_entities', ['headers' => ['Accept' => 'application/vnd.api+json']]); + + $this->assertResponseIsSuccessful(); + $this->assertJsonEquals([ + 'links' => [ + 'self' => '/pagination_disabled_entities', + ], + 'meta' => [ + 'totalItems' => 4, + ], + 'data' => [ + [ + 'id' => '/pagination_disabled_entities/1', + 'type' => 'PaginationDisabledEntity', + 'attributes' => [ + '_id' => 1, + ], + ], + [ + 'id' => '/pagination_disabled_entities/2', + 'type' => 'PaginationDisabledEntity', + 'attributes' => [ + '_id' => 2, + ], + ], + [ + 'id' => '/pagination_disabled_entities/3', + 'type' => 'PaginationDisabledEntity', + 'attributes' => [ + '_id' => 3, + ], + ], + [ + 'id' => '/pagination_disabled_entities/4', + 'type' => 'PaginationDisabledEntity', + 'attributes' => [ + '_id' => 4, + ], + ], + ], + ]); + + $this->assertMatchesResourceCollectionJsonSchema(PaginationDisabledEntity::class, format: 'jsonapi'); + } + + public function testSchemaCollectionJsonApi(): void + { + if ($this->isMongoDB()) { + $this->markTestSkipped('Not tested with mongodb.'); + } + + $schema = $this->schemaFactory->buildSchema(PaginationDisabledEntity::class, 'jsonapi', operation: $this->operationMetadataFactory->create('_api_/pagination_disabled_entities{._format}_get_collection')); + $this->assertArrayHasKey('definitions', $schema); + $this->assertArrayHasKey('JsonApiCollectionBaseSchema', $schema['definitions']); + $this->assertArrayHasKey('properties', $schema['definitions']['JsonApiCollectionBaseSchema']); + $this->assertArrayHasKey('links', $schema['definitions']['JsonApiCollectionBaseSchema']['properties']); + $this->assertSame( + [ + 'type' => 'object', + 'properties' => [ + 'self' => [ + 'type' => 'string', + 'format' => 'iri-reference', + ], + ], + 'example' => [ + 'self' => 'string', + ], + ], + $schema['definitions']['JsonApiCollectionBaseSchema']['properties']['links'] + ); + $this->assertArrayHasKey('meta', $schema['definitions']['JsonApiCollectionBaseSchema']['properties']); + $this->assertArrayHasKey('properties', $schema['definitions']['JsonApiCollectionBaseSchema']['properties']['meta']); + $this->assertSame( + [ + 'totalItems' => [ + 'type' => 'integer', + 'minimum' => 0, + ], + ], + $schema['definitions']['JsonApiCollectionBaseSchema']['properties']['meta']['properties'] + ); + } + + public function testCollectionHal(): void + { + if ($this->isMongoDB()) { + $this->markTestSkipped('Not tested with mongodb.'); + } + + self::createClient()->request('GET', '/pagination_disabled_entities', ['headers' => ['Accept' => 'application/hal+json']]); + + $this->assertResponseIsSuccessful(); + $this->assertJsonEquals([ + '_links' => [ + 'self' => [ + 'href' => '/pagination_disabled_entities', + ], + 'item' => [ + ['href' => '/pagination_disabled_entities/1'], + ['href' => '/pagination_disabled_entities/2'], + ['href' => '/pagination_disabled_entities/3'], + ['href' => '/pagination_disabled_entities/4'], + ], + ], + 'totalItems' => 4, + '_embedded' => [ + 'item' => [ + [ + '_links' => [ + 'self' => ['href' => '/pagination_disabled_entities/1'], + ], + 'id' => 1, + ], + [ + '_links' => [ + 'self' => ['href' => '/pagination_disabled_entities/2'], + ], + 'id' => 2, + ], + [ + '_links' => [ + 'self' => ['href' => '/pagination_disabled_entities/3'], + ], + 'id' => 3, + ], + [ + '_links' => [ + 'self' => ['href' => '/pagination_disabled_entities/4'], + ], + 'id' => 4, + ], + ], + ], + ]); + + $this->assertMatchesResourceCollectionJsonSchema(PaginationDisabledEntity::class, format: 'jsonhal'); + } + + public function testSchemaCollectionHal(): void + { + if ($this->isMongoDB()) { + $this->markTestSkipped('Not tested with mongodb.'); + } + + $schema = $this->schemaFactory->buildSchema(PaginationDisabledEntity::class, 'jsonhal', operation: $this->operationMetadataFactory->create('_api_/pagination_disabled_entities{._format}_get_collection')); + + $this->assertArrayHasKey('definitions', $schema); + $this->assertArrayHasKey('HalCollectionBaseSchema', $schema['definitions']); + $this->assertArrayHasKey('_links', $schema['definitions']['HalCollectionBaseSchema']['properties']); + $this->assertSame( + [ + 'type' => 'object', + 'properties' => [ + 'self' => [ + 'type' => 'object', + 'properties' => [ + 'href' => [ + 'type' => 'string', + 'format' => 'iri-reference', + ], + ], + ], + ], + ], + $schema['definitions']['HalCollectionBaseSchema']['properties']['_links'] + ); + $this->assertArrayNotHasKey('itemsPerPage', $schema['definitions']['HalCollectionBaseSchema']['properties']); + $this->assertArrayHasKey('totalItems', $schema['definitions']['HalCollectionBaseSchema']['properties']); + $this->assertSame( + [ + 'type' => 'integer', + 'minimum' => 0, + ], + $schema['definitions']['HalCollectionBaseSchema']['properties']['totalItems'] + ); + } + + public function testCollectionJsonLd(): void + { + if ($this->isMongoDB()) { + $this->markTestSkipped('Not tested with mongodb.'); + } + + self::createClient()->request('GET', '/pagination_disabled_entities', ['headers' => ['Accept' => 'application/ld+json']]); + + $this->assertResponseIsSuccessful(); + $this->assertJsonEquals([ + '@context' => '/contexts/PaginationDisabledEntity', + '@id' => '/pagination_disabled_entities', + '@type' => 'hydra:Collection', + 'hydra:totalItems' => 4, + 'hydra:member' => [ + [ + '@id' => '/pagination_disabled_entities/1', + '@type' => 'PaginationDisabledEntity', + 'id' => 1, + ], + [ + '@id' => '/pagination_disabled_entities/2', + '@type' => 'PaginationDisabledEntity', + 'id' => 2, + ], + [ + '@id' => '/pagination_disabled_entities/3', + '@type' => 'PaginationDisabledEntity', + 'id' => 3, + ], + + [ + '@id' => '/pagination_disabled_entities/4', + '@type' => 'PaginationDisabledEntity', + 'id' => 4, + ], + ], + ]); + + $this->assertMatchesResourceCollectionJsonSchema(PaginationDisabledEntity::class, format: 'jsonld'); + } + + public function testSchemaCollectionJsonLd(): void + { + if ($this->isMongoDB()) { + $this->markTestSkipped('Not tested with mongodb.'); + } + + $schema = $this->schemaFactory->buildSchema(PaginationDisabledEntity::class, 'jsonld', operation: $this->operationMetadataFactory->create('_api_/pagination_disabled_entities{._format}_get_collection')); + + $this->assertArrayHasKey('definitions', $schema); + $this->assertArrayHasKey('HydraCollectionBaseSchema', $schema['definitions']); + $this->assertArrayNotHasKey('hydra:view', $schema['definitions']['HydraCollectionBaseSchema']['properties']); + $this->assertArrayHasKey('hydra:totalItems', $schema['definitions']['HydraCollectionBaseSchema']['properties']); + $this->assertSame( + [ + 'type' => 'integer', + 'minimum' => 0, + ], + $schema['definitions']['HydraCollectionBaseSchema']['properties']['hydra:totalItems'] + ); + } +}