|
2 | 2 |
|
3 | 3 | namespace ProtoneMedia\LaravelCrossEloquentSearch; |
4 | 4 |
|
5 | | -use Illuminate\Database\Eloquent\Builder; |
6 | | -use Illuminate\Database\Eloquent\Collection as EloquentCollection; |
7 | | -use Illuminate\Pagination\Paginator; |
8 | | -use Illuminate\Support\Arr; |
9 | | -use Illuminate\Support\Collection; |
10 | | -use Illuminate\Support\Facades\DB; |
| 5 | +use Illuminate\Support\Facades\Facade; |
11 | 6 |
|
12 | | -class Search |
| 7 | +class Search extends Facade |
13 | 8 | { |
14 | | - private Collection $pendingQueries; |
15 | | - private int $perPage = 15; |
16 | | - private string $pageName = 'page'; |
17 | | - private $page; |
18 | | - private string $orderByDirection; |
19 | | - private bool $wildcardLeft = false; |
20 | | - private Collection $terms; |
21 | | - |
22 | | - public function __construct() |
23 | | - { |
24 | | - $this->pendingQueries = new Collection; |
25 | | - |
26 | | - $this->orderByAsc(); |
27 | | - } |
28 | | - |
29 | | - public static function new(): self |
30 | | - { |
31 | | - return new static; |
32 | | - } |
33 | | - |
34 | | - public function orderByAsc(): self |
| 9 | + protected static function getFacadeAccessor() |
35 | 10 | { |
36 | | - $this->orderByDirection = 'asc'; |
37 | | - |
38 | | - return $this; |
39 | | - } |
40 | | - |
41 | | - public function orderByDesc(): self |
42 | | - { |
43 | | - $this->orderByDirection = 'desc'; |
44 | | - |
45 | | - return $this; |
46 | | - } |
47 | | - |
48 | | - public function add($query, $columns, string $orderByColumn = 'updated_at'): self |
49 | | - { |
50 | | - $pendingQuery = new PendingQuery( |
51 | | - is_string($query) ? $query::query() : $query, |
52 | | - Collection::wrap($columns), |
53 | | - $orderByColumn, |
54 | | - $this->pendingQueries->count() |
55 | | - ); |
56 | | - |
57 | | - $this->pendingQueries->push($pendingQuery); |
58 | | - |
59 | | - return $this; |
60 | | - } |
61 | | - |
62 | | - public function wildcardLeft(): self |
63 | | - { |
64 | | - $this->wildcardLeft = true; |
65 | | - |
66 | | - return $this; |
67 | | - } |
68 | | - |
69 | | - private function makeOrderBy(): string |
70 | | - { |
71 | | - $modelOrderKeys = $this->pendingQueries->map->getModelKey('order')->implode(','); |
72 | | - |
73 | | - return "COALESCE({$modelOrderKeys})"; |
74 | | - } |
75 | | - |
76 | | - private function makeSelects(PendingQuery $currentPendingQuery): array |
77 | | - { |
78 | | - return $this->pendingQueries->flatMap(function (PendingQuery $pendingQuery) use ($currentPendingQuery) { |
79 | | - $qualifiedKeyName = $qualifiedOrderByColumnName = 'null'; |
80 | | - |
81 | | - if ($pendingQuery === $currentPendingQuery) { |
82 | | - $qualifiedKeyName = $pendingQuery->getQualifiedKeyName(); |
83 | | - $qualifiedOrderByColumnName = $pendingQuery->getQualifiedOrderByColumnName(); |
84 | | - } |
85 | | - |
86 | | - return [ |
87 | | - DB::raw("{$qualifiedKeyName} as {$pendingQuery->getModelKey()}"), |
88 | | - DB::raw("{$qualifiedOrderByColumnName} as {$pendingQuery->getModelKey('order')}"), |
89 | | - ]; |
90 | | - })->all(); |
91 | | - } |
92 | | - |
93 | | - public function paginate($perPage = 15, $pageName = 'page', $page = null): self |
94 | | - { |
95 | | - $this->page = $page ?: Paginator::resolveCurrentPage($pageName); |
96 | | - $this->pageName = $pageName; |
97 | | - $this->perPage = $perPage; |
98 | | - |
99 | | - return $this; |
100 | | - } |
101 | | - |
102 | | - public function addSearchQueryToBuilder(Builder $builder, PendingQuery $pendingQuery, string $term) |
103 | | - { |
104 | | - return $builder->where(function ($query) use ($pendingQuery) { |
105 | | - $pendingQuery->getQualifiedColumns()->each( |
106 | | - fn ($field) => $this->terms->each( |
107 | | - fn ($term) => $query->orWhere($field, 'like', ($this->wildcardLeft ? '%' : '') . "{$term}%") |
108 | | - ) |
109 | | - ); |
110 | | - }); |
111 | | - } |
112 | | - |
113 | | - private function buildQueries($term): Collection |
114 | | - { |
115 | | - return $this->pendingQueries->map(function (PendingQuery $pendingQuery) use ($term) { |
116 | | - return $pendingQuery->getFreshBuilder() |
117 | | - ->select($this->makeSelects($pendingQuery)) |
118 | | - ->from(DB::raw($pendingQuery->getTableAlias())) |
119 | | - ->tap(function ($builder) use ($pendingQuery, $term) { |
120 | | - $this->addSearchQueryToBuilder($builder, $pendingQuery, $term); |
121 | | - }); |
122 | | - }); |
123 | | - } |
124 | | - |
125 | | - public function get($term) |
126 | | - { |
127 | | - $this->terms = Collection::make(str_getcsv($term, ' ', '"'))->filter(); |
128 | | - |
129 | | - if ($this->terms->isEmpty()) { |
130 | | - throw new EmptySearchQueryException; |
131 | | - } |
132 | | - |
133 | | - $queries = $this->buildQueries($term); |
134 | | - |
135 | | - $firstQuery = $queries->shift(); |
136 | | - |
137 | | - $queries->each(fn (Builder $query) => $firstQuery->union($query)); |
138 | | - $firstQuery->orderBy(DB::raw($this->makeOrderBy()), $this->orderByDirection); |
139 | | - |
140 | | - $results = $this->perPage |
141 | | - ? $firstQuery->paginate($this->perPage, ['*'], $this->pageName, $this->page) |
142 | | - : $firstQuery->get(); |
143 | | - |
144 | | - $modelsPerType = $this->pendingQueries |
145 | | - ->keyBy->getModelKey() |
146 | | - ->map(function (PendingQuery $pendingQuery, $key) use ($results) { |
147 | | - $ids = $results->pluck($key)->filter(); |
148 | | - |
149 | | - return $ids->isNotEmpty() |
150 | | - ? $pendingQuery->newQueryWithoutScopes()->whereKey($ids)->get()->keyBy->getKey() |
151 | | - : null; |
152 | | - }); |
153 | | - |
154 | | - return $results->map(function ($item) use ($modelsPerType) { |
155 | | - $modelKey = Arr::first(array_flip(array_filter($item->toArray()))); |
156 | | - |
157 | | - return $modelsPerType->get($modelKey)->get($item->$modelKey); |
158 | | - }) |
159 | | - ->pipe(fn (Collection $models) => new EloquentCollection($models)) |
160 | | - ->when($this->perPage, fn (EloquentCollection $models) => $results->setCollection($models)); |
| 11 | + return 'laravel-cross-eloquent-search'; |
161 | 12 | } |
162 | 13 | } |
0 commit comments