From e0b0fe2e39a9cd9b02114a9488461cd091ee8adb Mon Sep 17 00:00:00 2001 From: gabrielpetrescu Date: Thu, 6 Feb 2025 17:15:20 +0700 Subject: [PATCH 1/2] feat: Add groupBy as a new feature in repository --- docs-v2/content/en/api/repositories.md | 11 ++++ src/Http/Requests/RestifyRequest.php | 5 ++ src/Repositories/Repository.php | 5 ++ .../Search/RepositorySearchService.php | 24 ++++++++ tests/Feature/RepositoryGroupByTest.php | 57 +++++++++++++++++++ 5 files changed, 102 insertions(+) create mode 100644 tests/Feature/RepositoryGroupByTest.php diff --git a/docs-v2/content/en/api/repositories.md b/docs-v2/content/en/api/repositories.md index 2ec9503f6..abc605d8d 100644 --- a/docs-v2/content/en/api/repositories.md +++ b/docs-v2/content/en/api/repositories.md @@ -819,3 +819,14 @@ public static $withs = ['posts']; `withs` is not a typo. Laravel uses the `with` property on models, on repositories we use `$withs`, it's not a typo. + +## Group by + +The group by filter is useful when you want to group the results by a certain column. + +```php +class PostRepository extends Repository +{ + public static array $groupBy = ['user_id']; +} +``` diff --git a/src/Http/Requests/RestifyRequest.php b/src/Http/Requests/RestifyRequest.php index fde304721..e2e5d6d2d 100644 --- a/src/Http/Requests/RestifyRequest.php +++ b/src/Http/Requests/RestifyRequest.php @@ -77,4 +77,9 @@ public function filters(): array ? $this->input('filters', []) : (json_decode(base64_decode($this->input('filters')), true) ?? []); } + + public function groupBy(): ?string + { + return $this->input('group_by'); + } } diff --git a/src/Repositories/Repository.php b/src/Repositories/Repository.php index 7c1380eea..e110e5a24 100644 --- a/src/Repositories/Repository.php +++ b/src/Repositories/Repository.php @@ -91,6 +91,11 @@ class Repository implements JsonSerializable, RestifySearchable */ public static array $sort; + /** + * The list of fields that can be used for grouping. + */ + public static array $groupBy = []; + /** * Attribute that should be used for displaying single model. */ diff --git a/src/Services/Search/RepositorySearchService.php b/src/Services/Search/RepositorySearchService.php index 5f3fbf241..1a117b000 100644 --- a/src/Services/Search/RepositorySearchService.php +++ b/src/Services/Search/RepositorySearchService.php @@ -43,6 +43,8 @@ public function search(RestifyRequest $request, Repository $repository): Builder $query = $this->applyFilters($request, $repository, $query); + $query = $this->applyGroupBy($request, $repository, $query); + $ordersBuilder = $this->prepareOrders($request, $query); return tap( @@ -217,6 +219,28 @@ protected function applyFilters(RestifyRequest $request, Repository $repository, return $query; } + protected function applyGroupBy(RestifyRequest $request, Repository $repository, $query) + { + if (! $request->has('group_by')) { + return $query; + } + + $groupByColumns = explode(',', $request->input('group_by')); + + foreach ($groupByColumns as $column) { + if (! in_array($column, $repository::$groupBy)) { + abort(422, sprintf( + 'The column [%s] is not allowed for grouping. Allowed columns are: %s', + $column, + implode(', ', $repository::$groupBy) + )); + } + $query->groupBy($column); + } + + return $query; + } + public static function make(): static { return new static; diff --git a/tests/Feature/RepositoryGroupByTest.php b/tests/Feature/RepositoryGroupByTest.php new file mode 100644 index 000000000..e27a48c6a --- /dev/null +++ b/tests/Feature/RepositoryGroupByTest.php @@ -0,0 +1,57 @@ +create([ + 'name' => 'John Doe', + ]); + + User::factory(4)->create([ + 'name' => 'Second John Doe', + ]); + + UserRepository::$groupBy = ['name']; + + $this->getJson(UserRepository::route(query: ['group_by' => 'name']))->assertJsonCount(2, 'data'); + } + + public function test_it_can_group_by_the_results_multiple_columns(): void + { + User::factory(3)->create([ + 'name' => 'John Doe', + 'avatar' => 'image.jpg', + ]); + + User::factory(1)->create([ + 'name' => 'Another John Doe', + 'avatar' => 'image.jpg', + ]); + + UserRepository::$groupBy = ['name', 'avatar']; + + $this->getJson(UserRepository::route(query: ['group_by' => 'name,avatar']))->assertJsonCount(2, 'data'); + } + + public function test_it_can_not_group_by_the_results_because_wrong_column(): void + { + User::factory(4)->create([ + 'name' => 'John Doe', + ]); + + User::factory(4)->create([ + 'name' => 'Second John Doe', + ]); + + UserRepository::$groupBy = ['email']; + + $this->getJson(UserRepository::route(query: ['group_by' => 'name']))->assertUnprocessable(); + } +} From 3621f4c1c972c41c10dbf9d786e5baa6cc008d6b Mon Sep 17 00:00:00 2001 From: gabrielpetrescu Date: Fri, 14 Feb 2025 15:59:06 +0200 Subject: [PATCH 2/2] apply qualifyColumn for groupBy column --- src/Services/Search/RepositorySearchService.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Services/Search/RepositorySearchService.php b/src/Services/Search/RepositorySearchService.php index 1a117b000..dc17c26fb 100644 --- a/src/Services/Search/RepositorySearchService.php +++ b/src/Services/Search/RepositorySearchService.php @@ -225,6 +225,7 @@ protected function applyGroupBy(RestifyRequest $request, Repository $repository, return $query; } + $model = $query->getModel(); $groupByColumns = explode(',', $request->input('group_by')); foreach ($groupByColumns as $column) { @@ -235,7 +236,7 @@ protected function applyGroupBy(RestifyRequest $request, Repository $repository, implode(', ', $repository::$groupBy) )); } - $query->groupBy($column); + $query->groupBy($model->qualifyColumn($column)); } return $query;