From ed4bbec4ee8dd93892a620a0cece9e3898ba05f2 Mon Sep 17 00:00:00 2001 From: Daniel Weaver Date: Tue, 2 Apr 2024 16:54:06 -0400 Subject: [PATCH 01/12] Initial attempt at entry builder updateOrCreate --- src/Stache/Query/EntryQueryBuilder.php | 37 +++++++++++++++++++ tests/Data/Entries/EntryQueryBuilderTest.php | 38 ++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/src/Stache/Query/EntryQueryBuilder.php b/src/Stache/Query/EntryQueryBuilder.php index fbf92cd17e..108ab6ab69 100644 --- a/src/Stache/Query/EntryQueryBuilder.php +++ b/src/Stache/Query/EntryQueryBuilder.php @@ -5,6 +5,7 @@ use Statamic\Contracts\Entries\QueryBuilder; use Statamic\Entries\EntryCollection; use Statamic\Facades; +use Statamic\Facades\Entry; use Statamic\Support\Arr; class EntryQueryBuilder extends Builder implements QueryBuilder @@ -148,4 +149,40 @@ public function prepareForFakeQuery(): array return $data; } + + public function firstOrCreate(array $attributes = [], array $values = []) + { + if (!is_null($instance = (clone $this)->where($attributes)->first())) { + return $instance; + } + + return $this->createOrFirst($attributes, $values); + } + + public function createOrFirst(array $attributes = [], array $values = []) + { + try { + $entry = Entry::make(); + if (!isset($attributes['collection']) && empty($this->collections)) { + return null; + } + + $entry->collection($attributes['collection'] ?? $this->collections[0] ?? null); + + $entry->data($attributes)->merge($values); + $entry->save(); + return $entry; + } catch (\Exception $e) { + return $this->where($attributes)->first(); + } + } + + public function updateOrCreate(array $attributes, array $values = []) + { + return tap($this->firstOrCreate($attributes, $values), function ($instance) use ($values) { + if (!$instance->wasRecentlyCreated) { + $instance->merge($values)->save(); + } + }); + } } diff --git a/tests/Data/Entries/EntryQueryBuilderTest.php b/tests/Data/Entries/EntryQueryBuilderTest.php index 54a060c850..57a43ac692 100644 --- a/tests/Data/Entries/EntryQueryBuilderTest.php +++ b/tests/Data/Entries/EntryQueryBuilderTest.php @@ -803,4 +803,42 @@ public function values_can_be_plucked() 'thing-2', ], Entry::query()->where('type', 'b')->pluck('slug')->all()); } + + /** @test */ + public function entries_can_be_found_or_created_or_updated() + { + $this->createDummyCollectionAndEntries(); + + // Create new entry + $entry = Entry::query() + ->where('collection', 'posts') + ->firstOrCreate( + ['id' => 'id-1'], + ['title' => 'Post 1'], + ); + $this->assertEquals('Post 1', $entry->title); + + // Get the first entry if it already exists + $entry = Entry::query() + ->where('collection', 'posts') + ->firstOrCreate( + ['id' => 'id-1'], + ['title' => 'Post 2'], + ); + $this->assertEquals('Post 1', $entry->title); + + // Create new entry + $entry = Entry::query()->updateOrCreate( + ['id' => 'id-2'], + ['title' => 'Post 2'], + ); + $this->assertEquals('Post 2', $entry->title); + + // Only update the entry if it already exists + $entry = Entry::query()->updateOrCreate( + ['id' => 'id-1'], + ['title' => 'Post 1 - Updated'], + ); + $this->assertEquals('Post 1 - Updated', $entry->title); + } } From 0a0b049904cf9c11e34214ea44e1e55b85b1802c Mon Sep 17 00:00:00 2001 From: Daniel Weaver Date: Wed, 3 Apr 2024 11:08:12 -0400 Subject: [PATCH 02/12] Adding methods to entry facade --- src/Contracts/Entries/EntryRepository.php | 6 ++++++ src/Facades/Entry.php | 3 +++ src/Stache/Repositories/EntryRepository.php | 15 +++++++++++++++ tests/Data/Entries/EntryQueryBuilderTest.php | 8 ++++---- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/Contracts/Entries/EntryRepository.php b/src/Contracts/Entries/EntryRepository.php index c306eba1a5..7065845cab 100644 --- a/src/Contracts/Entries/EntryRepository.php +++ b/src/Contracts/Entries/EntryRepository.php @@ -18,6 +18,12 @@ public function findByUri(string $uri); public function make(); + public function firstOrCreate(array $attributes, array $values = []); + + public function createOrFirst(array $attributes, array $values = []); + + public function updateOrCreate(array $attributes, array $values = []); + public function query(); public function save($entry); diff --git a/src/Facades/Entry.php b/src/Facades/Entry.php index 9453af9bd5..d6ce77c787 100644 --- a/src/Facades/Entry.php +++ b/src/Facades/Entry.php @@ -13,6 +13,9 @@ * @method static \Statamic\Contracts\Entries\Entry findOrFail($id) * @method static null|\Statamic\Contracts\Entries\Entry findByUri(string $uri, string $site) * @method static \Statamic\Contracts\Entries\Entry make() + * @method static \Statamic\Contracts\Entries\Entry firstOrCreate() + * @method static \Statamic\Contracts\Entries\Entry createOrFirst() + * @method static \Statamic\Contracts\Entries\Entry updateOrCreate() * @method static \Statamic\Contracts\Entries\QueryBuilder query() * @method static void save($entry) * @method static void delete($entry) diff --git a/src/Stache/Repositories/EntryRepository.php b/src/Stache/Repositories/EntryRepository.php index be2d678544..6ef513992b 100644 --- a/src/Stache/Repositories/EntryRepository.php +++ b/src/Stache/Repositories/EntryRepository.php @@ -116,6 +116,21 @@ public function make(): Entry return app(Entry::class); } + public function firstOrCreate(array $attributes, array $values = []) + { + return $this->query()->firstOrCreate($attributes, $values); + } + + public function createOrFirst(array $attributes, array $values = []) + { + return $this->query()->createOrFirst($attributes, $values); + } + + public function updateOrCreate(array $attributes, array $values = []) + { + return $this->query()->updateOrCreate($attributes, $values); + } + public function taxonomize($entry) { $entry->collection()->taxonomies()->each(function ($taxonomy) use ($entry) { diff --git a/tests/Data/Entries/EntryQueryBuilderTest.php b/tests/Data/Entries/EntryQueryBuilderTest.php index 57a43ac692..8781796236 100644 --- a/tests/Data/Entries/EntryQueryBuilderTest.php +++ b/tests/Data/Entries/EntryQueryBuilderTest.php @@ -828,15 +828,15 @@ public function entries_can_be_found_or_created_or_updated() $this->assertEquals('Post 1', $entry->title); // Create new entry - $entry = Entry::query()->updateOrCreate( - ['id' => 'id-2'], + $entry = Entry::updateOrCreate( + ['id' => 'id-2', 'collection' => 'posts'], ['title' => 'Post 2'], ); $this->assertEquals('Post 2', $entry->title); // Only update the entry if it already exists - $entry = Entry::query()->updateOrCreate( - ['id' => 'id-1'], + $entry = Entry::updateOrCreate( + ['id' => 'id-1', 'collection' => 'posts'], ['title' => 'Post 1 - Updated'], ); $this->assertEquals('Post 1 - Updated', $entry->title); From daaca7cb541d52d1042a7e1bede9c1adfe8535db Mon Sep 17 00:00:00 2001 From: Daniel Weaver Date: Fri, 5 Apr 2024 15:46:17 -0400 Subject: [PATCH 03/12] Cleaning up Entry implementations and adding Term implementations --- src/Contracts/Entries/EntryRepository.php | 4 +- src/Contracts/Taxonomies/TermRepository.php | 6 ++ src/Facades/Entry.php | 6 +- src/Facades/Term.php | 3 + src/Stache/Query/EntryQueryBuilder.php | 46 +++++++------- src/Stache/Query/TermQueryBuilder.php | 44 ++++++++++++++ src/Stache/Repositories/EntryRepository.php | 10 ++-- src/Stache/Repositories/TermRepository.php | 15 +++++ tests/Data/Entries/EntryQueryBuilderTest.php | 60 +++++++++++++------ .../Data/Taxonomies/TermQueryBuilderTest.php | 52 ++++++++++++++++ 10 files changed, 198 insertions(+), 48 deletions(-) diff --git a/src/Contracts/Entries/EntryRepository.php b/src/Contracts/Entries/EntryRepository.php index 7065845cab..86a9d7ead0 100644 --- a/src/Contracts/Entries/EntryRepository.php +++ b/src/Contracts/Entries/EntryRepository.php @@ -18,9 +18,9 @@ public function findByUri(string $uri); public function make(); - public function firstOrCreate(array $attributes, array $values = []); + public function firstOrNew(array $attributes, array $values = []); - public function createOrFirst(array $attributes, array $values = []); + public function firstOrCreate(array $attributes, array $values = []); public function updateOrCreate(array $attributes, array $values = []); diff --git a/src/Contracts/Taxonomies/TermRepository.php b/src/Contracts/Taxonomies/TermRepository.php index df0c4919b0..9eb74b3eb4 100644 --- a/src/Contracts/Taxonomies/TermRepository.php +++ b/src/Contracts/Taxonomies/TermRepository.php @@ -18,6 +18,12 @@ public function findOrFail($id); public function make(?string $slug = null); + public function firstOrNew(array $attributes, array $values = []); + + public function firstOrCreate(array $attributes, array $values = []); + + public function updateOrCreate(array $attributes, array $values = []); + public function query(); public function save($entry); diff --git a/src/Facades/Entry.php b/src/Facades/Entry.php index d6ce77c787..7613f8928c 100644 --- a/src/Facades/Entry.php +++ b/src/Facades/Entry.php @@ -13,9 +13,9 @@ * @method static \Statamic\Contracts\Entries\Entry findOrFail($id) * @method static null|\Statamic\Contracts\Entries\Entry findByUri(string $uri, string $site) * @method static \Statamic\Contracts\Entries\Entry make() - * @method static \Statamic\Contracts\Entries\Entry firstOrCreate() - * @method static \Statamic\Contracts\Entries\Entry createOrFirst() - * @method static \Statamic\Contracts\Entries\Entry updateOrCreate() + * @method static \Statamic\Contracts\Entries\Entry firstOrNew(array $attributes, array $values) + * @method static \Statamic\Contracts\Entries\Entry firstOrCreate(array $attributes, array $values) + * @method static \Statamic\Contracts\Entries\Entry updateOrCreate(array $attributes, array $values) * @method static \Statamic\Contracts\Entries\QueryBuilder query() * @method static void save($entry) * @method static void delete($entry) diff --git a/src/Facades/Term.php b/src/Facades/Term.php index b7c343ef87..94924a839b 100644 --- a/src/Facades/Term.php +++ b/src/Facades/Term.php @@ -18,6 +18,9 @@ * @method static delete($term) * @method static TermQueryBuilder query() * @method static TermContract make(string $slug = null) + * @method static TermContract firstOrNew(array $attributes, array $values) + * @method static TermContract firstOrCreate(array $attributes, array $values) + * @method static TermContract updateOrCreate(array $attributes, array $values) * @method static int entriesCount(Term $term) * * @see \Statamic\Contracts\Taxonomies\TermRepository diff --git a/src/Stache/Query/EntryQueryBuilder.php b/src/Stache/Query/EntryQueryBuilder.php index 108ab6ab69..5e934d0d36 100644 --- a/src/Stache/Query/EntryQueryBuilder.php +++ b/src/Stache/Query/EntryQueryBuilder.php @@ -150,39 +150,45 @@ public function prepareForFakeQuery(): array return $data; } - public function firstOrCreate(array $attributes = [], array $values = []) + public function firstOrNew(array $attributes = [], array $values = []) { - if (!is_null($instance = (clone $this)->where($attributes)->first())) { + if (! is_null($instance = $this->where($attributes)->first())) { return $instance; } - return $this->createOrFirst($attributes, $values); + /** @var \Statamic\Entries\Entry */ + $entry = Entry::make(); + $data = array_merge($attributes, $values); + $entry->collection($this->collections[0] ?? $data['collection'] ?? null); + $entry->slug($data['slug'] ?? null); + $entry->merge($data); + + return $entry; } - public function createOrFirst(array $attributes = [], array $values = []) + public function firstOrCreate(array $attributes = [], array $values = []) { - try { - $entry = Entry::make(); - if (!isset($attributes['collection']) && empty($this->collections)) { - return null; - } - - $entry->collection($attributes['collection'] ?? $this->collections[0] ?? null); + $entry = $this->firstOrNew($attributes, $values); - $entry->data($attributes)->merge($values); + // If the entry is dirty, then it's new and needs to be saved + if ($entry->isDirty()) { $entry->save(); - return $entry; - } catch (\Exception $e) { - return $this->where($attributes)->first(); } + + return $entry; } public function updateOrCreate(array $attributes, array $values = []) { - return tap($this->firstOrCreate($attributes, $values), function ($instance) use ($values) { - if (!$instance->wasRecentlyCreated) { - $instance->merge($values)->save(); - } - }); + $entry = $this->firstOrNew($attributes, $values); + + // If the entry is not dirty, then it already exists and this is an update + if (! $entry->isDirty()) { + $entry->merge($values); + } + + $entry->save(); + + return $entry; } } diff --git a/src/Stache/Query/TermQueryBuilder.php b/src/Stache/Query/TermQueryBuilder.php index deda55b377..92a8cb0fc9 100644 --- a/src/Stache/Query/TermQueryBuilder.php +++ b/src/Stache/Query/TermQueryBuilder.php @@ -4,7 +4,9 @@ use Statamic\Facades; use Statamic\Facades\Collection; +use Statamic\Facades\Term; use Statamic\Support\Arr; +use Statamic\Taxonomies\LocalizedTerm; use Statamic\Taxonomies\TermCollection; class TermQueryBuilder extends Builder @@ -202,4 +204,46 @@ public function prepareForFakeQuery(): array return $data; } + + public function firstOrNew(array $attributes = [], array $values = []) + { + if (! is_null($instance = $this->where($attributes)->first())) { + return $instance; + } + + /** @var \Statamic\Taxonomies\LocalizedTerm */ + $term = Term::make(); + $data = array_merge($attributes, $values); + $term->taxonomy($this->taxonomies[0] ?? $data['taxonomy'] ?? null); + $term->slug($data['slug'] ?? null); + $term->merge($data); + + return $term; + } + + public function firstOrCreate(array $attributes = [], array $values = []) + { + $term = $this->firstOrNew($attributes, $values); + + // If the term does not exist then it needs to be saved + if (! ($term instanceof LocalizedTerm)) { + $term->save(); + } + + return $term; + } + + public function updateOrCreate(array $attributes, array $values = []) + { + $term = $this->firstOrNew($attributes, $values); + + // If the term already exists and the values need to be updated + if ($term instanceof LocalizedTerm) { + $term->merge($values); + } + + $term->save(); + + return $term; + } } diff --git a/src/Stache/Repositories/EntryRepository.php b/src/Stache/Repositories/EntryRepository.php index 6ef513992b..fbe54a5022 100644 --- a/src/Stache/Repositories/EntryRepository.php +++ b/src/Stache/Repositories/EntryRepository.php @@ -116,14 +116,14 @@ public function make(): Entry return app(Entry::class); } - public function firstOrCreate(array $attributes, array $values = []) + public function firstOrNew(array $attributes = [], array $values = []) { - return $this->query()->firstOrCreate($attributes, $values); + return $this->query()->firstOrNew($attributes, $values); } - public function createOrFirst(array $attributes, array $values = []) + public function firstOrCreate(array $attributes, array $values = []) { - return $this->query()->createOrFirst($attributes, $values); + return $this->query()->firstOrCreate($attributes, $values); } public function updateOrCreate(array $attributes, array $values = []) @@ -167,7 +167,7 @@ public static function bindings(): array public function substitute($item) { $this->substitutionsById[$item->id()] = $item; - $this->substitutionsByUri[$item->locale().'@'.$item->uri()] = $item; + $this->substitutionsByUri[$item->locale() . '@' . $item->uri()] = $item; } public function applySubstitutions($items) diff --git a/src/Stache/Repositories/TermRepository.php b/src/Stache/Repositories/TermRepository.php index 2c0ec7e227..e7f8448d62 100644 --- a/src/Stache/Repositories/TermRepository.php +++ b/src/Stache/Repositories/TermRepository.php @@ -138,6 +138,21 @@ public function make(?string $slug = null): Term return app(Term::class)->slug($slug); } + public function firstOrNew(array $attributes, array $values = []) + { + return $this->query()->firstOrNew($attributes, $values); + } + + public function firstOrCreate(array $attributes, array $values = []) + { + return $this->query()->firstOrCreate($attributes, $values); + } + + public function updateOrCreate(array $attributes, array $values = []) + { + return $this->query()->updateOrCreate($attributes, $values); + } + public function entriesCount(Term $term): int { $items = $this->store->store($term->taxonomyHandle()) diff --git a/tests/Data/Entries/EntryQueryBuilderTest.php b/tests/Data/Entries/EntryQueryBuilderTest.php index 8781796236..916f78f998 100644 --- a/tests/Data/Entries/EntryQueryBuilderTest.php +++ b/tests/Data/Entries/EntryQueryBuilderTest.php @@ -805,40 +805,64 @@ public function values_can_be_plucked() } /** @test */ - public function entries_can_be_found_or_created_or_updated() + public function entries_can_be_found_made_created_or_updated() { - $this->createDummyCollectionAndEntries(); + Collection::make('posts')->save(); + + // Make a new entry without saving it + $entry = Entry::query() + ->where('collection', 'posts') + ->firstOrNew( + ['slug' => 'new-post-1'], + ['title' => 'New Post 1'], + ); + $this->assertEquals('posts', $entry->collectionHandle()); + $this->assertEquals('new-post-1', $entry->slug()); + $this->assertEquals('New Post 1', $entry->get('title')); + $this->assertNull($entry->id()); - // Create new entry + // Create a new entry with id 'new-post-1' $entry = Entry::query() ->where('collection', 'posts') ->firstOrCreate( - ['id' => 'id-1'], - ['title' => 'Post 1'], + ['slug' => 'new-post-1'], + ['title' => 'New Post 1'], ); - $this->assertEquals('Post 1', $entry->title); + $this->assertEquals('posts', $entry->collectionHandle()); + $this->assertEquals('new-post-1', $entry->slug); + $this->assertEquals('New Post 1', $entry->title); + $this->assertNotNull($entry->id()); - // Get the first entry if it already exists + // Get the first entry with id 'new-post-1' $entry = Entry::query() ->where('collection', 'posts') ->firstOrCreate( - ['id' => 'id-1'], - ['title' => 'Post 2'], + ['slug' => 'new-post-1'], + ['title' => 'New Post 1 - Not Created'], ); - $this->assertEquals('Post 1', $entry->title); + $this->assertEquals('posts', $entry->collectionHandle()); + $this->assertEquals('new-post-1', $entry->slug); + $this->assertNotEquals('New Post 1 - Not Created', $entry->title); + $this->assertNotNull($entry->id()); - // Create new entry + // Create a new entry with id 'new-post-2' $entry = Entry::updateOrCreate( - ['id' => 'id-2', 'collection' => 'posts'], - ['title' => 'Post 2'], + ['collection' => 'posts', 'slug' => 'new-post-2'], + ['title' => 'New Post 2'], ); - $this->assertEquals('Post 2', $entry->title); + $this->assertEquals('posts', $entry->collectionHandle()); + $this->assertEquals('new-post-2', $entry->slug); + $this->assertEquals('New Post 2', $entry->title); + $this->assertNotNull($entry->id()); - // Only update the entry if it already exists + // Update the entry with id 'new-post-2' $entry = Entry::updateOrCreate( - ['id' => 'id-1', 'collection' => 'posts'], - ['title' => 'Post 1 - Updated'], + ['collection' => 'posts', 'slug' => 'new-post-2'], + ['title' => 'New Post 2 - Updated'], ); - $this->assertEquals('Post 1 - Updated', $entry->title); + $this->assertEquals('posts', $entry->collectionHandle()); + $this->assertEquals('new-post-2', $entry->slug); + $this->assertEquals('New Post 2 - Updated', $entry->title); + $this->assertNotNull($entry->id()); } } diff --git a/tests/Data/Taxonomies/TermQueryBuilderTest.php b/tests/Data/Taxonomies/TermQueryBuilderTest.php index 2a0c0ce108..8a8e0046ee 100644 --- a/tests/Data/Taxonomies/TermQueryBuilderTest.php +++ b/tests/Data/Taxonomies/TermQueryBuilderTest.php @@ -601,4 +601,56 @@ public function terms_are_found_using_offset() $terms = Term::query()->offset(1)->get(); $this->assertEquals(['b', 'c'], $terms->map->slug()->all()); } + + /** @test */ + public function terms_can_be_found_made_created_or_updated() + { + Taxonomy::make('tags')->save(); + + // Make a new term + $term = Term::query() + ->where('taxonomy', 'tags') + ->firstOrNew( + ['slug' => 'term-1'], + ['title' => 'Term 1'], + ); + $this->assertEquals('Term 1', $term->title()); + $this->assertNull(Term::find('term-1')); + + // Create a new term + $term = Term::query() + ->where('taxonomy', 'tags') + ->firstOrCreate( + ['slug' => 'term-1'], + ['title' => 'Term 1'], + ); + $this->assertEquals('Term 1', $term->title()); + $this->assertNotNull(Term::find('tags::term-1')); + + // Get the first term + $term = Term::query() + ->where('taxonomy', 'tags') + ->firstOrCreate( + ['slug' => 'term-1'], + ['title' => 'Term 1 - Updated'], + ); + $this->assertNotEquals('Term 1 - Updated', $term->title()); + $this->assertNotNull(Term::find('tags::term-1')); + + // Create a new term + $term = Term::updateOrCreate( + ['slug' => 'term-2', 'taxonomy' => 'tags'], + ['title' => 'Term 2'], + ); + $this->assertEquals('Term 2', $term->title()); + $this->assertNotNull(Term::find('tags::term-2')); + + // Update the term + $term = Term::updateOrCreate( + ['slug' => 'term-2', 'taxonomy' => 'tags'], + ['title' => 'Term 2 - Updated'], + ); + $this->assertEquals('Term 2 - Updated', $term->title()); + $this->assertNotNull(Term::find('tags::term-2')); + } } From 6f9082f1c81595097a0df56bdb3977202f8610f6 Mon Sep 17 00:00:00 2001 From: Daniel Weaver Date: Fri, 5 Apr 2024 15:50:39 -0400 Subject: [PATCH 04/12] Code style --- src/Stache/Repositories/EntryRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Stache/Repositories/EntryRepository.php b/src/Stache/Repositories/EntryRepository.php index fbe54a5022..d82a60b07c 100644 --- a/src/Stache/Repositories/EntryRepository.php +++ b/src/Stache/Repositories/EntryRepository.php @@ -167,7 +167,7 @@ public static function bindings(): array public function substitute($item) { $this->substitutionsById[$item->id()] = $item; - $this->substitutionsByUri[$item->locale() . '@' . $item->uri()] = $item; + $this->substitutionsByUri[$item->locale().'@'.$item->uri()] = $item; } public function applySubstitutions($items) From 6006a2ca1bc3404f5f1891bae1b2a43a3a90d8b9 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Mon, 15 Apr 2024 19:08:58 +0100 Subject: [PATCH 05/12] split tests for easier reviewing --- tests/Data/Entries/EntryQueryBuilderTest.php | 133 ++++++++++++------ .../Data/Taxonomies/TermQueryBuilderTest.php | 105 ++++++++++++++ 2 files changed, 193 insertions(+), 45 deletions(-) diff --git a/tests/Data/Entries/EntryQueryBuilderTest.php b/tests/Data/Entries/EntryQueryBuilderTest.php index e030545d2a..dcf4c24435 100644 --- a/tests/Data/Entries/EntryQueryBuilderTest.php +++ b/tests/Data/Entries/EntryQueryBuilderTest.php @@ -895,64 +895,107 @@ public function values_can_be_plucked() } /** @test */ - public function entries_can_be_found_made_created_or_updated() + public function entry_can_be_found_using_first_or_new() { Collection::make('posts')->save(); + $entry = EntryFactory::collection('posts')->slug('david-hasselhoff')->data(['title' => 'David Hasselhoff'])->create(); - // Make a new entry without saving it - $entry = Entry::query() + $firstOrNew = Entry::query() ->where('collection', 'posts') ->firstOrNew( - ['slug' => 'new-post-1'], - ['title' => 'New Post 1'], + ['slug' => 'david-hasselhoff'], + ['title' => 'David Hasselhoff'] ); - $this->assertEquals('posts', $entry->collectionHandle()); - $this->assertEquals('new-post-1', $entry->slug()); - $this->assertEquals('New Post 1', $entry->get('title')); - $this->assertNull($entry->id()); - // Create a new entry with id 'new-post-1' - $entry = Entry::query() + $this->assertSame($entry, $firstOrNew); + } + + /** @test */ + public function entry_can_be_created_using_first_or_new() + { + Collection::make('posts')->save(); + + $firstOrNew = Entry::query() + ->where('collection', 'posts') + ->firstOrNew( + ['slug' => 'david-hasselhoff'], + ['title' => 'David Hasselhoff'] + ); + + $this->assertNull($firstOrNew->id()); + $this->assertSame('David Hasselhoff', $firstOrNew->title); + $this->assertSame('david-hasselhoff', $firstOrNew->slug()); + $this->assertSame('posts', $firstOrNew->collection()->handle()); + } + + /** @test */ + public function entry_can_be_found_using_first_or_create() + { + Collection::make('posts')->save(); + $entry = EntryFactory::collection('posts')->slug('david-hasselhoff')->data(['title' => 'David Hasselhoff'])->create(); + + $firstOrCreate = Entry::query() ->where('collection', 'posts') ->firstOrCreate( - ['slug' => 'new-post-1'], - ['title' => 'New Post 1'], + ['slug' => 'david-hasselhoff'], + ['title' => 'David Hasselhoff'] ); - $this->assertEquals('posts', $entry->collectionHandle()); - $this->assertEquals('new-post-1', $entry->slug); - $this->assertEquals('New Post 1', $entry->title); - $this->assertNotNull($entry->id()); - // Get the first entry with id 'new-post-1' - $entry = Entry::query() + $this->assertSame($entry, $firstOrCreate); + } + + /** @test */ + public function entry_can_be_created_using_first_or_create() + { + Collection::make('posts')->save(); + + $firstOrCreate = Entry::query() ->where('collection', 'posts') ->firstOrCreate( - ['slug' => 'new-post-1'], - ['title' => 'New Post 1 - Not Created'], + ['slug' => 'david-hasselhoff'], + ['title' => 'David Hasselhoff'] + ); + + $this->assertNotNull($firstOrCreate->id()); + $this->assertSame('David Hasselhoff', $firstOrCreate->title); + $this->assertSame('david-hasselhoff', $firstOrCreate->slug()); + $this->assertSame('posts', $firstOrCreate->collection()->handle()); + } + + /** @test */ + public function entry_can_be_found_and_updated_using_update_or_create() + { + Collection::make('posts')->save(); + $entry = EntryFactory::collection('posts')->slug('david-hasselhoff')->data(['title' => 'David Hasselhoff'])->create(); + + $updateOrCreate = Entry::query() + ->where('collection', 'posts') + ->updateOrCreate( + ['slug' => 'david-hasselhoff'], + ['title' => 'The Hoff'] ); - $this->assertEquals('posts', $entry->collectionHandle()); - $this->assertEquals('new-post-1', $entry->slug); - $this->assertNotEquals('New Post 1 - Not Created', $entry->title); - $this->assertNotNull($entry->id()); - - // Create a new entry with id 'new-post-2' - $entry = Entry::updateOrCreate( - ['collection' => 'posts', 'slug' => 'new-post-2'], - ['title' => 'New Post 2'], - ); - $this->assertEquals('posts', $entry->collectionHandle()); - $this->assertEquals('new-post-2', $entry->slug); - $this->assertEquals('New Post 2', $entry->title); - $this->assertNotNull($entry->id()); - - // Update the entry with id 'new-post-2' - $entry = Entry::updateOrCreate( - ['collection' => 'posts', 'slug' => 'new-post-2'], - ['title' => 'New Post 2 - Updated'], - ); - $this->assertEquals('posts', $entry->collectionHandle()); - $this->assertEquals('new-post-2', $entry->slug); - $this->assertEquals('New Post 2 - Updated', $entry->title); - $this->assertNotNull($entry->id()); + + $this->assertSame($entry->id(), $updateOrCreate->id()); + $this->assertSame('The Hoff', $updateOrCreate->title); + $this->assertSame('david-hasselhoff', $updateOrCreate->slug()); + $this->assertSame('posts', $updateOrCreate->collection()->handle()); + } + + /** @test */ + public function entry_can_be_created_using_update_or_create() + { + Collection::make('posts')->save(); + + $updateOrCreate = Entry::query() + ->where('collection', 'posts') + ->updateOrCreate( + ['slug' => 'david-hasselhoff'], + ['title' => 'The Hoff'] + ); + + $this->assertNotNull($updateOrCreate->id()); + $this->assertSame('The Hoff', $updateOrCreate->title); + $this->assertSame('david-hasselhoff', $updateOrCreate->slug()); + $this->assertSame('posts', $updateOrCreate->collection()->handle()); } } diff --git a/tests/Data/Taxonomies/TermQueryBuilderTest.php b/tests/Data/Taxonomies/TermQueryBuilderTest.php index 5229f8cb99..d3b8065db6 100644 --- a/tests/Data/Taxonomies/TermQueryBuilderTest.php +++ b/tests/Data/Taxonomies/TermQueryBuilderTest.php @@ -653,4 +653,109 @@ public function terms_can_be_found_made_created_or_updated() $this->assertEquals('Term 2 - Updated', $term->title()); $this->assertNotNull(Term::find('tags::term-2')); } + + /** @test */ + public function term_can_be_found_using_first_or_new() + { + Taxonomy::make('tags')->save(); + $term = tap(Term::make()->taxonomy('tags')->inDefaultLocale()->slug('alfa')->data(['title' => 'Alfa']))->save(); + + $firstOrNew = Term::query() + ->where('taxonomy', 'tags') + ->firstOrNew( + ['slug' => 'alfa'], + ['title' => 'Alfa'] + ); + + $this->assertEquals($term->slug(), $firstOrNew->slug()); + } + + /** @test */ + public function term_can_be_created_using_first_or_new() + { + Taxonomy::make('tags')->save(); + + $firstOrNew = Term::query() + ->where('taxonomy', 'tags') + ->firstOrNew( + ['slug' => 'alfa'], + ['title' => 'Alfa'] + ); + + $this->assertNull(Term::find('tags::alfa')); + $this->assertSame('Alfa', $firstOrNew->get('title')); + $this->assertSame('alfa', $firstOrNew->slug()); + $this->assertSame('tags', $firstOrNew->taxonomy()->handle()); + } + + /** @test */ + public function term_can_be_found_using_first_or_create() + { + Taxonomy::make('tags')->save(); + $term = tap(Term::make()->taxonomy('tags')->inDefaultLocale()->slug('alfa')->data(['title' => 'Alfa']))->save(); + + $firstOrCreate = Term::query() + ->where('taxonomy', 'tags') + ->firstOrCreate( + ['slug' => 'alfa'], + ['title' => 'Alfa'] + ); + + $this->assertEquals($term->slug(), $firstOrCreate->slug()); + } + + /** @test */ + public function term_can_be_created_using_first_or_create() + { + Taxonomy::make('tags')->save(); + + $firstOrCreate = Term::query() + ->where('taxonomy', 'tags') + ->firstOrCreate( + ['slug' => 'alfa'], + ['title' => 'Alfa'] + ); + + $this->assertNotNull(Term::find('tags::alfa')); + $this->assertSame('Alfa', $firstOrCreate->get('title')); + $this->assertSame('alfa', $firstOrCreate->slug()); + $this->assertSame('tags', $firstOrCreate->taxonomy()->handle()); + } + + /** @test */ + public function term_can_be_found_and_updated_using_update_or_create() + { + Taxonomy::make('tags')->save(); + $term = tap(Term::make()->taxonomy('tags')->inDefaultLocale()->slug('alfa')->data(['title' => 'Alfa']))->save(); + + $updateOrCreate = Term::query() + ->where('taxonomy', 'tags') + ->updateOrCreate( + ['slug' => 'alfa'], + ['title' => 'Alfa - Updated'] + ); + + $this->assertEquals($term->slug(), $updateOrCreate->slug()); + $this->assertSame('Alfa - Updated', $updateOrCreate->title); + $this->assertSame('alfa', $updateOrCreate->slug()); + $this->assertSame('tags', $updateOrCreate->taxonomy()->handle()); + } + + /** @test */ + public function term_can_be_created_using_update_or_create() + { + Taxonomy::make('tags')->save(); + + $updateOrCreate = Term::query() + ->where('taxonomy', 'tags') + ->updateOrCreate( + ['slug' => 'alfa'], + ['title' => 'Alfa'] + ); + + $this->assertNotNull(Term::find('tags::alfa')); + $this->assertSame('Alfa', $updateOrCreate->get('title')); + $this->assertSame('alfa', $updateOrCreate->slug()); + $this->assertSame('tags', $updateOrCreate->taxonomy()->handle()); + } } From f45936ff461d1899234e1e1936332b3fe252adb3 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Mon, 15 Apr 2024 19:58:33 +0100 Subject: [PATCH 06/12] cleanup the code a little --- src/Stache/Query/EntryQueryBuilder.php | 28 ++++++++++++++++---------- src/Stache/Query/TermQueryBuilder.php | 25 ++++++++++++++--------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/Stache/Query/EntryQueryBuilder.php b/src/Stache/Query/EntryQueryBuilder.php index 3fd7dd0305..9264b55eca 100644 --- a/src/Stache/Query/EntryQueryBuilder.php +++ b/src/Stache/Query/EntryQueryBuilder.php @@ -5,8 +5,8 @@ use Statamic\Contracts\Entries\QueryBuilder; use Statamic\Entries\EntryCollection; use Statamic\Facades; -use Statamic\Facades\Entry; use Statamic\Facades\Collection; +use Statamic\Facades\Entry; use Statamic\Support\Arr; class EntryQueryBuilder extends Builder implements QueryBuilder @@ -230,21 +230,23 @@ public function firstOrNew(array $attributes = [], array $values = []) return $instance; } - /** @var \Statamic\Entries\Entry */ - $entry = Entry::make(); $data = array_merge($attributes, $values); - $entry->collection($this->collections[0] ?? $data['collection'] ?? null); - $entry->slug($data['slug'] ?? null); - $entry->merge($data); - return $entry; + if (($this->collections && count($this->collections) > 1) && ! isset($data['collection'])) { + throw new \Exception('Please specify a collection.'); + } + + return Entry::make() + ->collection($this->collections[0] ?? $data['collection']) + ->slug($data['slug'] ?? (isset($data['title']) ? Str::slug($data['title']) : null)) + ->data(Arr::except($data, ['collection', 'slug'])); } public function firstOrCreate(array $attributes = [], array $values = []) { $entry = $this->firstOrNew($attributes, $values); - // If the entry is dirty, then it's new and needs to be saved + // When the entry is dirty, it means it's new and should be saved. if ($entry->isDirty()) { $entry->save(); } @@ -256,9 +258,13 @@ public function updateOrCreate(array $attributes, array $values = []) { $entry = $this->firstOrNew($attributes, $values); - // If the entry is not dirty, then it already exists and this is an update - if (! $entry->isDirty()) { - $entry->merge($values); + // When the entry is clean, it means it's existing and should be updated. + if ($entry->isClean()) { + if ($slug = Arr::get($values, 'slug')) { + $entry->slug($slug); + } + + $entry->merge(Arr::except($values, ['slug'])); } $entry->save(); diff --git a/src/Stache/Query/TermQueryBuilder.php b/src/Stache/Query/TermQueryBuilder.php index 92a8cb0fc9..c1ac93a957 100644 --- a/src/Stache/Query/TermQueryBuilder.php +++ b/src/Stache/Query/TermQueryBuilder.php @@ -6,6 +6,7 @@ use Statamic\Facades\Collection; use Statamic\Facades\Term; use Statamic\Support\Arr; +use Statamic\Support\Str; use Statamic\Taxonomies\LocalizedTerm; use Statamic\Taxonomies\TermCollection; @@ -211,22 +212,24 @@ public function firstOrNew(array $attributes = [], array $values = []) return $instance; } - /** @var \Statamic\Taxonomies\LocalizedTerm */ - $term = Term::make(); $data = array_merge($attributes, $values); - $term->taxonomy($this->taxonomies[0] ?? $data['taxonomy'] ?? null); - $term->slug($data['slug'] ?? null); - $term->merge($data); - return $term; + if (($this->taxonomies && count($this->taxonomies) > 1) && ! isset($data['taxonomy'])) { + throw new \Exception('Please specify a taxonomy.'); + } + + return Term::make() + ->taxonomy($this->taxonomies[0] ?? $data['taxonomy']) + ->slug($data['slug'] ?? (isset($data['title']) ? Str::slug($data['title']) : null)) + ->data(Arr::except($data, ['taxonomy', 'slug'])); } public function firstOrCreate(array $attributes = [], array $values = []) { $term = $this->firstOrNew($attributes, $values); - // If the term does not exist then it needs to be saved - if (! ($term instanceof LocalizedTerm)) { + // When the term is not a LocalizedTerm, it means it's newe and should be saved. + if (! $term instanceof LocalizedTerm) { $term->save(); } @@ -237,8 +240,12 @@ public function updateOrCreate(array $attributes, array $values = []) { $term = $this->firstOrNew($attributes, $values); - // If the term already exists and the values need to be updated + // When the term is a LocalizedTerm, it means it's existing and should be updated. if ($term instanceof LocalizedTerm) { + if ($slug = Arr::get($values, 'slug')) { + $term->slug($slug); + } + $term->merge($values); } From dd8d3bb719b2436c3fbe5b6f08ae1480c9d6e7fd Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Mon, 15 Apr 2024 20:03:31 +0100 Subject: [PATCH 07/12] get rid of the old test --- .../Data/Taxonomies/TermQueryBuilderTest.php | 52 ------------------- 1 file changed, 52 deletions(-) diff --git a/tests/Data/Taxonomies/TermQueryBuilderTest.php b/tests/Data/Taxonomies/TermQueryBuilderTest.php index d3b8065db6..05f43d62a5 100644 --- a/tests/Data/Taxonomies/TermQueryBuilderTest.php +++ b/tests/Data/Taxonomies/TermQueryBuilderTest.php @@ -602,58 +602,6 @@ public function terms_are_found_using_offset() $this->assertEquals(['b', 'c'], $terms->map->slug()->all()); } - /** @test */ - public function terms_can_be_found_made_created_or_updated() - { - Taxonomy::make('tags')->save(); - - // Make a new term - $term = Term::query() - ->where('taxonomy', 'tags') - ->firstOrNew( - ['slug' => 'term-1'], - ['title' => 'Term 1'], - ); - $this->assertEquals('Term 1', $term->title()); - $this->assertNull(Term::find('term-1')); - - // Create a new term - $term = Term::query() - ->where('taxonomy', 'tags') - ->firstOrCreate( - ['slug' => 'term-1'], - ['title' => 'Term 1'], - ); - $this->assertEquals('Term 1', $term->title()); - $this->assertNotNull(Term::find('tags::term-1')); - - // Get the first term - $term = Term::query() - ->where('taxonomy', 'tags') - ->firstOrCreate( - ['slug' => 'term-1'], - ['title' => 'Term 1 - Updated'], - ); - $this->assertNotEquals('Term 1 - Updated', $term->title()); - $this->assertNotNull(Term::find('tags::term-1')); - - // Create a new term - $term = Term::updateOrCreate( - ['slug' => 'term-2', 'taxonomy' => 'tags'], - ['title' => 'Term 2'], - ); - $this->assertEquals('Term 2', $term->title()); - $this->assertNotNull(Term::find('tags::term-2')); - - // Update the term - $term = Term::updateOrCreate( - ['slug' => 'term-2', 'taxonomy' => 'tags'], - ['title' => 'Term 2 - Updated'], - ); - $this->assertEquals('Term 2 - Updated', $term->title()); - $this->assertNotNull(Term::find('tags::term-2')); - } - /** @test */ public function term_can_be_found_using_first_or_new() { From f23b77ab5b4567ba49999ee757ed8fc4a7c85192 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Mon, 15 Apr 2024 20:25:37 +0100 Subject: [PATCH 08/12] move next to the other find/query methods --- src/Contracts/Entries/EntryRepository.php | 4 +-- src/Contracts/Taxonomies/TermRepository.php | 4 +-- src/Stache/Repositories/EntryRepository.php | 30 ++++++++++----------- src/Stache/Repositories/TermRepository.php | 30 ++++++++++----------- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/Contracts/Entries/EntryRepository.php b/src/Contracts/Entries/EntryRepository.php index 86a9d7ead0..b8a824329c 100644 --- a/src/Contracts/Entries/EntryRepository.php +++ b/src/Contracts/Entries/EntryRepository.php @@ -16,14 +16,14 @@ public function findOrFail($id); public function findByUri(string $uri); - public function make(); - public function firstOrNew(array $attributes, array $values = []); public function firstOrCreate(array $attributes, array $values = []); public function updateOrCreate(array $attributes, array $values = []); + public function make(); + public function query(); public function save($entry); diff --git a/src/Contracts/Taxonomies/TermRepository.php b/src/Contracts/Taxonomies/TermRepository.php index 9eb74b3eb4..8c251b1028 100644 --- a/src/Contracts/Taxonomies/TermRepository.php +++ b/src/Contracts/Taxonomies/TermRepository.php @@ -16,14 +16,14 @@ public function findByUri(string $uri); public function findOrFail($id); - public function make(?string $slug = null); - public function firstOrNew(array $attributes, array $values = []); public function firstOrCreate(array $attributes, array $values = []); public function updateOrCreate(array $attributes, array $values = []); + public function make(?string $slug = null); + public function query(); public function save($entry); diff --git a/src/Stache/Repositories/EntryRepository.php b/src/Stache/Repositories/EntryRepository.php index d82a60b07c..f8f7d84e56 100644 --- a/src/Stache/Repositories/EntryRepository.php +++ b/src/Stache/Repositories/EntryRepository.php @@ -92,6 +92,21 @@ public function findByUri(string $uri, ?string $site = null): ?Entry : $entry; } + public function firstOrNew(array $attributes = [], array $values = []) + { + return $this->query()->firstOrNew($attributes, $values); + } + + public function firstOrCreate(array $attributes, array $values = []) + { + return $this->query()->firstOrCreate($attributes, $values); + } + + public function updateOrCreate(array $attributes, array $values = []) + { + return $this->query()->updateOrCreate($attributes, $values); + } + public function save($entry) { if (! $entry->id()) { @@ -116,21 +131,6 @@ public function make(): Entry return app(Entry::class); } - public function firstOrNew(array $attributes = [], array $values = []) - { - return $this->query()->firstOrNew($attributes, $values); - } - - public function firstOrCreate(array $attributes, array $values = []) - { - return $this->query()->firstOrCreate($attributes, $values); - } - - public function updateOrCreate(array $attributes, array $values = []) - { - return $this->query()->updateOrCreate($attributes, $values); - } - public function taxonomize($entry) { $entry->collection()->taxonomies()->each(function ($taxonomy) use ($entry) { diff --git a/src/Stache/Repositories/TermRepository.php b/src/Stache/Repositories/TermRepository.php index e7f8448d62..27aaa153ef 100644 --- a/src/Stache/Repositories/TermRepository.php +++ b/src/Stache/Repositories/TermRepository.php @@ -112,6 +112,21 @@ public function findOrFail($id): Term return $term; } + public function firstOrNew(array $attributes, array $values = []) + { + return $this->query()->firstOrNew($attributes, $values); + } + + public function firstOrCreate(array $attributes, array $values = []) + { + return $this->query()->firstOrCreate($attributes, $values); + } + + public function updateOrCreate(array $attributes, array $values = []) + { + return $this->query()->updateOrCreate($attributes, $values); + } + public function save($term) { $this->store @@ -138,21 +153,6 @@ public function make(?string $slug = null): Term return app(Term::class)->slug($slug); } - public function firstOrNew(array $attributes, array $values = []) - { - return $this->query()->firstOrNew($attributes, $values); - } - - public function firstOrCreate(array $attributes, array $values = []) - { - return $this->query()->firstOrCreate($attributes, $values); - } - - public function updateOrCreate(array $attributes, array $values = []) - { - return $this->query()->updateOrCreate($attributes, $values); - } - public function entriesCount(Term $term): int { $items = $this->store->store($term->taxonomyHandle()) From ebc4c02570d61670e1df3e178a3443212054e04f Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Mon, 15 Apr 2024 20:50:23 +0100 Subject: [PATCH 09/12] Implement `findOrNew` --- src/Contracts/Entries/EntryRepository.php | 2 ++ src/Contracts/Taxonomies/TermRepository.php | 2 ++ src/Stache/Query/EntryQueryBuilder.php | 9 +++++++ src/Stache/Query/TermQueryBuilder.php | 9 +++++++ src/Stache/Repositories/EntryRepository.php | 5 ++++ src/Stache/Repositories/TermRepository.php | 5 ++++ tests/Data/Entries/EntryQueryBuilderTest.php | 27 +++++++++++++++++++ .../Data/Taxonomies/TermQueryBuilderTest.php | 27 +++++++++++++++++++ 8 files changed, 86 insertions(+) diff --git a/src/Contracts/Entries/EntryRepository.php b/src/Contracts/Entries/EntryRepository.php index b8a824329c..f79019a5b5 100644 --- a/src/Contracts/Entries/EntryRepository.php +++ b/src/Contracts/Entries/EntryRepository.php @@ -16,6 +16,8 @@ public function findOrFail($id); public function findByUri(string $uri); + public function findOrNew($id); + public function firstOrNew(array $attributes, array $values = []); public function firstOrCreate(array $attributes, array $values = []); diff --git a/src/Contracts/Taxonomies/TermRepository.php b/src/Contracts/Taxonomies/TermRepository.php index 8c251b1028..2f44df9ac6 100644 --- a/src/Contracts/Taxonomies/TermRepository.php +++ b/src/Contracts/Taxonomies/TermRepository.php @@ -16,6 +16,8 @@ public function findByUri(string $uri); public function findOrFail($id); + public function findOrNew($id); + public function firstOrNew(array $attributes, array $values = []); public function firstOrCreate(array $attributes, array $values = []); diff --git a/src/Stache/Query/EntryQueryBuilder.php b/src/Stache/Query/EntryQueryBuilder.php index 9264b55eca..8000734f19 100644 --- a/src/Stache/Query/EntryQueryBuilder.php +++ b/src/Stache/Query/EntryQueryBuilder.php @@ -224,6 +224,15 @@ public function prepareForFakeQuery(): array return $data; } + public function findOrNew($id) + { + if (! is_null($entry = $this->find($id))) { + return $entry; + } + + return Entry::make(); + } + public function firstOrNew(array $attributes = [], array $values = []) { if (! is_null($instance = $this->where($attributes)->first())) { diff --git a/src/Stache/Query/TermQueryBuilder.php b/src/Stache/Query/TermQueryBuilder.php index c1ac93a957..2e56e60c1b 100644 --- a/src/Stache/Query/TermQueryBuilder.php +++ b/src/Stache/Query/TermQueryBuilder.php @@ -206,6 +206,15 @@ public function prepareForFakeQuery(): array return $data; } + public function findOrNew($id) + { + if (! is_null($term = $this->find($id))) { + return $term; + } + + return Term::make(); + } + public function firstOrNew(array $attributes = [], array $values = []) { if (! is_null($instance = $this->where($attributes)->first())) { diff --git a/src/Stache/Repositories/EntryRepository.php b/src/Stache/Repositories/EntryRepository.php index f8f7d84e56..f879418881 100644 --- a/src/Stache/Repositories/EntryRepository.php +++ b/src/Stache/Repositories/EntryRepository.php @@ -92,6 +92,11 @@ public function findByUri(string $uri, ?string $site = null): ?Entry : $entry; } + public function findOrNew($id) + { + return $this->query()->findOrNew($id); + } + public function firstOrNew(array $attributes = [], array $values = []) { return $this->query()->firstOrNew($attributes, $values); diff --git a/src/Stache/Repositories/TermRepository.php b/src/Stache/Repositories/TermRepository.php index 27aaa153ef..771534d4fd 100644 --- a/src/Stache/Repositories/TermRepository.php +++ b/src/Stache/Repositories/TermRepository.php @@ -112,6 +112,11 @@ public function findOrFail($id): Term return $term; } + public function findOrNew($id) + { + return $this->query()->findOrNew($id); + } + public function firstOrNew(array $attributes, array $values = []) { return $this->query()->firstOrNew($attributes, $values); diff --git a/tests/Data/Entries/EntryQueryBuilderTest.php b/tests/Data/Entries/EntryQueryBuilderTest.php index dcf4c24435..d3ffc5fdea 100644 --- a/tests/Data/Entries/EntryQueryBuilderTest.php +++ b/tests/Data/Entries/EntryQueryBuilderTest.php @@ -4,6 +4,7 @@ use Facades\Tests\Factories\EntryFactory; use Illuminate\Support\Carbon; +use Statamic\Contracts\Entries\Entry as EntryContract; use Statamic\Facades\Blueprint; use Statamic\Facades\Collection; use Statamic\Facades\Entry; @@ -894,6 +895,32 @@ public function values_can_be_plucked() ], Entry::query()->where('type', 'b')->pluck('slug')->all()); } + /** @test */ + public function entry_can_be_found_using_find_or_new() + { + Collection::make('posts')->save(); + $entry = EntryFactory::collection('posts')->id('hoff')->slug('david-hasselhoff')->data(['title' => 'David Hasselhoff'])->create(); + + $findOrNew = Entry::query() + ->where('collection', 'posts') + ->findOrNew('hoff'); + + $this->assertSame($entry, $findOrNew); + } + + /** @test */ + public function entry_can_be_created_using_find_or_new() + { + Collection::make('posts')->save(); + + $findOrNew = Entry::query() + ->where('collection', 'posts') + ->findOrNew('hoff'); + + $this->assertNull($findOrNew->id()); + $this->assertInstanceOf(EntryContract::class, $findOrNew); + } + /** @test */ public function entry_can_be_found_using_first_or_new() { diff --git a/tests/Data/Taxonomies/TermQueryBuilderTest.php b/tests/Data/Taxonomies/TermQueryBuilderTest.php index 05f43d62a5..7b76b85299 100644 --- a/tests/Data/Taxonomies/TermQueryBuilderTest.php +++ b/tests/Data/Taxonomies/TermQueryBuilderTest.php @@ -3,6 +3,7 @@ namespace Tests\Data\Taxonomies; use Facades\Tests\Factories\EntryFactory; +use Statamic\Contracts\Taxonomies\Term as TermContract; use Statamic\Facades\Blueprint; use Statamic\Facades\Collection; use Statamic\Facades\Site; @@ -602,6 +603,32 @@ public function terms_are_found_using_offset() $this->assertEquals(['b', 'c'], $terms->map->slug()->all()); } + /** @test */ + public function term_can_be_found_using_find_or_new() + { + Taxonomy::make('tags')->save(); + $term = tap(Term::make()->taxonomy('tags')->inDefaultLocale()->slug('alfa')->data(['title' => 'Alfa']))->save(); + + $findOrNew = Term::query() + ->where('taxonomy', 'tags') + ->findOrNew('tags::alfa'); + + $this->assertEquals($term->slug(), $findOrNew->slug()); + } + + /** @test */ + public function term_can_be_created_using_find_or_new() + { + Taxonomy::make('tags')->save(); + + $findOrNew = Term::query() + ->where('taxonomy', 'tags') + ->findOrNew('tags::alfa'); + + $this->assertEmpty($findOrNew->slug()); + $this->assertInstanceOf(TermContract::class, $findOrNew); + } + /** @test */ public function term_can_be_found_using_first_or_new() { From 1f5efb0ca3986aa7eb76aaab67591bd65e20d06b Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Mon, 15 Apr 2024 20:59:14 +0100 Subject: [PATCH 10/12] Implement `findOr` --- src/Contracts/Entries/EntryRepository.php | 4 +++ src/Contracts/Taxonomies/TermRepository.php | 4 +++ src/Stache/Query/EntryQueryBuilder.php | 10 +++++++ src/Stache/Query/TermQueryBuilder.php | 10 +++++++ src/Stache/Repositories/EntryRepository.php | 6 ++++ src/Stache/Repositories/TermRepository.php | 6 ++++ tests/Data/Entries/EntryQueryBuilderTest.php | 29 +++++++++++++++++++ .../Data/Taxonomies/TermQueryBuilderTest.php | 29 +++++++++++++++++++ 8 files changed, 98 insertions(+) diff --git a/src/Contracts/Entries/EntryRepository.php b/src/Contracts/Entries/EntryRepository.php index f79019a5b5..d34a7b0120 100644 --- a/src/Contracts/Entries/EntryRepository.php +++ b/src/Contracts/Entries/EntryRepository.php @@ -2,6 +2,8 @@ namespace Statamic\Contracts\Entries; +use Closure; + interface EntryRepository { public function all(); @@ -18,6 +20,8 @@ public function findByUri(string $uri); public function findOrNew($id); + public function findOr($id, Closure $callback); + public function firstOrNew(array $attributes, array $values = []); public function firstOrCreate(array $attributes, array $values = []); diff --git a/src/Contracts/Taxonomies/TermRepository.php b/src/Contracts/Taxonomies/TermRepository.php index 2f44df9ac6..cd445a343e 100644 --- a/src/Contracts/Taxonomies/TermRepository.php +++ b/src/Contracts/Taxonomies/TermRepository.php @@ -2,6 +2,8 @@ namespace Statamic\Contracts\Taxonomies; +use Closure; + interface TermRepository { public function all(); @@ -18,6 +20,8 @@ public function findOrFail($id); public function findOrNew($id); + public function findOr($id, Closure $callback); + public function firstOrNew(array $attributes, array $values = []); public function firstOrCreate(array $attributes, array $values = []); diff --git a/src/Stache/Query/EntryQueryBuilder.php b/src/Stache/Query/EntryQueryBuilder.php index 8000734f19..5bb0379c22 100644 --- a/src/Stache/Query/EntryQueryBuilder.php +++ b/src/Stache/Query/EntryQueryBuilder.php @@ -2,6 +2,7 @@ namespace Statamic\Stache\Query; +use Closure; use Statamic\Contracts\Entries\QueryBuilder; use Statamic\Entries\EntryCollection; use Statamic\Facades; @@ -233,6 +234,15 @@ public function findOrNew($id) return Entry::make(); } + public function findOr($id, Closure $callback) + { + if (! is_null($entry = $this->find($id))) { + return $entry; + } + + return $callback(); + } + public function firstOrNew(array $attributes = [], array $values = []) { if (! is_null($instance = $this->where($attributes)->first())) { diff --git a/src/Stache/Query/TermQueryBuilder.php b/src/Stache/Query/TermQueryBuilder.php index 2e56e60c1b..e674d75af4 100644 --- a/src/Stache/Query/TermQueryBuilder.php +++ b/src/Stache/Query/TermQueryBuilder.php @@ -2,6 +2,7 @@ namespace Statamic\Stache\Query; +use Closure; use Statamic\Facades; use Statamic\Facades\Collection; use Statamic\Facades\Term; @@ -215,6 +216,15 @@ public function findOrNew($id) return Term::make(); } + public function findOr($id, Closure $callback) + { + if (! is_null($term = $this->find($id))) { + return $term; + } + + return $callback(); + } + public function firstOrNew(array $attributes = [], array $values = []) { if (! is_null($instance = $this->where($attributes)->first())) { diff --git a/src/Stache/Repositories/EntryRepository.php b/src/Stache/Repositories/EntryRepository.php index f879418881..f522de7705 100644 --- a/src/Stache/Repositories/EntryRepository.php +++ b/src/Stache/Repositories/EntryRepository.php @@ -2,6 +2,7 @@ namespace Statamic\Stache\Repositories; +use Closure; use Statamic\Contracts\Entries\Entry; use Statamic\Contracts\Entries\EntryRepository as RepositoryContract; use Statamic\Contracts\Entries\QueryBuilder; @@ -97,6 +98,11 @@ public function findOrNew($id) return $this->query()->findOrNew($id); } + public function findOr($id, Closure $callback) + { + return $this->query()->findOr($id, $callback); + } + public function firstOrNew(array $attributes = [], array $values = []) { return $this->query()->firstOrNew($attributes, $values); diff --git a/src/Stache/Repositories/TermRepository.php b/src/Stache/Repositories/TermRepository.php index 771534d4fd..7e468fcd52 100644 --- a/src/Stache/Repositories/TermRepository.php +++ b/src/Stache/Repositories/TermRepository.php @@ -2,6 +2,7 @@ namespace Statamic\Stache\Repositories; +use Closure; use Statamic\Contracts\Taxonomies\Term; use Statamic\Contracts\Taxonomies\TermRepository as RepositoryContract; use Statamic\Exceptions\TaxonomyNotFoundException; @@ -117,6 +118,11 @@ public function findOrNew($id) return $this->query()->findOrNew($id); } + public function findOr($id, Closure $callback) + { + return $this->query()->findOr($id, $callback); + } + public function firstOrNew(array $attributes, array $values = []) { return $this->query()->firstOrNew($attributes, $values); diff --git a/tests/Data/Entries/EntryQueryBuilderTest.php b/tests/Data/Entries/EntryQueryBuilderTest.php index d3ffc5fdea..b0d820f5ef 100644 --- a/tests/Data/Entries/EntryQueryBuilderTest.php +++ b/tests/Data/Entries/EntryQueryBuilderTest.php @@ -921,6 +921,35 @@ public function entry_can_be_created_using_find_or_new() $this->assertInstanceOf(EntryContract::class, $findOrNew); } + /** @test */ + public function entry_can_be_found_using_find_or() + { + Collection::make('posts')->save(); + $entry = EntryFactory::collection('posts')->id('hoff')->slug('david-hasselhoff')->data(['title' => 'David Hasselhoff'])->create(); + + $findOrNew = Entry::query() + ->where('collection', 'posts') + ->findOr('hoff', function () { + return 'This could be anything.'; + }); + + $this->assertSame($entry, $findOrNew); + } + + /** @test */ + public function callback_is_called_using_find_or() + { + Collection::make('posts')->save(); + + $findOrNew = Entry::query() + ->where('collection', 'posts') + ->findOr('hoff', function () { + return 'This could be anything.'; + }); + + $this->assertSame('This could be anything.', $findOrNew); + } + /** @test */ public function entry_can_be_found_using_first_or_new() { diff --git a/tests/Data/Taxonomies/TermQueryBuilderTest.php b/tests/Data/Taxonomies/TermQueryBuilderTest.php index 7b76b85299..6782656c38 100644 --- a/tests/Data/Taxonomies/TermQueryBuilderTest.php +++ b/tests/Data/Taxonomies/TermQueryBuilderTest.php @@ -629,6 +629,35 @@ public function term_can_be_created_using_find_or_new() $this->assertInstanceOf(TermContract::class, $findOrNew); } + /** @test */ + public function term_can_be_found_using_find_or() + { + Taxonomy::make('tags')->save(); + $term = tap(Term::make()->taxonomy('tags')->inDefaultLocale()->slug('alfa')->data(['title' => 'Alfa']))->save(); + + $findOrNew = Term::query() + ->where('taxonomy', 'tags') + ->findOr('tags::alfa', function () { + return 'This could be anything.'; + }); + + $this->assertEquals($term->slug(), $findOrNew->slug()); + } + + /** @test */ + public function callback_is_called_using_find_or() + { + Taxonomy::make('tags')->save(); + + $findOrNew = Term::query() + ->where('taxonomy', 'tags') + ->findOr('tags::alfa', function () { + return 'This could be anything.'; + }); + + $this->assertSame('This could be anything.', $findOrNew); + } + /** @test */ public function term_can_be_found_using_first_or_new() { From e0e266e0c0202104c8f36fc7b089b376d80021eb Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Mon, 15 Apr 2024 21:07:59 +0100 Subject: [PATCH 11/12] =?UTF-8?q?=F0=9F=8D=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Data/Entries/EntryQueryBuilderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Data/Entries/EntryQueryBuilderTest.php b/tests/Data/Entries/EntryQueryBuilderTest.php index b0d820f5ef..3f8cfa88f2 100644 --- a/tests/Data/Entries/EntryQueryBuilderTest.php +++ b/tests/Data/Entries/EntryQueryBuilderTest.php @@ -943,7 +943,7 @@ public function callback_is_called_using_find_or() $findOrNew = Entry::query() ->where('collection', 'posts') - ->findOr('hoff', function () { + ->findOr('hoff', function () { return 'This could be anything.'; }); From 19691e58bbf3f67c3c5a2fc9646bbbb6408d047a Mon Sep 17 00:00:00 2001 From: Daniel Weaver Date: Mon, 19 Aug 2024 14:10:24 -0400 Subject: [PATCH 12/12] Fix linting --- tests/Data/Entries/EntryQueryBuilderTest.php | 2 +- tests/Data/Taxonomies/TermQueryBuilderTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Data/Entries/EntryQueryBuilderTest.php b/tests/Data/Entries/EntryQueryBuilderTest.php index 991494afb7..bee0b4ad01 100644 --- a/tests/Data/Entries/EntryQueryBuilderTest.php +++ b/tests/Data/Entries/EntryQueryBuilderTest.php @@ -4,9 +4,9 @@ use Facades\Tests\Factories\EntryFactory; use Illuminate\Support\Carbon; -use Statamic\Contracts\Entries\Entry as EntryContract; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; +use Statamic\Contracts\Entries\Entry as EntryContract; use Statamic\Facades\Blueprint; use Statamic\Facades\Collection; use Statamic\Facades\Entry; diff --git a/tests/Data/Taxonomies/TermQueryBuilderTest.php b/tests/Data/Taxonomies/TermQueryBuilderTest.php index ac2f3dacc2..0936c836bb 100644 --- a/tests/Data/Taxonomies/TermQueryBuilderTest.php +++ b/tests/Data/Taxonomies/TermQueryBuilderTest.php @@ -3,8 +3,8 @@ namespace Tests\Data\Taxonomies; use Facades\Tests\Factories\EntryFactory; -use Statamic\Contracts\Taxonomies\Term as TermContract; use PHPUnit\Framework\Attributes\Test; +use Statamic\Contracts\Taxonomies\Term as TermContract; use Statamic\Facades\Blueprint; use Statamic\Facades\Collection; use Statamic\Facades\Site;