diff --git a/src/Hydra/Serializer/PartialCollectionViewNormalizer.php b/src/Hydra/Serializer/PartialCollectionViewNormalizer.php index 51ecf50bb8..12f3ee5727 100644 --- a/src/Hydra/Serializer/PartialCollectionViewNormalizer.php +++ b/src/Hydra/Serializer/PartialCollectionViewNormalizer.php @@ -157,8 +157,13 @@ private function cursorPaginationFields(array $fields, int $direction, $object): $operator = $direction > 0 ? $forwardRangeOperator : $backwardRangeOperator; + $value = $this->propertyAccessor->getValue($object, $field['field']); + if ($value instanceof \DateTimeInterface) { + $value = $value->format(\DateTimeInterface::ATOM); + } + $paginationFilters[$field['field']] = [ - $operator => (string) $this->propertyAccessor->getValue($object, $field['field']), + $operator => (string) $value, ]; } diff --git a/tests/Fixtures/TestBundle/Entity/Issue8085/DatedCursorDummy.php b/tests/Fixtures/TestBundle/Entity/Issue8085/DatedCursorDummy.php new file mode 100644 index 0000000000..654402930a --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Issue8085/DatedCursorDummy.php @@ -0,0 +1,44 @@ + + * + * 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\Issue8085; + +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\GetCollection; +use Doctrine\ORM\Mapping as ORM; + +#[ApiResource( + operations: [ + new GetCollection( + paginationItemsPerPage: 3, + paginationPartial: true, + paginationViaCursor: [['field' => 'createdAt', 'direction' => 'DESC']], + ), + ], + graphQlOperations: [], +)] +#[ApiFilter(DateFilter::class, properties: ['createdAt'])] +#[ORM\Entity] +#[ORM\Table(name: 'issue_8085_dated_cursor_dummy')] +class DatedCursorDummy +{ + #[ORM\Id] + #[ORM\Column(type: 'integer')] + #[ORM\GeneratedValue] + public ?int $id = null; + + #[ORM\Column(type: 'datetime_immutable')] + public ?\DateTimeImmutable $createdAt = null; +} diff --git a/tests/Functional/JsonLd/CursorPaginationDateTimeTest.php b/tests/Functional/JsonLd/CursorPaginationDateTimeTest.php new file mode 100644 index 0000000000..af75584313 --- /dev/null +++ b/tests/Functional/JsonLd/CursorPaginationDateTimeTest.php @@ -0,0 +1,71 @@ + + * + * 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\JsonLd; + +use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue8085\DatedCursorDummy; +use ApiPlatform\Tests\RecreateSchemaTrait; +use ApiPlatform\Tests\SetupClassResourcesTrait; + +/** + * Regression test for https://github.com/api-platform/core/issues/8085. + * + * Cursor pagination must support DateTimeInterface fields without throwing + * "Object of class DateTime could not be converted to string". + */ +final class CursorPaginationDateTimeTest extends ApiTestCase +{ + use RecreateSchemaTrait; + use SetupClassResourcesTrait; + + protected static ?bool $alwaysBootKernel = false; + + public static function getResources(): array + { + return [DatedCursorDummy::class]; + } + + public function testCursorPaginationWithDateTimeField(): void + { + if ($this->isMongoDB()) { + $this->markTestSkipped(); + } + + $this->recreateSchema([DatedCursorDummy::class]); + $manager = $this->getManager(); + for ($i = 1; $i <= 5; ++$i) { + $d = new DatedCursorDummy(); + $d->createdAt = new \DateTimeImmutable("2024-01-0$i 12:00:00", new \DateTimeZone('UTC')); + $manager->persist($d); + } + $manager->flush(); + + $response = self::createClient()->request('GET', '/dated_cursor_dummies', [ + 'headers' => ['Accept' => 'application/ld+json'], + ]); + + $this->assertResponseIsSuccessful(); + $body = $response->toArray(); + + $this->assertArrayHasKey('hydra:view', $body); + $this->assertArrayHasKey('hydra:next', $body['hydra:view']); + + // 5 rows (2024-01-01..05) sorted DESC + 3 per page → visible page is 05,04,03; + // hydra:next must use lt with the last visible item (2024-01-03) as ISO 8601. + $this->assertMatchesRegularExpression( + '#createdAt%5Blt%5D=2024-01-03T12%3A00%3A00(?:%2B00%3A00|Z)#', + $body['hydra:view']['hydra:next'], + ); + } +}