From 2817601501843e87b8e730e318e90f80a5822c06 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: 2026年2月11日 12:53:40 +0000 Subject: [PATCH 1/4] Add `min`, `max`, `sum` and `avg` methods to Stache query builder --- src/Query/Builder.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 1bfa6f156a8..da081638833 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -707,6 +707,31 @@ protected function onceWithColumns($columns, $callback) abstract public function pluck($column, $key = null); + public function min($column) + { + return $this->pluck($column)->min(); + } + + public function max($column) + { + return $this->pluck($column)->max(); + } + + public function sum($column) + { + return $this->pluck($column)->sum(); + } + + public function avg($column) + { + return $this->pluck($column)->avg(); + } + + public function average($column) + { + return $this->avg($column); + } + public function when($value, $callback, $default = null) { if ($value) { From f285267d914d00501f962a6fc56b5af8deab97c8 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: 2026年2月11日 12:53:51 +0000 Subject: [PATCH 2/4] Add tests --- tests/Data/Assets/AssetQueryBuilderTest.php | 68 +++++++++++++++++ tests/Data/Entries/EntryQueryBuilderTest.php | 76 +++++++++++++++++++ .../Data/Taxonomies/TermQueryBuilderTest.php | 72 ++++++++++++++++++ tests/Data/Users/UserQueryBuilderTest.php | 64 ++++++++++++++++ 4 files changed, 280 insertions(+) diff --git a/tests/Data/Assets/AssetQueryBuilderTest.php b/tests/Data/Assets/AssetQueryBuilderTest.php index c487bf7ca43..3b57451c55d 100644 --- a/tests/Data/Assets/AssetQueryBuilderTest.php +++ b/tests/Data/Assets/AssetQueryBuilderTest.php @@ -715,6 +715,74 @@ public function values_can_be_plucked() 'f.jpg', ], $this->container->queryAssets()->where('extension', 'jpg')->pluck('path')->all()); } + + #[Test] + public function can_get_min_value() + { + Asset::find('test::a.jpg')->data(['type' => 'b', 'quantity' => 1])->save(); + Asset::find('test::b.txt')->data(['type' => 'b', 'quantity' => 2])->save(); + Asset::find('test::c.txt')->data(['type' => 'a', 'quantity' => 3])->save(); + Asset::find('test::d.jpg')->data(['type' => 'b', 'quantity' => 4])->save(); + + $this->assertEquals(1, $this->container->queryAssets()->min('quantity')); + + // Assert only queried values are plucked. + $this->assertEquals(3, $this->container->queryAssets()->where('type', 'a')->min('quantity')); + + // Assert returns null when there's no results. + $this->assertNull($this->container->queryAssets()->where('type', 'c')->min('quantity')); + } + + #[Test] + public function can_get_max_value() + { + Asset::find('test::a.jpg')->data(['type' => 'b', 'quantity' => 1])->save(); + Asset::find('test::b.txt')->data(['type' => 'b', 'quantity' => 2])->save(); + Asset::find('test::c.txt')->data(['type' => 'a', 'quantity' => 3])->save(); + Asset::find('test::d.jpg')->data(['type' => 'b', 'quantity' => 4])->save(); + + $this->assertEquals(4, $this->container->queryAssets()->max('quantity')); + + // Assert only queried values are plucked. + $this->assertEquals(3, $this->container->queryAssets()->where('type', 'a')->max('quantity')); + + // Assert returns null when there's no results. + $this->assertNull($this->container->queryAssets()->where('type', 'c')->max('quantity')); + } + + #[Test] + public function can_sum_values() + { + Asset::find('test::a.jpg')->data(['type' => 'b', 'quantity' => 1])->save(); + Asset::find('test::b.txt')->data(['type' => 'b', 'quantity' => 2])->save(); + Asset::find('test::c.txt')->data(['type' => 'a', 'quantity' => 3])->save(); + Asset::find('test::d.jpg')->data(['type' => 'b', 'quantity' => 4])->save(); + + $this->assertEquals(10, $this->container->queryAssets()->sum('quantity')); + + // Assert only queried values are plucked. + $this->assertEquals(3, $this->container->queryAssets()->where('type', 'a')->sum('quantity')); + + // Assert falls back to 0 when there's no results. + $this->assertEquals(0, $this->container->queryAssets()->where('type', 'c')->sum('quantity')); + } + + #[Test] + public function can_get_average_value() + { + Asset::find('test::a.jpg')->data(['type' => 'b', 'quantity' => 1])->save(); + Asset::find('test::b.txt')->data(['type' => 'b', 'quantity' => 2])->save(); + Asset::find('test::c.txt')->data(['type' => 'a', 'quantity' => 3])->save(); + Asset::find('test::d.jpg')->data(['type' => 'b', 'quantity' => 4])->save(); + + $this->assertEquals(2.5, $this->container->queryAssets()->average('quantity')); + + // Assert only queried values are plucked. + $this->assertEquals(3, $this->container->queryAssets()->where('type', 'a')->average('quantity')); + + // Assert returns null when there's no results. + $this->assertNull($this->container->queryAssets()->where('type', 'c')->average('quantity')); + } } class CustomScope extends Scope diff --git a/tests/Data/Entries/EntryQueryBuilderTest.php b/tests/Data/Entries/EntryQueryBuilderTest.php index 4ecce920530..0fbf2d5adba 100644 --- a/tests/Data/Entries/EntryQueryBuilderTest.php +++ b/tests/Data/Entries/EntryQueryBuilderTest.php @@ -1257,6 +1257,82 @@ public function values_can_be_plucked() ], Entry::query()->where('type', 'b')->pluck('slug')->all()); } + #[Test] + public function can_get_min_value() + { + $this->createDummyCollectionAndEntries(); + Entry::find('id-2')->set('type', 'b')->set('quantity', 2)->save(); + Entry::find('id-3')->set('type', 'b')->set('quantity', 3)->save(); + Collection::make('things')->save(); + EntryFactory::id('id-4')->slug('thing-1')->collection('things')->data(['type' => 'a', 'quantity' => 4])->create(); + EntryFactory::id('id-5')->slug('thing-2')->collection('things')->data(['type' => 'b', 'quantity' => 5])->create(); + + $this->assertEquals(2, Entry::query()->min('quantity')); + + // Assert only queried values are plucked. + $this->assertEquals(4, Entry::query()->where('type', 'a')->min('quantity')); + + // Assert returns null when there's no results. + $this->assertNull(Entry::query()->where('type', 'c')->min('quantity')); + } + + #[Test] + public function can_get_max_value() + { + $this->createDummyCollectionAndEntries(); + Entry::find('id-2')->set('type', 'b')->set('quantity', 2)->save(); + Entry::find('id-3')->set('type', 'b')->set('quantity', 3)->save(); + Collection::make('things')->save(); + EntryFactory::id('id-4')->slug('thing-1')->collection('things')->data(['type' => 'a', 'quantity' => 4])->create(); + EntryFactory::id('id-5')->slug('thing-2')->collection('things')->data(['type' => 'b', 'quantity' => 5])->create(); + + $this->assertEquals(5, Entry::query()->max('quantity')); + + // Assert only queried values are plucked. + $this->assertEquals(4, Entry::query()->where('type', 'a')->max('quantity')); + + // Assert returns null when there's no results. + $this->assertNull(Entry::query()->where('type', 'c')->max('quantity')); + } + + #[Test] + public function can_sum_values() + { + $this->createDummyCollectionAndEntries(); + Entry::find('id-2')->set('type', 'b')->set('quantity', 2)->save(); + Entry::find('id-3')->set('type', 'b')->set('quantity', 3)->save(); + Collection::make('things')->save(); + EntryFactory::id('id-4')->slug('thing-1')->collection('things')->data(['type' => 'a', 'quantity' => 4])->create(); + EntryFactory::id('id-5')->slug('thing-2')->collection('things')->data(['type' => 'b', 'quantity' => 5])->create(); + + $this->assertEquals(14, Entry::query()->sum('quantity')); + + // Assert only queried values are plucked. + $this->assertEquals(10, Entry::query()->where('type', 'b')->sum('quantity')); + + // Assert falls back to 0 when there's no results. + $this->assertEquals(0, Entry::query()->where('type', 'c')->sum('quantity')); + } + + #[Test] + public function can_get_average_value() + { + $this->createDummyCollectionAndEntries(); + Entry::find('id-2')->set('type', 'b')->set('quantity', 2)->save(); + Entry::find('id-3')->set('type', 'b')->set('quantity', 3)->save(); + Collection::make('things')->save(); + EntryFactory::id('id-4')->slug('thing-1')->collection('things')->data(['type' => 'a', 'quantity' => 4])->create(); + EntryFactory::id('id-5')->slug('thing-2')->collection('things')->data(['type' => 'b', 'quantity' => 5])->create(); + + $this->assertEquals(3.5, Entry::query()->average('quantity')); + + // Assert only queried values are plucked. + $this->assertEquals(4, Entry::query()->where('type', 'a')->average('quantity')); + + // Assert returns null when there's no results. + $this->assertNull(Entry::query()->where('type', 'c')->average('quantity')); + } + #[Test] public function entry_can_be_found_using_first_or_fail() { diff --git a/tests/Data/Taxonomies/TermQueryBuilderTest.php b/tests/Data/Taxonomies/TermQueryBuilderTest.php index 230b4b2640d..7c4621dc243 100644 --- a/tests/Data/Taxonomies/TermQueryBuilderTest.php +++ b/tests/Data/Taxonomies/TermQueryBuilderTest.php @@ -775,6 +775,78 @@ public function terms_are_found_using_where_relation() $this->assertCount(1, $terms); $this->assertEquals(['c'], $terms->map->slug->all()); } + + #[Test] + public function can_get_min_value() + { + Taxonomy::make('tags')->save(); + Term::make('a')->taxonomy('tags')->data(['type' => 'b', 'quantity' => 1])->save(); + Term::make('b')->taxonomy('tags')->data(['type' => 'b', 'quantity' => 2])->save(); + Term::make('c')->taxonomy('tags')->data(['type' => 'a', 'quantity' => 3])->save(); + Term::make('d')->taxonomy('tags')->data(['type' => 'b', 'quantity' => 4])->save(); + + $this->assertEquals(1, Term::query()->min('quantity')); + + // Assert only queried values are plucked. + $this->assertEqualsassertEquals(3, Term::query()->where('type', 'a')->min('quantity')); + + // Assert returns null when there's no results. + $this->assertNull(Term::query()->where('type', 'c')->min('quantity')); + } + + #[Test] + public function can_get_max_value() + { + Taxonomy::make('tags')->save(); + Term::make('a')->taxonomy('tags')->data(['type' => 'b', 'quantity' => 1])->save(); + Term::make('b')->taxonomy('tags')->data(['type' => 'b', 'quantity' => 2])->save(); + Term::make('c')->taxonomy('tags')->data(['type' => 'a', 'quantity' => 3])->save(); + Term::make('d')->taxonomy('tags')->data(['type' => 'b', 'quantity' => 4])->save(); + + $this->assertEquals(4, Term::query()->max('quantity')); + + // Assert only queried values are plucked. + $this->assertEquals(3, Term::query()->where('type', 'a')->max('quantity')); + + // Assert returns null when there's no results. + $this->assertNull(Term::query()->where('type', 'c')->max('quantity')); + } + + #[Test] + public function can_sum_values() + { + Taxonomy::make('tags')->save(); + Term::make('a')->taxonomy('tags')->data(['type' => 'b', 'quantity' => 1])->save(); + Term::make('b')->taxonomy('tags')->data(['type' => 'b', 'quantity' => 2])->save(); + Term::make('c')->taxonomy('tags')->data(['type' => 'a', 'quantity' => 3])->save(); + Term::make('d')->taxonomy('tags')->data(['type' => 'b', 'quantity' => 4])->save(); + + $this->assertEquals(10, Term::query()->sum('quantity')); + + // Assert only queried values are plucked. + $this->assertEquals(3, Term::query()->where('type', 'a')->sum('quantity')); + + // Assert falls back to 0 when there's no results. + $this->assertEquals(0, Term::query()->where('type', 'c')->sum('quantity')); + } + + #[Test] + public function can_get_average_value() + { + Taxonomy::make('tags')->save(); + Term::make('a')->taxonomy('tags')->data(['type' => 'b', 'quantity' => 1])->save(); + Term::make('b')->taxonomy('tags')->data(['type' => 'b', 'quantity' => 2])->save(); + Term::make('c')->taxonomy('tags')->data(['type' => 'a', 'quantity' => 3])->save(); + Term::make('d')->taxonomy('tags')->data(['type' => 'b', 'quantity' => 4])->save(); + + $this->assertEquals(2.5, Term::query()->average('quantity')); + + // Assert only queried values are plucked. + $this->assertEquals(3, Term::query()->where('type', 'a')->average('quantity')); + + // Assert returns null when there's no results. + $this->assertNull(Term::query()->where('type', 'c')->average('quantity')); + } } class CustomScope extends Scope diff --git a/tests/Data/Users/UserQueryBuilderTest.php b/tests/Data/Users/UserQueryBuilderTest.php index 2ce099cb07b..6dc0059cbee 100644 --- a/tests/Data/Users/UserQueryBuilderTest.php +++ b/tests/Data/Users/UserQueryBuilderTest.php @@ -483,6 +483,70 @@ public function values_can_be_plucked() ], User::query()->where('type', 'b')->pluck('name')->all()); } + #[Test] + public function can_get_min_value() + { + User::make()->email('gandalf@precious.com')->data(['name' => 'Gandalf', 'type' => 'a', 'quantity' => 1])->save(); + User::make()->email('smeagol@precious.com')->data(['name' => 'Smeagol', 'type' => 'b', 'quantity' => 2])->save(); + User::make()->email('frodo@precious.com')->data(['name' => 'Frodo', 'type' => 'b', 'quantity' => 3])->save(); + + $this->assertEquals(1, User::query()->min('quantity')); + + // Assert only queried values are plucked. + $this->assertEquals(2, User::query()->where('type', 'b')->min('quantity')); + + // Assert returns null when there's no results. + $this->assertNull(User::query()->where('type', 'c')->min('quantity')); + } + + #[Test] + public function can_get_max_value() + { + User::make()->email('gandalf@precious.com')->data(['name' => 'Gandalf', 'type' => 'a', 'quantity' => 1])->save(); + User::make()->email('smeagol@precious.com')->data(['name' => 'Smeagol', 'type' => 'b', 'quantity' => 2])->save(); + User::make()->email('frodo@precious.com')->data(['name' => 'Frodo', 'type' => 'b', 'quantity' => 3])->save(); + + $this->assertEquals(3, User::query()->max('quantity')); + + // Assert only queried values are plucked. + $this->assertEquals(1, User::query()->where('type', 'a')->max('quantity')); + + // Assert returns null when there's no results. + $this->assertNull(User::query()->where('type', 'c')->max('quantity')); + } + + #[Test] + public function can_sum_values() + { + User::make()->email('gandalf@precious.com')->data(['name' => 'Gandalf', 'type' => 'a', 'quantity' => 1])->save(); + User::make()->email('smeagol@precious.com')->data(['name' => 'Smeagol', 'type' => 'b', 'quantity' => 2])->save(); + User::make()->email('frodo@precious.com')->data(['name' => 'Frodo', 'type' => 'b', 'quantity' => 3])->save(); + + $this->assertEquals(6, User::query()->sum('quantity')); + + // Assert only queried values are plucked. + $this->assertEquals(5, User::query()->where('type', 'b')->sum('quantity')); + + // Assert falls back to 0 when there's no results. + $this->assertEquals(0, User::query()->where('type', 'c')->sum('quantity')); + } + + #[Test] + public function can_get_average_value() + { + User::make()->email('gandalf@precious.com')->data(['name' => 'Gandalf', 'type' => 'a', 'quantity' => 1])->save(); + User::make()->email('smeagol@precious.com')->data(['name' => 'Smeagol', 'type' => 'b', 'quantity' => 2])->save(); + User::make()->email('frodo@precious.com')->data(['name' => 'Frodo', 'type' => 'b', 'quantity' => 3])->save(); + + $this->assertEquals(2, User::query()->average('quantity')); + + // Assert only queried values are plucked. + $this->assertEquals(2.5, User::query()->where('type', 'b')->average('quantity')); + + // Assert returns null when there's no results. + $this->assertNull(User::query()->where('type', 'c')->average('quantity')); + } + /** @test **/ public function users_are_found_using_scopes() { From b5aee6edb5bcdf09e5daf597c7cdc72f3b0a81e3 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: 2026年2月11日 12:54:00 +0000 Subject: [PATCH 3/4] Add missing `#[Test]` attribute --- tests/Data/Entries/EntryQueryBuilderTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Data/Entries/EntryQueryBuilderTest.php b/tests/Data/Entries/EntryQueryBuilderTest.php index 0fbf2d5adba..57de5abadab 100644 --- a/tests/Data/Entries/EntryQueryBuilderTest.php +++ b/tests/Data/Entries/EntryQueryBuilderTest.php @@ -1224,6 +1224,7 @@ public static function filterByStatusProvider() ]; } + #[Test] public function values_can_be_plucked() { $this->createDummyCollectionAndEntries(); From c96d562a7f64aa8f55c7f74710d753a542b12ffc Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: 2026年2月11日 13:21:26 +0000 Subject: [PATCH 4/4] wip --- tests/Data/Taxonomies/TermQueryBuilderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Data/Taxonomies/TermQueryBuilderTest.php b/tests/Data/Taxonomies/TermQueryBuilderTest.php index 7c4621dc243..0fcbf38c76f 100644 --- a/tests/Data/Taxonomies/TermQueryBuilderTest.php +++ b/tests/Data/Taxonomies/TermQueryBuilderTest.php @@ -788,7 +788,7 @@ public function can_get_min_value() $this->assertEquals(1, Term::query()->min('quantity')); // Assert only queried values are plucked. - $this->assertEqualsassertEquals(3, Term::query()->where('type', 'a')->min('quantity')); + $this->assertEquals(3, Term::query()->where('type', 'a')->min('quantity')); // Assert returns null when there's no results. $this->assertNull(Term::query()->where('type', 'c')->min('quantity'));

AltStyle によって変換されたページ (->オリジナル) /