diff --git a/src/JsonApi/State/JsonApiProvider.php b/src/JsonApi/State/JsonApiProvider.php index bf86cb406de..0976d9ce849 100644 --- a/src/JsonApi/State/JsonApiProvider.php +++ b/src/JsonApi/State/JsonApiProvider.php @@ -70,6 +70,12 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $filters = array_merge($pageParameter, $filters); } + foreach (['page', 'itemsPerPage', 'pagination', 'partial'] as $paginationParameter) { + if (isset($queryParameters[$paginationParameter]) && !\is_array($queryParameters[$paginationParameter]) && !isset($filters[$paginationParameter])) { + $filters[$paginationParameter] = $queryParameters[$paginationParameter]; + } + } + [$included, $properties] = $this->transformFieldsetsParameters($queryParameters, $operation->getShortName() ?? ''); if ($properties) { diff --git a/src/JsonApi/Tests/State/JsonApiProviderTest.php b/src/JsonApi/Tests/State/JsonApiProviderTest.php index 5ce94261026..e1478a7c8cd 100644 --- a/src/JsonApi/Tests/State/JsonApiProviderTest.php +++ b/src/JsonApi/Tests/State/JsonApiProviderTest.php @@ -37,4 +37,25 @@ public function testProvide(): void $provider = new JsonApiProvider($decorated); $provider->provide($operation, [], $context); } + + public function testProvideMergesFlatPaginationWithBracketFilter(): void + { + $request = new Request(['page' => '2', 'itemsPerPage' => '5', 'pagination' => 'true', 'filter' => ['custom' => 'true']]); + $request->setRequestFormat('jsonapi'); + + $operation = new Get(class: \stdClass::class, shortName: 'dummy'); + $context = ['request' => $request]; + $decorated = $this->createMock(ProviderInterface::class); + $decorated->expects($this->once())->method('provide')->with($operation, [], $context); + + $provider = new JsonApiProvider($decorated); + $provider->provide($operation, [], $context); + + $this->assertSame([ + 'custom' => 'true', + 'page' => '2', + 'itemsPerPage' => '5', + 'pagination' => 'true', + ], $request->attributes->get('_api_filters')); + } } diff --git a/tests/Functional/JsonApiFlatPaginationTest.php b/tests/Functional/JsonApiFlatPaginationTest.php new file mode 100644 index 00000000000..8495531a2f0 --- /dev/null +++ b/tests/Functional/JsonApiFlatPaginationTest.php @@ -0,0 +1,85 @@ + + * + * 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\Symfony\Bundle\Test\ApiTestCase; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; +use ApiPlatform\Tests\RecreateSchemaTrait; +use ApiPlatform\Tests\SetupClassResourcesTrait; + +final class JsonApiFlatPaginationTest extends ApiTestCase +{ + use RecreateSchemaTrait; + use SetupClassResourcesTrait; + + protected static ?bool $alwaysBootKernel = false; + + /** + * @return class-string[] + */ + public static function getResources(): array + { + return [Dummy::class]; + } + + protected function setUp(): void + { + parent::setUp(); + + if ($this->isMongoDB()) { + return; + } + + $this->recreateSchema([Dummy::class]); + $this->loadFixtures(); + } + + /** + * Regression test for issue #7888: when a JSON:API request combines a + * bracket-form filter (filter[...]) with a flat pagination param (page=N), + * the page param must still drive the current page rather than being + * silently dropped. + */ + public function testFlatPageWithBracketFilterDrivesCurrentPage(): void + { + if ($this->isMongoDB()) { + $this->markTestSkipped('Not tested with mongodb.'); + } + + $response = self::createClient()->request('GET', '/dummies?filter[name]=foo&page=2', [ + 'headers' => ['Accept' => 'application/vnd.api+json'], + ]); + + $this->assertResponseIsSuccessful(); + $this->assertResponseHeaderSame('content-type', 'application/vnd.api+json; charset=utf-8'); + + $data = $response->toArray(); + + $this->assertSame(2, $data['meta']['currentPage'] ?? null); + $this->assertSame(5, $data['meta']['totalItems'] ?? null); + } + + private function loadFixtures(): void + { + $manager = $this->getManager(); + + for ($i = 1; $i <= 5; ++$i) { + $dummy = new Dummy(); + $dummy->setName('foo #'.$i); + $manager->persist($dummy); + } + + $manager->flush(); + } +}