diff --git a/src/Entries/EntryRepository.php b/src/Entries/EntryRepository.php index 4a3e00c6..f05045e7 100644 --- a/src/Entries/EntryRepository.php +++ b/src/Entries/EntryRepository.php @@ -6,6 +6,7 @@ use Statamic\Contracts\Entries\QueryBuilder; use Statamic\Eloquent\Jobs\UpdateCollectionEntryOrder; use Statamic\Eloquent\Jobs\UpdateCollectionEntryParent; +use Statamic\Entries\EntryCollection; use Statamic\Facades\Blink; use Statamic\Stache\Repositories\EntryRepository as StacheRepository; @@ -51,6 +52,30 @@ public function findByUri(string $uri, ?string $site = null): ?EntryContract return $this->substitutionsById[$item->id()] ?? $item; } + public function findByIds($ids): EntryCollection + { + $cached = collect($ids)->flip()->map(fn ($_, $id) => Blink::get("eloquent-entry-{$id}")); + $missingIds = $cached->reject()->keys(); + + $missingById = $this->query() + ->whereIn('id', $missingIds) + ->get() + ->keyBy->id(); + + $missingById->each(function ($entry, $id) { + Blink::put("eloquent-entry-{$id}", $entry); + }); + + $items = $cached + ->map(fn ($entry, $id) => $entry ?? $missingById->get($id)) + ->filter() + ->values(); + + $this->applySubstitutions($items); + + return EntryCollection::make($items); + } + public function save($entry) { $model = $entry->toModel(); diff --git a/tests/Entries/EntryRepositoryTest.php b/tests/Entries/EntryRepositoryTest.php index c874376b..e2f6b221 100644 --- a/tests/Entries/EntryRepositoryTest.php +++ b/tests/Entries/EntryRepositoryTest.php @@ -4,11 +4,14 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Event; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; use Statamic\Eloquent\Entries\Entry; use Statamic\Eloquent\Entries\EntryModel; use Statamic\Eloquent\Entries\EntryRepository; +use Statamic\Entries\EntryCollection; use Statamic\Events\CollectionTreeSaved; +use Statamic\Facades\Blink; use Statamic\Facades\Collection; use Statamic\Stache\Stache; use Tests\TestCase; @@ -234,4 +237,73 @@ public function it_updates_the_parents_of_specific_entries_in_a_collection() 4 => null, ], EntryModel::all()->mapWithKeys(fn ($e) => [$e->id => $e->data['parent'] ?? null])->all()); } + + #[Test, Group('EntryRepository#findByIds')] + public function it_gets_entries_by_ids() + { + $collection = Collection::make('pages')->routes('{slug}')->save(); + $expected = collect([ + (new Entry)->collection($collection)->slug('foo'), + (new Entry)->collection($collection)->slug('bar'), + ])->each->save(); + + $actual = (new EntryRepository(new Stache))->findByIds($expected->map->id()); + + $this->assertInstanceOf(EntryCollection::class, $actual); + $this->assertEquals($expected->map->id()->all(), $actual->map->id()->all()); + } + + #[Test, Group('EntryRepository#findByIds')] + public function it_loads_entries_from_database_given_partial_cache_when_finding_by_ids() + { + $collection = Collection::make('pages')->routes('{slug}')->save(); + $expected = collect([ + (new Entry)->collection($collection)->slug('foo'), + (new Entry)->collection($collection)->slug('bar'), + ]); + + $expected->first()->save(); + Blink::flush(); + $expected->last()->save(); + + $actual = (new EntryRepository(new Stache))->findByIds($expected->map->id()); + + $this->assertNotNull($expected->first()->id()); + $this->assertNotSame($expected->first(), $actual->first()); + $this->assertEquals($expected->first()->id(), $actual->first()->id()); + $this->assertNotNull($actual->last()); + $this->assertSame($expected->last(), $actual->last()); + } + + #[Test, Group('EntryRepository#findByIds')] + public function it_returns_entries_in_exact_order_when_finding_by_ids() + { + $collection = Collection::make('pages')->routes('{slug}')->save(); + $entries = collect([ + (new Entry)->collection($collection)->slug('foo'), + (new Entry)->collection($collection)->slug('bar'), + (new Entry)->collection($collection)->slug('baz'), + ])->each->save(); + + Blink::flush(); + + $expected = collect([2, 0, 1])->map(fn ($index) => $entries[$index]->id())->all(); + $actual = (new EntryRepository(new Stache))->findByIds($expected); + + $this->assertEquals($expected, $actual->map->id()->all()); + } + + #[Test, Group('EntryRepository#findByIds')] + public function it_skips_missing_entires_when_finding_by_ids() + { + $collection = Collection::make('pages')->routes('{slug}')->save(); + $expected = tap((new Entry)->collection($collection)->slug('foo'))->save(); + + $actual = (new EntryRepository(new Stache))->findByIds([ + $expected->id(), + 'missing', + ]); + + $this->assertEquals([$expected->id()], $actual->map->id()->all()); + } }