Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
Search
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
4 / 4
4
100.00% covered (success)
100.00%
1 / 1
 search
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
1
 modifySearchQuery
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 modifyFilters
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 afterSearch
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace DevToolbelt\LaravelFastCrud\Actions;
6
7use DevToolbelt\Enums\Http\HttpStatusCode;
8use DevToolbelt\LaravelFastCrud\Traits\Limitable;
9use DevToolbelt\LaravelFastCrud\Traits\Pageable;
10use DevToolbelt\LaravelFastCrud\Traits\Searchable;
11use DevToolbelt\LaravelFastCrud\Traits\Sortable;
12use Exception;
13use Illuminate\Database\Eloquent\Builder;
14use Illuminate\Http\JsonResponse;
15use Illuminate\Http\Request;
16use Psr\Http\Message\ResponseInterface;
17
18/**
19 * Provides the search/list (GET collection) action for CRUD controllers.
20 *
21 * Retrieves a paginated, filtered, and sorted list of model instances.
22 * Supports the following query parameters:
23 * - filter[column][operator]=value - Filter records (see SearchOperator enum)
24 * - sort=column,-desc_column - Sort by columns (prefix with - for DESC)
25 * - perPage=N - Items per page (default from config or 40)
26 * - skipPagination=true - Return all records without pagination
27 *
28 * @method string modelClassName() Returns the Eloquent model class name
29 * @method JsonResponse|ResponseInterface answerSuccess(mixed $data, HttpStatusCode $code, array $meta = [])
30 *         Returns success response
31 *
32 * @property array $data Paginated records data (from Pageable trait)
33 * @property array $paginationData Pagination metadata (from Pageable trait)
34 */
35trait Search
36{
37    use Searchable;
38    use Sortable;
39    use Limitable;
40    use Pageable;
41
42    /**
43     * Filter key used for full-text-like term search.
44     */
45    protected string $termFieldName = 'term';
46
47    /**
48     * Model fields used when applying term search.
49     *
50     * @var array<int, string>
51     */
52    protected array $termFields = [];
53
54    /**
55     * Searches and returns a paginated list of records.
56     *
57     * @param Request $request The HTTP request with filter, sort, and pagination parameters
58     * @param string|null $method Model serialization method (default from config or 'toArray')
59     * @return JsonResponse|ResponseInterface JSON response with records and pagination metadata
60     *
61     * @throws Exception When an invalid search operator is provided
62     *
63     * @example
64     * ```
65     * GET /products?filter[name][like]=Samsung&filter[price][gte]=100&sort=-created_at&perPage=20
66     * ```
67     */
68    public function search(Request $request, ?string $method = null): JsonResponse|ResponseInterface
69    {
70        $method = $method ?? config('devToolbelt.fast-crud.search.method', 'toArray');
71        $httpStatus = HttpStatusCode::from(
72            config('devToolbelt.fast-crud.search.http_status', HttpStatusCode::OK->value)
73        );
74        $defaultPerPage = config('devToolbelt.fast-crud.search.per_page', 40);
75        $perPage = (int) $request->input('perPage', $defaultPerPage);
76        $modelName = $this->modelClassName();
77        $query = $modelName::query();
78
79        $this->modifySearchQuery($query);
80        $filters = $this->modifyFilters($request->get('filter', []));
81        $this->processSearch($query, $filters);
82        $this->processSort($query, $request->input('sort', ''));
83
84        $this->buildPagination($query, $perPage, $method);
85        $this->afterSearch($this->data);
86
87        return $this->answerSuccess(data: $this->data, code: $httpStatus, meta: [
88            'pagination' => $this->paginationData
89        ]);
90    }
91
92    /**
93     * Hook to modify the search query before filters and sorting are applied.
94     *
95     * Override this method to add base conditions, eager loading,
96     * or scopes that should always be applied to search queries.
97     *
98     * @param Builder $query The query builder instance
99     *
100     * @example
101     * ```php
102     * protected function modifySearchQuery(Builder $query): void
103     * {
104     *     $query->with(['category', 'tags'])
105     *           ->where('is_active', true);
106     * }
107     * ```
108     */
109    protected function modifySearchQuery(Builder $query): void
110    {
111    }
112
113    /**
114     * Hook to modify the filters before they are applied to the query.
115     *
116     * Override this method to add, remove, or transform filters
117     * before they are processed by the search engine.
118     *
119     * @param array<string, mixed> $filters The original filters from the request
120     * @return array<string, mixed> The modified filters
121     *
122     * @example
123     * ```php
124     * protected function modifyFilters(array $filters): array
125     * {
126     *     $filters['is_active'] = ['eq' => true];
127     *     unset($filters['internal_field']);
128     *     return $filters;
129     * }
130     * ```
131     */
132    protected function modifyFilters(array $filters): array
133    {
134        return $filters;
135    }
136
137    /**
138     * Hook called after the search results have been fetched.
139     *
140     * Override this method to perform post-search actions,
141     * such as caching, analytics, or result transformation.
142     *
143     * @param array<int, array<string, mixed>> $data The search results array
144     */
145    protected function afterSearch(array $data): void
146    {
147    }
148}