From 642321db61b9790400a4c2c593092cab4c3ae6f2 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli <36352093+GuySartorelli@users.noreply.github.com> Date: Thu, 20 Jul 2023 16:37:03 +1200 Subject: [PATCH] FIX Trigger eagerloading for first() and last() (#10875) --- src/ORM/DataList.php | 19 +++++++++++++++---- tests/php/ORM/DataListEagerLoadingTest.php | 19 +++++++++++++++++++ tests/php/ORM/DataListTest.php | 22 ++++++++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/ORM/DataList.php b/src/ORM/DataList.php index 4b1598877..01c486579 100644 --- a/src/ORM/DataList.php +++ b/src/ORM/DataList.php @@ -1408,8 +1408,13 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li */ public function first() { - foreach ($this->dataQuery->firstRow()->execute() as $row) { - return $this->createDataObject($row); + // We need to trigger eager loading by iterating over the list, rather than just fetching + // the first row from the dataQuery. + // This limit and offset logic mimics that $this->dataQuery->firstRow() would ultimately do. + $limitOffset = $this->dataQuery->query()->getLimit() ?? []; + $offset = array_key_exists('start', $limitOffset) ? $limitOffset['start'] : 0; + foreach ($this->limit(1, $offset) as $record) { + return $record; } return null; } @@ -1423,8 +1428,14 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li */ public function last() { - foreach ($this->dataQuery->lastRow()->execute() as $row) { - return $this->createDataObject($row); + // We need to trigger eager loading by iterating over the list, rather than just fetching + // the last row from the dataQuery. + // This limit and offset logic mimics that $this->dataQuery->lastRow() would ultimately do. + $limitOffset = $this->dataQuery->query()->getLimit() ?? []; + $offset = array_key_exists('start', $limitOffset) ? $limitOffset['start'] : 0; + $index = max($this->count() + $offset - 1, 0); + foreach ($this->limit(1, $index) as $record) { + return $record; } return null; } diff --git a/tests/php/ORM/DataListEagerLoadingTest.php b/tests/php/ORM/DataListEagerLoadingTest.php index 256daec69..380f86c29 100644 --- a/tests/php/ORM/DataListEagerLoadingTest.php +++ b/tests/php/ORM/DataListEagerLoadingTest.php @@ -4,6 +4,7 @@ namespace SilverStripe\ORM\Tests; use InvalidArgumentException; use SilverStripe\Dev\SapphireTest; +use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DB; @@ -1115,4 +1116,22 @@ class DataListEagerLoadingTest extends SapphireTest ], ]; } + + public function testFirstHasEagerloadedRelation() + { + $record = EagerLoadObject::create(['Title' => 'My obj']); + $record->write(); + $record->HasManyEagerLoadObjects()->add(HasManyEagerLoadObject::create(['Title' => 'My related obj'])); + $obj = EagerLoadObject::get()->eagerLoad('HasManyEagerLoadObjects')->first(); + $this->assertInstanceOf(ArrayList::class, $obj->HasManyEagerLoadObjects()); + } + + public function testLastHasEagerloadedRelation() + { + $record = EagerLoadObject::create(['Title' => 'My obj']); + $record->write(); + $record->HasManyEagerLoadObjects()->add(HasManyEagerLoadObject::create(['Title' => 'My related obj'])); + $obj = EagerLoadObject::get()->eagerLoad('HasManyEagerLoadObjects')->last(); + $this->assertInstanceOf(ArrayList::class, $obj->HasManyEagerLoadObjects()); + } } diff --git a/tests/php/ORM/DataListTest.php b/tests/php/ORM/DataListTest.php index 20ded987e..5d663d148 100755 --- a/tests/php/ORM/DataListTest.php +++ b/tests/php/ORM/DataListTest.php @@ -2081,6 +2081,28 @@ class DataListTest extends SapphireTest ], $productTitles); } + public function testFirst() + { + $list = Sortable::get()->sort('Sort'); + $this->assertGreaterThanOrEqual( + 3, + $list->count(), + 'We must have at least 3 items for this test to be valid' + ); + $this->assertSame('Steve', $list->first()->Name); + } + + public function testLast() + { + $list = Sortable::get()->sort('Sort'); + $this->assertGreaterThanOrEqual( + 3, + $list->count(), + 'We must have at least 3 items for this test to be valid' + ); + $this->assertSame('John', $list->last()->Name); + } + public function testChunkedFetch() { $expectedIDs = Team::get()->map('ID', 'ID')->toArray();