diff --git a/docs-v3/content/docs/api/getters.md b/docs-v3/content/docs/api/getters.md index 5f4959a7f..3045c8e4b 100644 --- a/docs-v3/content/docs/api/getters.md +++ b/docs-v3/content/docs/api/getters.md @@ -143,6 +143,27 @@ public function handle(Request $request) } ``` +#### Accessing the filtered query + +For index getters, you can access the filtered query builder via `$request->filteredQuery()`. This query builder already has all filters, search queries, and other query modifiers applied by Restify: + +```php +public function handle(Request $request): JsonResponse +{ + // Get the filtered query builder with all applied filters, search, etc. + $query = $request->filteredQuery(); + + // You can further refine the query + $data = $query->where('status', 'active')->get(); + + return response()->json([ + 'data' => $data, + ]); +} +``` + +This allows you to build upon the existing query without having to manually apply filters again. + ## Getter customizations Getters could be easily customized. @@ -278,16 +299,26 @@ Index getters are used when you have to apply them for many items. ### Index getter definition -The index getter definition differs in how it receives arguments for the `handle` method. +The index getter definition receives only the `$request` in the `handle` method. You can access the filtered query builder using `$request->filteredQuery()`: ```php public function handle(Request $request): JsonResponse { - // + // Get the filtered query builder with all applied filters, search, etc. + $query = $request->filteredQuery(); + + // You can further refine the query + $data = $query->where('status', 'active')->get(); + + return response()->json([ + 'data' => $data, + ]); } ``` +The filtered query builder contains all repository filters, search queries, and other query modifiers already applied. This allows you to leverage existing filters without re-implementing them. + ### Index getter registration To register an index getter, we have to use the `->onlyOnIndex()` accessor: diff --git a/src/Http/Requests/Concerns/InteractWithRepositories.php b/src/Http/Requests/Concerns/InteractWithRepositories.php index 100357ac9..a793f866f 100644 --- a/src/Http/Requests/Concerns/InteractWithRepositories.php +++ b/src/Http/Requests/Concerns/InteractWithRepositories.php @@ -7,6 +7,7 @@ use Binaryk\LaravelRestify\Repositories\Repository; use Binaryk\LaravelRestify\Repositories\RepositoryInstance; use Binaryk\LaravelRestify\Restify; +use Binaryk\LaravelRestify\Services\Search\RepositorySearchService; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; @@ -121,4 +122,9 @@ public function isViaRepository(): bool return $parentRepository && $parentRepositoryId; } + + public function filteredQuery(): Builder + { + return RepositorySearchService::make()->search($this, $this->repository()); + } } diff --git a/src/MCP/Bootstrap/BootMcpTools.php b/src/MCP/Bootstrap/BootMcpTools.php index 401c66cc0..8beadc2ee 100644 --- a/src/MCP/Bootstrap/BootMcpTools.php +++ b/src/MCP/Bootstrap/BootMcpTools.php @@ -140,8 +140,8 @@ protected function discoverCustomTools(): Collection protected function discoverRepositoryTools(): Collection { return collect(Restify::$repositories) - ->filter(fn(string $repo): bool => in_array(HasMcpTools::class, class_uses_recursive($repo))) - ->flatMap(fn(string $repoClass): Collection => $this->discoverRepositoryOperations($repoClass)) + ->filter(fn (string $repo): bool => in_array(HasMcpTools::class, class_uses_recursive($repo))) + ->flatMap(fn (string $repoClass): Collection => $this->discoverRepositoryOperations($repoClass)) ->values(); } @@ -211,10 +211,10 @@ protected function discoverActions(string $repositoryClass, Repository $reposito $actionRequest = app(McpActionRequest::class); return $repository->resolveActions($actionRequest) - ->filter(fn($action): bool => $action instanceof Action) - ->filter(fn(Action $action): bool => $action->isShownOnMcp($actionRequest, $repository)) - ->filter(fn(Action $action): bool => $action->authorizedToSee($actionRequest)) - ->unique(fn(Action $action): string => $action->uriKey()) + ->filter(fn ($action): bool => $action instanceof Action) + ->filter(fn (Action $action): bool => $action->isShownOnMcp($actionRequest, $repository)) + ->filter(fn (Action $action): bool => $action->authorizedToSee($actionRequest)) + ->unique(fn (Action $action): string => $action->uriKey()) ->map(function (Action $action) use ($repositoryClass, $repository): array { $instance = new ActionTool($repositoryClass, $action); @@ -241,10 +241,10 @@ protected function discoverGetters(string $repositoryClass, Repository $reposito $getterRequest = app(McpGetterRequest::class); return $repository->resolveGetters($getterRequest) - ->filter(fn($getter): bool => $getter instanceof Getter) - ->filter(fn(Getter $getter): bool => $getter->isShownOnMcp($getterRequest, $repository)) - ->filter(fn(Getter $getter): bool => $getter->authorizedToSee($getterRequest)) - ->unique(fn(Getter $getter): string => $getter->uriKey()) + ->filter(fn ($getter): bool => $getter instanceof Getter) + ->filter(fn (Getter $getter): bool => $getter->isShownOnMcp($getterRequest, $repository)) + ->filter(fn (Getter $getter): bool => $getter->authorizedToSee($getterRequest)) + ->unique(fn (Getter $getter): string => $getter->uriKey()) ->map(function (Getter $getter) use ($repositoryClass, $repository): array { $instance = new GetterTool($repositoryClass, $getter); diff --git a/tests/Fixtures/Post/Getters/PostsFilteredQueryGetter.php b/tests/Fixtures/Post/Getters/PostsFilteredQueryGetter.php new file mode 100644 index 000000000..532b9c4a4 --- /dev/null +++ b/tests/Fixtures/Post/Getters/PostsFilteredQueryGetter.php @@ -0,0 +1,24 @@ +filteredQuery(); + + $count = $query->count(); + + return response()->json([ + 'message' => 'filtered query works', + 'count' => $count, + ]); + } +} diff --git a/tests/Fixtures/Post/PostRepository.php b/tests/Fixtures/Post/PostRepository.php index 1bcf4f821..7fee7c03e 100644 --- a/tests/Fixtures/Post/PostRepository.php +++ b/tests/Fixtures/Post/PostRepository.php @@ -7,6 +7,7 @@ use Binaryk\LaravelRestify\Http\Requests\ActionRequest; use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; use Binaryk\LaravelRestify\Repositories\Repository; +use Binaryk\LaravelRestify\Tests\Fixtures\Post\Getters\PostsFilteredQueryGetter; use Binaryk\LaravelRestify\Tests\Fixtures\Post\Getters\PostsIndexGetter; use Binaryk\LaravelRestify\Tests\Fixtures\Post\Getters\PostsIndexInvokableGetter; use Binaryk\LaravelRestify\Tests\Fixtures\Post\Getters\PostsShowGetter; @@ -139,6 +140,7 @@ public function getters(RestifyRequest $request): array PostsIndexGetter::make(), PostsShowGetter::make()->onlyOnShow(), UnauthenticatedActionGetter::make()->withoutMiddleware(AuthorizeRestify::class), + PostsFilteredQueryGetter::make(), new PostsShowInvokableGetter, new PostsIndexInvokableGetter, ]; diff --git a/tests/Getters/ListGettersControllerTest.php b/tests/Getters/ListGettersControllerTest.php index e51ae3b1b..dd9729abe 100644 --- a/tests/Getters/ListGettersControllerTest.php +++ b/tests/Getters/ListGettersControllerTest.php @@ -16,7 +16,7 @@ public function test_could_list_getters_for_repository(): void fn (AssertableJson $json) => $json ->has('data') ->where('data.0.uriKey', 'posts-index-getter') - ->count('data', 4) + ->count('data', 5) ->etc() ); } @@ -31,7 +31,7 @@ public function test_could_list_getters_for_given_repository(): void fn (AssertableJson $json) => $json ->has('data') ->where('data.1.uriKey', 'posts-show-getter') - ->count('data', 5) + ->count('data', 6) ->etc() ); } diff --git a/tests/Getters/PerformGetterControllerTest.php b/tests/Getters/PerformGetterControllerTest.php index 5762b54e0..73fb5e7c0 100644 --- a/tests/Getters/PerformGetterControllerTest.php +++ b/tests/Getters/PerformGetterControllerTest.php @@ -2,10 +2,12 @@ namespace Binaryk\LaravelRestify\Tests\Getters; +use Binaryk\LaravelRestify\Tests\Fixtures\Post\Getters\PostsFilteredQueryGetter; use Binaryk\LaravelRestify\Tests\Fixtures\Post\Getters\PostsIndexGetter; use Binaryk\LaravelRestify\Tests\Fixtures\Post\Getters\PostsIndexInvokableGetter; use Binaryk\LaravelRestify\Tests\Fixtures\Post\Getters\PostsShowGetter; use Binaryk\LaravelRestify\Tests\Fixtures\Post\Getters\PostsShowInvokableGetter; +use Binaryk\LaravelRestify\Tests\Fixtures\Post\Post; use Binaryk\LaravelRestify\Tests\Fixtures\Post\PostRepository; use Binaryk\LaravelRestify\Tests\IntegrationTestCase; use Illuminate\Testing\Fluent\AssertableJson; @@ -62,4 +64,44 @@ public function test_could_perform_repository_invokable_getter(): void ->etc() ); } + + public function test_getter_can_access_filtered_query(): void + { + // Create 5 posts: 3 active, 2 inactive + Post::factory()->count(3)->create(['is_active' => true]); + Post::factory()->count(2)->create(['is_active' => false]); + + // Call getter without filter - should return all 5 posts + $this + ->getJson(PostRepository::getter(PostsFilteredQueryGetter::class)) + ->assertOk() + ->assertJson( + fn (AssertableJson $json) => $json + ->where('message', 'filtered query works') + ->where('count', 5) + ->etc() + ); + + // Call getter with active filter - should return only 3 active posts + $this + ->getJson(PostRepository::getter(PostsFilteredQueryGetter::class).'?is_active=1') + ->assertOk() + ->assertJson( + fn (AssertableJson $json) => $json + ->where('message', 'filtered query works') + ->where('count', 3) + ->etc() + ); + + // Call getter with inactive filter - should return only 2 inactive posts + $this + ->getJson(PostRepository::getter(PostsFilteredQueryGetter::class).'?is_active=0') + ->assertOk() + ->assertJson( + fn (AssertableJson $json) => $json + ->where('message', 'filtered query works') + ->where('count', 2) + ->etc() + ); + } }