1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Jhormantasayco\LaravelSearchzy; |
4
|
|
|
|
5
|
|
|
use Illuminate\Support\Arr; |
6
|
|
|
use Illuminate\Support\Str; |
7
|
|
|
|
8
|
|
|
trait Searchzy { |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* Agrupa todas los closures de las relaciones del Modelo, en forma de árbol. |
12
|
|
|
* |
13
|
|
|
* @var array |
14
|
|
|
*/ |
15
|
|
|
protected $relationConstraints = []; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Define el array de las relaciones y el query de las relaciones. En el query |
19
|
|
|
* ya se aplicaron las closures de cada relación. |
20
|
|
|
* |
21
|
|
|
* @var array |
22
|
|
|
*/ |
23
|
|
|
protected $eagerRelationConstraints = []; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Define el array con todos los inputs searchable del Modelo. |
27
|
|
|
* |
28
|
|
|
* @var array |
29
|
|
|
*/ |
30
|
|
|
protected $searchableInputs = []; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Define el array con todos los inputs filterable del Modelo. |
34
|
|
|
* |
35
|
|
|
* @var array |
36
|
|
|
*/ |
37
|
|
|
protected $filterableInputs = []; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Define el valor de la 'keyword' de searchzy. |
41
|
|
|
* |
42
|
|
|
* @var array |
43
|
|
|
*/ |
44
|
|
|
protected $searchableKeyword; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Scope que realiza una búsqueda searchzy. |
48
|
|
|
* |
49
|
|
|
* @param Illuminate\Database\Eloquent\Builder $query |
50
|
|
|
* @return Illuminate\Database\Eloquent\Builder |
51
|
|
|
*/ |
52
|
|
|
public function scopeSearchzy($query, $keyword = NULL){ |
53
|
|
|
|
54
|
|
|
$keyword = $keyword ?: config('searchzy.keyword'); |
55
|
|
|
|
56
|
|
|
$this->searchableKeyword = request()->get($keyword, NULL); |
57
|
|
|
|
58
|
|
|
$this->searchableInputsKeyword = $this->getInputsKeyword(); |
|
|
|
|
59
|
|
|
|
60
|
|
|
$this->filterableInputs = $this->getInputsRequest('filterable'); |
61
|
|
|
|
62
|
|
|
$this->searchableInputs = $this->getInputsRequest('searchable'); |
63
|
|
|
|
64
|
|
|
$query = $this->parseInputsKeywordConstraints($query); |
65
|
|
|
|
66
|
|
|
$query = $this->parseRelationConstraints($query); |
67
|
|
|
|
68
|
|
|
$query = $this->loadRelationContraints($query); |
69
|
|
|
|
70
|
|
|
return $query; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Agrupa las relaciones del Modelo. Retorna un array 'arbol' de las relaciones y sus columnas. |
75
|
|
|
* |
76
|
|
|
* @param array $arrInputs |
77
|
|
|
* @return array |
78
|
|
|
*/ |
79
|
|
|
protected function parseRelationInputs($arrInputs){ |
80
|
|
|
|
81
|
|
|
$relationInputs = []; |
82
|
|
|
|
83
|
|
|
foreach (array_keys($arrInputs) as $attribute) { |
84
|
|
|
|
85
|
|
|
if (Str::contains($attribute, ':')) { |
86
|
|
|
|
87
|
|
|
[$relation, $column] = explode(':', $attribute); |
|
|
|
|
88
|
|
|
|
89
|
|
|
$relationInputs[$relation][] = $column; |
|
|
|
|
90
|
|
|
} |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
return $relationInputs; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Agrupas las columnas propias del Modelo. |
98
|
|
|
* |
99
|
|
|
* @param array $arrInputs |
100
|
|
|
* @return array |
101
|
|
|
*/ |
102
|
|
|
protected function parseModelInputs($arrInputs){ |
103
|
|
|
|
104
|
|
|
$modelInputs = []; |
105
|
|
|
|
106
|
|
|
foreach (array_keys($arrInputs) as $attribute) { |
107
|
|
|
|
108
|
|
|
if (!Str::contains($attribute, ':')) { |
109
|
|
|
|
110
|
|
|
$modelInputs[] = $attribute; |
111
|
|
|
} |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
return $modelInputs; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* Parsea los inputs de búsqueda del Modelo. |
119
|
|
|
* |
120
|
|
|
* @param Builder $query |
121
|
|
|
* @return Builder |
122
|
|
|
*/ |
123
|
|
|
protected function parseInputsKeywordConstraints($query){ |
124
|
|
|
|
125
|
|
|
//Aplicación del los where's de los atributos searchable propios del Modelo. |
126
|
|
|
|
127
|
|
|
$searchableModelInputs = $this->parseModelInputs($this->searchableInputsKeyword); |
|
|
|
|
128
|
|
|
|
129
|
|
|
$query = $query->where(function($query) use ($searchableModelInputs){ |
130
|
|
|
|
131
|
|
|
// Aplicación de los where's en las columnas propias del Modelo. |
132
|
|
|
|
133
|
|
|
$query = $query->where(function($query) use ($searchableModelInputs){ |
134
|
|
|
|
135
|
|
|
foreach ($searchableModelInputs as $attribute) { |
136
|
|
|
|
137
|
|
|
$value = Arr::get($this->searchableInputsKeyword, $attribute, $this->searchableKeyword); |
|
|
|
|
138
|
|
|
|
139
|
|
|
if ($value) { |
140
|
|
|
|
141
|
|
|
$query->orWhere($attribute, 'LIKE', "%{$value}") |
142
|
|
|
->orWhere($attribute, 'LIKE', "{$value}%") |
143
|
|
|
->orWhere($attribute, 'LIKE', "%{$value}%"); |
144
|
|
|
} |
145
|
|
|
} |
146
|
|
|
}); |
147
|
|
|
|
148
|
|
|
// Aplicación de los where's de las relaciones del Modelo que cuyo valor es el del 'keyword'. |
149
|
|
|
|
150
|
|
|
if ($this->searchableKeyword) { |
151
|
|
|
|
152
|
|
|
$searchableRelationInputs = $this->parseRelationInputs($this->searchableInputsKeyword); |
|
|
|
|
153
|
|
|
|
154
|
|
|
$searchableRelation = $this->parseRelationInputs($this->searchableInputs); |
155
|
|
|
|
156
|
|
|
foreach ($searchableRelationInputs as $attribute => $columns) { |
157
|
|
|
|
158
|
|
|
if (!in_array($attribute, array_keys($searchableRelation))) { |
159
|
|
|
|
160
|
|
|
$query->orWhereHas($attribute, function ($query) use ($attribute, $searchableRelationInputs) { |
161
|
|
|
|
162
|
|
|
$query = $query->where(function($query) use ($attribute, $searchableRelationInputs){ |
|
|
|
|
163
|
|
|
|
164
|
|
|
$columns = $searchableRelationInputs[$attribute] ?? []; |
165
|
|
|
|
166
|
|
|
foreach ($columns as $column) { |
167
|
|
|
|
168
|
|
|
$value = $this->searchableInputsKeyword["{$attribute}:{$column}"] ?? $this->searchableKeyword; |
|
|
|
|
169
|
|
|
|
170
|
|
|
$query->orWhere($column, 'LIKE', "%{$value}") |
171
|
|
|
->orWhere($column, 'LIKE', "{$value}%") |
172
|
|
|
->orWhere($column, 'LIKE', "%{$value}%"); |
173
|
|
|
} |
174
|
|
|
}); |
175
|
|
|
}); |
176
|
|
|
} |
177
|
|
|
} |
178
|
|
|
} |
179
|
|
|
}); |
180
|
|
|
|
181
|
|
|
// Aplicación del los where's de los atributos filterable propios del Modelo. |
182
|
|
|
|
183
|
|
|
$filterableModelInputs = $this->parseModelInputs($this->filterableInputs); |
184
|
|
|
|
185
|
|
|
$filterableModelInputs = Arr::only($this->filterableInputs, $filterableModelInputs); |
186
|
|
|
|
187
|
|
|
foreach ($filterableModelInputs as $column => $value) { |
188
|
|
|
|
189
|
|
|
$query->where($column, '=', str_trimmer($value)); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
// Se añade los constraints para las relaciones definidads en el searchable del Modelo. |
193
|
|
|
|
194
|
|
|
$searchableRelationInputs = $this->parseRelationInputs($this->searchableInputs); |
195
|
|
|
|
196
|
|
|
foreach ($searchableRelationInputs as $attribute => $columns) { |
197
|
|
|
|
198
|
|
|
$this->addRelationConstraints([$attribute => function ($query) use ($attribute, $searchableRelationInputs) { |
199
|
|
|
|
200
|
|
|
$columns = $searchableRelationInputs[$attribute] ?? []; |
201
|
|
|
|
202
|
|
|
foreach ($columns as $column) { |
203
|
|
|
|
204
|
|
|
$value = Arr::get($this->searchableInputs, "{$attribute}:{$column}"); |
205
|
|
|
|
206
|
|
|
$query->where(function($query) use ($column, $value){ |
207
|
|
|
|
208
|
|
|
$query->orWhere($column, 'LIKE', "%{$value}") |
209
|
|
|
->orWhere($column, 'LIKE', "{$value}%") |
210
|
|
|
->orWhere($column, 'LIKE', "%{$value}%"); |
211
|
|
|
}); |
212
|
|
|
} |
213
|
|
|
}]); |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
// Se añade los constraints de las relaciones definidads en el filterable del Modelo. |
217
|
|
|
|
218
|
|
|
$filterableRelationInputs = $this->parseRelationInputs($this->filterableInputs); |
219
|
|
|
|
220
|
|
|
foreach ($filterableRelationInputs as $attribute => $columns) { |
221
|
|
|
|
222
|
|
|
$this->addRelationConstraints([$attribute => function ($query) use ($attribute, $filterableRelationInputs) { |
223
|
|
|
|
224
|
|
|
$columns = $filterableRelationInputs[$attribute] ?? []; |
225
|
|
|
|
226
|
|
|
foreach ($columns as $column) { |
227
|
|
|
|
228
|
|
|
$value = Arr::get($this->filterableInputs, "{$attribute}:{$column}"); |
229
|
|
|
|
230
|
|
|
$query->where($column, '=', str_trimmer($value)); |
231
|
|
|
} |
232
|
|
|
}]); |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
return $query; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Agrupa las closures por cada relación en {relationConstraints}. |
240
|
|
|
* |
241
|
|
|
* @param array $relations |
242
|
|
|
* @return void |
243
|
|
|
*/ |
244
|
|
|
protected function addRelationConstraints(array $relations){ |
245
|
|
|
|
246
|
|
|
foreach ($relations as $name => $closure) { |
247
|
|
|
|
248
|
|
|
$this->relationConstraints[$name][] = $closure; |
249
|
|
|
} |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* Sí hay closures en las relaciones, aplica al query y agrupalas |
254
|
|
|
* por cada la relación en {eagerRelationConstraints}. |
255
|
|
|
* |
256
|
|
|
* @param Builder $query |
257
|
|
|
* @return Builder |
258
|
|
|
*/ |
259
|
|
|
protected function parseRelationConstraints($query){ |
260
|
|
|
|
261
|
|
|
if ($this->relationConstraints) { |
262
|
|
|
|
263
|
|
|
foreach ($this->relationConstraints as $relation => $constraints) { |
264
|
|
|
|
265
|
|
|
$this->eagerRelationConstraints[$relation] = function($query) use ($constraints) { |
266
|
|
|
|
267
|
|
|
foreach ($constraints as $constraint) { |
268
|
|
|
|
269
|
|
|
$constraint($query); |
270
|
|
|
} |
271
|
|
|
}; |
272
|
|
|
} |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
return $query; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* Aplica los 'closures' que estan en {eagerRelationConstraints} |
280
|
|
|
* por cada relación vía whereHas. |
281
|
|
|
* |
282
|
|
|
* @param Builder $query |
283
|
|
|
* @return Builder |
284
|
|
|
*/ |
285
|
|
|
protected function loadRelationContraints($query){ |
286
|
|
|
|
287
|
|
|
if ($this->eagerRelationConstraints) { |
288
|
|
|
|
289
|
|
|
foreach ($this->eagerRelationConstraints as $relation => $closure) { |
290
|
|
|
|
291
|
|
|
$query->whereHas($relation, $closure); |
292
|
|
|
} |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
return $query; |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
/** |
299
|
|
|
* Retorna un array con los inputs 'searchables' cuyo valor |
300
|
|
|
* será el que ingresado en la 'keyword'. |
301
|
|
|
* |
302
|
|
|
* @param string $keyword |
|
|
|
|
303
|
|
|
* @return array |
304
|
|
|
*/ |
305
|
|
|
protected function getInputsKeyword(){ |
306
|
|
|
|
307
|
|
|
$arrInputs = []; |
308
|
|
|
|
309
|
|
|
$searchable = Arr::wrap($this->searchable); |
|
|
|
|
310
|
|
|
|
311
|
|
|
$searchable = Arr::isAssoc($searchable) ? array_keys($searchable) : $searchable; |
312
|
|
|
|
313
|
|
|
foreach ($searchable as $column) { |
314
|
|
|
|
315
|
|
|
$arrInputs[$column] = $this->searchableKeyword ?: request()->get($column, NULL); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
$arrInputs = array_keys_replace($arrInputs, Arr::wrap($this->searchable)); |
|
|
|
|
319
|
|
|
|
320
|
|
|
return $arrInputs; |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
/** |
324
|
|
|
* Obtiene los inputs definidos en el Modelo de las propiedades 'searchable' y 'filterable'. |
325
|
|
|
* |
326
|
|
|
* @param string $property |
327
|
|
|
* @return array |
328
|
|
|
*/ |
329
|
|
|
protected function getInputsRequest($property){ |
330
|
|
|
|
331
|
|
|
$arrInputs = []; |
332
|
|
|
|
333
|
|
|
if (property_exists($this, $property)) { |
334
|
|
|
|
335
|
|
|
$arrInputs = Arr::wrap($this->{$property}); |
336
|
|
|
|
337
|
|
|
$arrInputs = Arr::isAssoc($arrInputs) ? array_keys($arrInputs) : $arrInputs; |
338
|
|
|
|
339
|
|
|
$arrInputs = array_filter_empty(request()->only($arrInputs)); |
340
|
|
|
|
341
|
|
|
$arrInputs = array_keys_replace($arrInputs, Arr::wrap($this->{$property})); |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
return $arrInputs; |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
/** |
348
|
|
|
* Obtiene los inputs definidos en el Modelo que estan en el Request. |
349
|
|
|
* |
350
|
|
|
* @link (https://timacdonald.me/query-scopes-meet-action-scopes/) |
351
|
|
|
* @param Builder $query |
352
|
|
|
* @param array $extraParams |
353
|
|
|
* @param mixed $default |
354
|
|
|
* @return array |
355
|
|
|
*/ |
356
|
|
|
public function scopeSearchzyInputs($query, $extraParams = [], $default = '') : array { |
|
|
|
|
357
|
|
|
|
358
|
|
|
$searchable = property_exists($this, 'searchable') ? Arr::wrap($this->searchable) : []; |
|
|
|
|
359
|
|
|
|
360
|
|
|
$searchable = Arr::isAssoc($searchable) ? array_keys($searchable) : $searchable; |
361
|
|
|
|
362
|
|
|
$filterable = property_exists($this, 'filterable') ? Arr::wrap($this->filterable) : []; |
|
|
|
|
363
|
|
|
|
364
|
|
|
$filterable = Arr::isAssoc($filterable) ? array_keys($filterable) : $filterable; |
365
|
|
|
|
366
|
|
|
$extraParams = Arr::wrap($extraParams); |
367
|
|
|
|
368
|
|
|
$keyword = Arr::wrap(config('searchzy.keyword')); |
369
|
|
|
|
370
|
|
|
$params = array_merge($searchable, $filterable, $keyword, $extraParams); |
371
|
|
|
|
372
|
|
|
$params = array_only_filler(request()->all(), $params, $default); |
373
|
|
|
|
374
|
|
|
return $params; |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
} |
378
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.