Searchzy::getInputsFromRequest()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
1
<?php
2
3
namespace Jhormantasayco\LaravelSearchzy;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Str;
8
9
trait Searchzy
10
{
11
    /**
12
     * Agrupa todas los closures de las relaciones del Modelo, en forma de árbol.
13
     *
14
     * @var array
15
     */
16
    private $relationConstraints = [];
17
18
    /**
19
     * Define el array de las relaciones y el query de las relaciones. En el query
20
     * ya se aplicaron las closures de cada relación.
21
     *
22
     * @var array
23
     */
24
    private $eagerRelationConstraints = [];
25
26
    /**
27
     * Define el array con los valores searchable con la 'keyword' de searchzy.
28
     *
29
     * @var array
30
     */
31
    private $searchableInputsKeyword;
32
33
    /**
34
     * Define el array con todos los inputs searchable del Modelo.
35
     *
36
     * @var array
37
     */
38
    private $searchableInputs = [];
39
40
    /**
41
     * Define el array con todos los inputs filterable del Modelo.
42
     *
43
     * @var array
44
     */
45
    private $filterableInputs = [];
46
47
    /**
48
     * Define el array con todos los inputs adicionales del Modelo.
49
     *
50
     * @var array
51
     */
52
    private $aditionableInputs = [];
53
54
    /**
55
     * Define el valor de la 'keyword' de searchzy.
56
     *
57
     * @var array
58
     */
59
    private $searchableKeyword;
60
61
    /**
62
     * Define el request usado por searchzy.
63
     *
64
     * @var Request
65
     */
66
    private $currentRequest;
67
68
    /**
69
     * Scope que realiza una búsqueda searchzy.
70
     *
71
     * @param  Illuminate\Database\Eloquent\Builder $query
72
     * @return Illuminate\Database\Eloquent\Builder
73
     */
74
    public function scopeSearchzy($query, $keyword = null, $request = null): Builder
75
    {
76
        $keyword = $keyword ?: config('searchzy.keyword');
77
78
        $this->currentRequest = $request ?: request();
79
80
        $this->searchableKeyword = $this->currentRequest->get($keyword, null);
81
82
        $this->searchableInputsKeyword = $this->getInputsKeyword();
83
84
        $this->searchableInputs = $this->getInputsFromRequest('searchable', 'searchableInputs');
85
86
        $this->filterableInputs = $this->getInputsFromRequest('filterable', 'filterableInputs');
87
88
        $query = $this->parseInputsKeywordConstraints($query);
89
90
        $query = $this->parseRelationConstraints($query);
91
92
        $query = $this->loadRelationContraints($query);
93
94
        return $query;
95
    }
96
97
    /**
98
     * Agrupa las relaciones del Modelo. Retorna un array 'arbol' de las relaciones y sus columnas.
99
     *
100
     * @param  array $arrInputs
101
     * @return array
102
     */
103
    private function parseRelationInputs($arrInputs): array
104
    {
105
        $relationInputs = [];
106
107
        foreach (array_keys($arrInputs) as $attribute) {
108
109
            if (Str::contains($attribute, ':')) {
110
111
                [$relation, $column] = explode(':', $attribute);
0 ignored issues
show
Bug introduced by
The variable $relation does not exist. Did you mean $relationInputs?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
Bug introduced by
The variable $column does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
112
113
                $relationInputs[$relation][] = $column;
0 ignored issues
show
Bug introduced by
The variable $relation does not exist. Did you mean $relationInputs?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
114
            }
115
        }
116
117
        return $relationInputs;
118
    }
119
120
    /**
121
     * Agrupas las columnas propias del Modelo.
122
     *
123
     * @param  array $arrInputs
124
     * @return array
125
     */
126
    private function parseModelInputs($arrInputs): array
127
    {
128
        $modelInputs = [];
129
130
        foreach (array_keys($arrInputs) as $attribute) {
131
132
            if (!Str::contains($attribute, ':')) {
133
134
                $modelInputs[] = $attribute;
135
            }
136
        }
137
138
        return $modelInputs;
139
    }
140
141
    /**
142
     * Aplica los 'wheres' que serán aplicado en la tabla del Modelo
143
     *
144
     * @param  Builder $query
145
     * @return Builder
146
     */
147
    private function applySearchableConstraints($query): Builder
148
    {
149
        $searchableModelInputs = $this->parseModelInputs($this->searchableInputsKeyword);
150
151
        if (count($searchableModelInputs)) {
152
153
            $query = $query->where(function ($query) use ($searchableModelInputs) {
154
155
                foreach ($searchableModelInputs as $attribute) {
156
157
                    $value = Arr::get($this->searchableInputsKeyword, $attribute, $this->searchableKeyword);
158
159
                    if (filter_nullables($value)) {
160
161
                        $query->orWhere($attribute, 'LIKE', "%{$value}")
162
                            ->orWhere($attribute, 'LIKE', "{$value}%")
163
                            ->orWhere($attribute, 'LIKE', "%{$value}%");
164
                    }
165
                }
166
            });
167
        }
168
169
        return $query;
170
    }
171
172
    /**
173
     * Aplica los 'wheres' que serán aplicado en la relaciones del Modelo.
174
     *
175
     * @param  Builder $query
176
     * @return Builder
177
     */
178
    private function applySearchableRelationConstraints($query): Builder
179
    {
180
        if (filter_nullables($this->searchableKeyword)) {
181
182
            $searchableRelationInputs = $this->parseRelationInputs($this->searchableInputsKeyword);
183
184
            $searchableRelation = $this->parseRelationInputs($this->searchableInputs);
185
186
            foreach ($searchableRelationInputs as $attribute => $columns) {
187
188
                if (!in_array($attribute, array_keys($searchableRelation))) {
189
190
                    $query->orWhereHas($attribute, function ($query) use ($attribute, $searchableRelationInputs) {
191
192
                        $query = $query->where(function ($query) use ($attribute, $searchableRelationInputs) {
0 ignored issues
show
Unused Code introduced by
$query is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
193
194
                            $columns = $searchableRelationInputs[$attribute] ?? [];
195
196
                            foreach ($columns as $column) {
197
198
                                $value = $this->searchableInputsKeyword["{$attribute}:{$column}"] ?? $this->searchableKeyword;
199
200
                                if (filter_nullables($value)) {
201
202
                                    $query->orWhere($column, 'LIKE', "%{$value}")
203
                                        ->orWhere($column, 'LIKE', "{$value}%")
204
                                        ->orWhere($column, 'LIKE', "%{$value}%");
205
                                }
206
                            }
207
                        });
208
                    });
209
                }
210
            }
211
        }
212
213
        return $query;
214
    }
215
216
    /**
217
     * Obtiene el operador del where usado en el armado de la consulta de filterable.
218
     *
219
     * @param  mixed $value
220
     * @return array
221
     */
222
    private function getOperatorFilterable($value): array
223
    {
224
        $operator = is_array($value) ? 'whereIn' : 'where';
225
226
        $value = is_array($value) ? array_filter($value, 'filter_nullables') : str_trimmer($value);
227
228
        return [$operator, $value];
229
    }
230
231
    /**
232
     * Aplicación del los where's de los atributos filterable propios del Modelo.
233
     *
234
     * @param  Builder $query
235
     * @return Builder
236
     */
237
    private function applyFilterableConstraints($query): Builder
238
    {
239
        $filterableModelInputs = $this->parseModelInputs($this->filterableInputs);
240
241
        $filterableModelInputs = Arr::only($this->filterableInputs, $filterableModelInputs);
242
243
        foreach ($filterableModelInputs as $column => $value) {
244
245
            list($operator, $value) = $this->getOperatorFilterable($value);
246
247
            if (is_array($value) and !count($value)) {
248
                break;
249
            }
250
251
            $query->{$operator}($column, $value);
252
        }
253
254
        return $query;
255
    }
256
257
    /**
258
     * Añade la relación y su callback en un array para despues ser llamada via whereHas.
259
     *
260
     * @param  Builder $query
261
     * @return Builder
262
     */
263
    private function addRelationSearchableFromConstraint($query): void
0 ignored issues
show
Unused Code introduced by
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
264
    {
265
        $searchableRelationInputs = $this->parseRelationInputs($this->searchableInputs);
266
267
        if (count($searchableRelationInputs)) {
268
269
            foreach ($searchableRelationInputs as $attribute => $columns) {
270
271
                $this->addRelationConstraints([$attribute => function ($query) use ($attribute, $searchableRelationInputs) {
272
273
                    $columns = $searchableRelationInputs[$attribute] ?? [];
274
275
                    foreach ($columns as $column) {
276
277
                        $value = Arr::get($this->searchableInputs, "{$attribute}:{$column}");
278
279
                        if (filter_nullables($value)) {
280
281
                            $query->where(function ($query) use ($column, $value) {
282
283
                                $query->orWhere($column, 'LIKE', "%{$value}")
284
                                    ->orWhere($column, 'LIKE', "{$value}%")
285
                                    ->orWhere($column, 'LIKE', "%{$value}%");
286
                            });
287
                        }
288
                    }
289
                }]);
290
            }
291
        }
292
    }
293
294
    /**
295
     * Añade la relación y su callback en un array para despues ser llamada via whereHas.
296
     *
297
     * @param  Builder $query
298
     * @return Builder
299
     */
300
    private function addRelationFilterableFromConstraint($query): void
0 ignored issues
show
Unused Code introduced by
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
301
    {
302
        $filterableRelationInputs = $this->parseRelationInputs($this->filterableInputs);
303
304
        if (count($filterableRelationInputs)) {
305
306
            foreach ($filterableRelationInputs as $attribute => $columns) {
307
308
                $this->addRelationConstraints([$attribute => function ($query) use ($attribute, $filterableRelationInputs) {
309
310
                    $columns = $filterableRelationInputs[$attribute] ?? [];
311
312
                    foreach ($columns as $column) {
313
314
                        $value = Arr::get($this->filterableInputs, "{$attribute}:{$column}");
315
316
                        list($operator, $value) = $this->getOperatorFilterable($value);
317
318
                        if (is_array($value) and !count($value)) {
319
                            break;
320
                        }
321
322
                        $query->{$operator}($column, $value);
323
                    }
324
                }]);
325
            }
326
        }
327
    }
328
329
    /**
330
     * Parsea los inputs de búsqueda del Modelo.
331
     *
332
     * @param  Builder $query
333
     * @return Builder
334
     */
335
    private function parseInputsKeywordConstraints($query): Builder
336
    {
337
        $query = $query->where(function ($query) {
338
            $this->applySearchableConstraints($query);
339
            $this->applySearchableRelationConstraints($query);
340
        });
341
342
        $query = $this->applyFilterableConstraints($query);
343
344
        /** Añade los closures a un array para ser luego usado cuando se cargan su relaciones */
345
346
        $this->addRelationSearchableFromConstraint($query);
347
348
        $this->addRelationFilterableFromConstraint($query);
349
350
        return $query;
351
    }
352
353
    /**
354
     * Agrupa las closures por cada relación en {relationConstraints}.
355
     *
356
     * @param  array  $relations
357
     * @return void
358
     */
359
    private function addRelationConstraints(array $relations): void
360
    {
361
        foreach ($relations as $name => $closure) {
362
363
            $this->relationConstraints[$name][] = $closure;
364
        }
365
    }
366
367
    /**
368
     * Sí hay closures en las relaciones, aplica al query y agrupalas
369
     * por cada la relación en {eagerRelationConstraints}.
370
     *
371
     * @param  Builder $query
372
     * @return Builder
373
     */
374
    private function parseRelationConstraints($query): Builder
375
    {
376
        if ($this->relationConstraints) {
377
378
            foreach ($this->relationConstraints as $relation => $constraints) {
379
380
                $this->eagerRelationConstraints[$relation] = function ($query) use ($constraints) {
381
382
                    foreach ($constraints as $constraint) {
383
384
                        $constraint($query);
385
                    }
386
                };
387
            }
388
        }
389
390
        return $query;
391
    }
392
393
    /**
394
     * Aplica los 'closures' que estan en {eagerRelationConstraints} por cada relación vía whereHas.
395
     *
396
     * @param  Builder $query
397
     * @return Builder
398
     */
399
    private function loadRelationContraints($query): Builder
400
    {
401
        if ($this->eagerRelationConstraints) {
402
403
            foreach ($this->eagerRelationConstraints as $relation => $closure) {
404
405
                $query->whereHas($relation, $closure);
406
            }
407
        }
408
409
        return $query;
410
    }
411
412
    /**
413
     * Retorna un array con los inputs 'searchables' cuyo valor será el ingresado en la 'keyword'.
414
     *
415
     * @return array
416
     */
417
    private function getInputsKeyword(): array
418
    {
419
        $arrInputs  = [];
420
421
        $searchableInputsFromModel = $this->getInputsFromModel('searchable', 'searchableInputs');
422
423
        if (count($searchableInputsFromModel)) {
424
425
            foreach ($searchableInputsFromModel as $column) {
426
427
                $arrInputs[$column] = $this->searchableKeyword ?: $this->currentRequest->get($column, null);
428
            }
429
430
            $arrInputs = array_keys_replace($arrInputs, $this->getInputsFromModel('searchable', 'searchableInputs', true));
431
        }
432
433
        return $arrInputs;
434
    }
435
436
    /**
437
     * Obtiene los inputs definidos en el Modelo y que se encuentran en el Request.
438
     *
439
     * @param  string $property
440
     * @param  string $method
441
     * @return array
442
     */
443
    private function getInputsFromRequest($property, $method): array
444
    {
445
        $inputsFromModel = $this->getInputsFromModel($property, $method);
446
447
        $filledInputs = array_filter_empty($this->currentRequest->only($inputsFromModel));
448
449
        $filledInputs = array_keys_replace($filledInputs, $this->getInputsFromModel($property, $method, true));
450
451
        $filledInputs = array_filter_recursive($filledInputs, 'filter_nullables');
452
453
        return $filledInputs;
454
    }
455
456
    /**
457
     * Obtiene los inputs definidos en el Model, tanto en la propiedad como método.
458
     *
459
     * @param  string $property
460
     * @param  string $method
461
     * @return array
462
     */
463
    private function getInputsFromModel($property, $method, $associative = false): array
464
    {
465
        $inputs = [];
466
467
        $inputs = property_exists($this, $property) ? Arr::wrap($this->{$property}) : $inputs;
468
469
        $inputs = method_exists($this, $method) ? Arr::wrap($this->{$method}()) : $inputs;
470
471
        if ($associative) {
472
            return $inputs;
473
        }
474
475
        $inputs = Arr::isAssoc($inputs) ? array_keys($inputs) : $inputs;
476
477
        return $inputs;
478
    }
479
480
    /**
481
     * Obtiene los inputs de searchzy (keyword, searchzy, extra) cuyo valor será el de Request o el definido por defecto.
482
     *
483
     * @link    (https://timacdonald.me/query-scopes-meet-action-scopes/)
484
     * @param   Builder $query
485
     * @param   array   $extra
486
     * @param   string  $default
487
     * @return  array
488
     */
489
    public function scopeSearchzyInputs($query, $extra = [], $default = '', $request = null): array
0 ignored issues
show
Unused Code introduced by
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
490
    {
491
        $this->currentRequest = $request ?: request();
492
493
        $searchable  = $this->getInputsFromModel('searchable', 'searchableInputs');
494
495
        $filterable  = $this->getInputsFromModel('filterable', 'filterableInputs');
496
497
        $aditionable = $this->getInputsFromModel('aditionable', 'aditionableInputs');
498
499
        $extra   = Arr::wrap($extra);
500
501
        $keyword = Arr::wrap(config('searchzy.keyword'));
502
503
        $inputs = array_merge($searchable, $filterable, $aditionable, $keyword, $extra);
504
505
        $inputs = array_filler($this->currentRequest->all(), $inputs, $default);
506
507
        return $inputs;
508
    }
509
}
510