Completed
Push — master ( 2dc70f...198f7b )
by Jhorman Alexander
10:37
created

Searchzy::getInputsFromModel()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 16
nop 3
dl 0
loc 12
rs 9.5555
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 todos los inputs searchable del Modelo.
28
     *
29
     * @var array
30
     */
31
    private $searchableInputs = [];
32
33
    /**
34
     * Define el array con todos los inputs filterable del Modelo.
35
     *
36
     * @var array
37
     */
38
    private $filterableInputs = [];
39
40
    /**
41
     * Define el array con todos los inputs adicionales del Modelo.
42
     *
43
     * @var array
44
     */
45
    private $aditionableInputs = [];
46
47
    /**
48
     * Define el valor de la 'keyword' de searchzy.
49
     *
50
     * @var array
51
     */
52
    private $searchableKeyword;
53
54
    /**
55
     * Define el request usado por searchzy.
56
     *
57
     * @var Request
58
     */
59
    private $currentRequest;
60
61
    /**
62
     * Scope que realiza una búsqueda searchzy.
63
     *
64
     * @param  Illuminate\Database\Eloquent\Builder $query
65
     * @return Illuminate\Database\Eloquent\Builder
66
     */
67
    public function scopeSearchzy($query, $keyword = null, $request = null): Builder
68
    {
69
        $keyword = $keyword ?: config('searchzy.keyword');
70
71
        $this->currentRequest = $request ?: request();
72
73
        $this->searchableKeyword = $this->currentRequest->get($keyword, null);
74
75
        $this->searchableInputsKeyword = $this->getInputsKeyword();
0 ignored issues
show
Bug introduced by
The property searchableInputsKeyword does not seem to exist. Did you mean searchableInputs?

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.

Loading history...
76
77
        $this->searchableInputs = $this->getInputsFromRequest('searchable', 'searchableInputs');
78
79
        $this->filterableInputs = $this->getInputsFromRequest('filterable', 'filterableInputs');
80
81
        $query = $this->parseInputsKeywordConstraints($query);
82
83
        $query = $this->parseRelationConstraints($query);
84
85
        $query = $this->loadRelationContraints($query);
86
87
        return $query;
88
    }
89
90
    /**
91
     * Agrupa las relaciones del Modelo. Retorna un array 'arbol' de las relaciones y sus columnas.
92
     *
93
     * @param  array $arrInputs
94
     * @return array
95
     */
96
    private function parseRelationInputs($arrInputs): array
97
    {
98
        $relationInputs = [];
99
100
        foreach (array_keys($arrInputs) as $attribute) {
101
102
            if (Str::contains($attribute, ':')) {
103
104
                [$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...
105
106
                $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...
107
            }
108
        }
109
110
        return $relationInputs;
111
    }
112
113
    /**
114
     * Agrupas las columnas propias del Modelo.
115
     *
116
     * @param  array $arrInputs
117
     * @return array
118
     */
119
    private function parseModelInputs($arrInputs): array
120
    {
121
        $modelInputs = [];
122
123
        foreach (array_keys($arrInputs) as $attribute) {
124
125
            if (!Str::contains($attribute, ':')) {
126
127
                $modelInputs[] = $attribute;
128
            }
129
        }
130
131
        return $modelInputs;
132
    }
133
134
    /**
135
     * Parsea los inputs de búsqueda del Modelo.
136
     *
137
     * @param  Builder $query
138
     * @return Builder
139
     */
140
    private function parseInputsKeywordConstraints($query): Builder
141
    {
142
        // Aplicación del los where's de los atributos searchable propios del Modelo.
143
144
        $searchableModelInputs = $this->parseModelInputs($this->searchableInputsKeyword);
0 ignored issues
show
Bug introduced by
The property searchableInputsKeyword does not seem to exist. Did you mean searchableInputs?

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.

Loading history...
145
146
        $query = $query->where(function ($query) use ($searchableModelInputs) {
147
148
            // Aplicación de los where's en las columnas propias del Modelo, cuyo valor es el del 'keyword'.
149
150
            $query = $query->where(function ($query) use ($searchableModelInputs) {
151
152
                foreach ($searchableModelInputs as $attribute) {
153
154
                    $value = Arr::get($this->searchableInputsKeyword, $attribute, $this->searchableKeyword);
0 ignored issues
show
Bug introduced by
The property searchableInputsKeyword does not seem to exist. Did you mean searchableInputs?

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.

Loading history...
155
156
                    if ($value) {
157
158
                        $query->orWhere($attribute, 'LIKE', "%{$value}")
159
                            ->orWhere($attribute, 'LIKE', "{$value}%")
160
                            ->orWhere($attribute, 'LIKE', "%{$value}%");
161
                    }
162
                }
163
            });
164
165
            // Aplicación de los where's de las relaciones del Modelo, cuyo valor es el del 'keyword'.
166
167
            if ($this->searchableKeyword) {
168
169
                $searchableRelationInputs = $this->parseRelationInputs($this->searchableInputsKeyword);
0 ignored issues
show
Bug introduced by
The property searchableInputsKeyword does not seem to exist. Did you mean searchableInputs?

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.

Loading history...
170
171
                $searchableRelation = $this->parseRelationInputs($this->searchableInputs);
172
173
                foreach ($searchableRelationInputs as $attribute => $columns) {
174
175
                    if (!in_array($attribute, array_keys($searchableRelation))) {
176
177
                        $query->orWhereHas($attribute, function ($query) use ($attribute, $searchableRelationInputs) {
178
179
                            $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...
180
181
                                $columns = $searchableRelationInputs[$attribute] ?? [];
182
183
                                foreach ($columns as $column) {
184
185
                                    $value = $this->searchableInputsKeyword["{$attribute}:{$column}"] ?? $this->searchableKeyword;
0 ignored issues
show
Bug introduced by
The property searchableInputsKeyword does not seem to exist. Did you mean searchableInputs?

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.

Loading history...
186
187
                                        $query->orWhere($column, 'LIKE', "%{$value}")
188
                                            ->orWhere($column, 'LIKE', "{$value}%")
189
                                            ->orWhere($column, 'LIKE', "%{$value}%");
190
                                }
191
                            });
192
                        });
193
                    }
194
                }
195
            }
196
        });
197
198
        // Aplicación del los where's de los atributos filterable propios del Modelo.
199
200
        $filterableModelInputs = $this->parseModelInputs($this->filterableInputs);
201
202
        $filterableModelInputs = Arr::only($this->filterableInputs, $filterableModelInputs);
203
204
        foreach ($filterableModelInputs as $column => $value) {
205
206
            $operator = is_array($value) ? 'whereIn' : 'where';
207
208
            $value    = is_array($value) ? array_filter($value, 'filter_nullables') : str_trimmer($value);
209
210
            if (is_array($value) and !count($value)) {
211
                break;
212
            }
213
214
            $query->{$operator}($column, $value);
215
        }
216
217
        // Se añade los constraints para las relaciones definidads en el searchable del Modelo.
218
219
        $searchableRelationInputs = $this->parseRelationInputs($this->searchableInputs);
220
221
        foreach ($searchableRelationInputs as $attribute => $columns) {
222
223
            $this->addRelationConstraints([$attribute => function ($query) use ($attribute, $searchableRelationInputs) {
224
225
                $columns = $searchableRelationInputs[$attribute] ?? [];
226
227
                foreach ($columns as $column) {
228
229
                    $value = Arr::get($this->searchableInputs, "{$attribute}:{$column}");
230
231
                    $query->where(function ($query) use ($column, $value) {
232
233
                        $query->orWhere($column, 'LIKE', "%{$value}")
234
                            ->orWhere($column, 'LIKE', "{$value}%")
235
                            ->orWhere($column, 'LIKE', "%{$value}%");
236
                    });
237
                }
238
            }]);
239
        }
240
241
        // Se añade los constraints de las relaciones definidads en el filterable del Modelo.
242
243
        $filterableRelationInputs = $this->parseRelationInputs($this->filterableInputs);
244
245
        foreach ($filterableRelationInputs as $attribute => $columns) {
246
247
            $this->addRelationConstraints([$attribute => function ($query) use ($attribute, $filterableRelationInputs) {
248
249
                $columns = $filterableRelationInputs[$attribute] ?? [];
250
251
                foreach ($columns as $column) {
252
253
                    $value = Arr::get($this->filterableInputs, "{$attribute}:{$column}");
254
255
                    $operator = is_array($value) ? 'whereIn' : 'where';
256
257
                    $value    = is_array($value) ? array_filter($value, 'filter_nullables') : str_trimmer($value);
258
259
                    if (is_array($value) and !count($value)) {
260
                        break;
261
                    }
262
263
                    $query->{$operator}($column, $value);
264
                }
265
            }]);
266
        }
267
268
        return $query;
269
    }
270
271
    /**
272
     * Agrupa las closures por cada relación en {relationConstraints}.
273
     *
274
     * @param  array  $relations
275
     * @return void
276
     */
277
    private function addRelationConstraints(array $relations): void
278
    {
279
        foreach ($relations as $name => $closure) {
280
281
            $this->relationConstraints[$name][] = $closure;
282
        }
283
    }
284
285
    /**
286
     * Sí hay closures en las relaciones, aplica al query y agrupalas
287
     * por cada la relación en {eagerRelationConstraints}.
288
     *
289
     * @param  Builder $query
290
     * @return Builder
291
     */
292
    private function parseRelationConstraints($query): Builder
293
    {
294
        if ($this->relationConstraints) {
295
296
            foreach ($this->relationConstraints as $relation => $constraints) {
297
298
                $this->eagerRelationConstraints[$relation] = function ($query) use ($constraints) {
299
300
                    foreach ($constraints as $constraint) {
301
302
                        $constraint($query);
303
                    }
304
                };
305
            }
306
        }
307
308
        return $query;
309
    }
310
311
    /**
312
     * Aplica los 'closures' que estan en {eagerRelationConstraints} por cada relación vía whereHas.
313
     *
314
     * @param  Builder $query
315
     * @return Builder
316
     */
317
    private function loadRelationContraints($query): Builder
318
    {
319
        if ($this->eagerRelationConstraints) {
320
321
            foreach ($this->eagerRelationConstraints as $relation => $closure) {
322
323
                $query->whereHas($relation, $closure);
324
            }
325
        }
326
327
        return $query;
328
    }
329
330
    /**
331
     * Retorna un array con los inputs 'searchables' cuyo valor será el ingresado en la 'keyword'.
332
     *
333
     * @return array
334
     */
335
    private function getInputsKeyword(): array
336
    {
337
        $arrInputs  = [];
338
339
        $searchableInputsFromModel = $this->getInputsFromModel('searchable', 'searchableInputs');
340
341
        if (count($searchableInputsFromModel)) {
342
343
            foreach (array_keys($searchableInputsFromModel) as $column) {
344
345
                $arrInputs[$column] = $this->searchableKeyword ?: $this->currentRequest->get($column, null);
346
            }
347
348
            $arrInputs = array_keys_replace($arrInputs, $searchableInputsFromModel);
349
        }
350
351
        return $arrInputs;
352
    }
353
354
    /**
355
     * Obtiene los inputs definidos en el Modelo y que se encuentran en el Request.
356
     *
357
     * @param  string $property
358
     * @param  string $method
359
     * @return array
360
     */
361
    private function getInputsFromRequest($property, $method): array
0 ignored issues
show
Unused Code introduced by
The parameter $property 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...
Unused Code introduced by
The parameter $method 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...
362
    {
363
        $inputsFromModel = $this->getInputsFromModel('filterable', 'filterableInputs');
364
365
        $filledInputs = array_filter_empty($this->currentRequest->only(array_keys($inputsFromModel)));
366
367
        $filledInputs = array_keys_replace($filledInputs, $inputsFromModel);
368
369
        $filledInputs = array_filter_recursive($filledInputs, 'filter_nullables');
370
371
        return $filledInputs;
372
    }
373
374
    /**
375
     * Obtiene los inputs definidos en el Model, tanto en la propiedad como método.
376
     *
377
     * @param  string $property
378
     * @param  string $method
379
     * @param  bool $keys
380
     * @return array
381
     */
382
    private function getInputsFromModel($property, $method, $keys = false): array
383
    {
384
        $inputs = [];
385
386
        $inputs = property_exists($this, $property) ? Arr::wrap($this->{$property}) : $inputs;
387
388
        $inputs = method_exists($this, $method) ? Arr::wrap($this->{$method}()) : $inputs;
389
390
        $inputs = $keys ? (Arr::isAssoc($inputs) ? array_keys($inputs) : $inputs) : $inputs;
391
392
        return $inputs;
393
    }
394
395
    /**
396
     * Obtiene los inputs de searchzy (keyword, searchzy, extra) cuyo valor será el de Request o el definido por defecto.
397
     *
398
     * @link    (https://timacdonald.me/query-scopes-meet-action-scopes/)
399
     * @param   Builder $query
400
     * @param   array   $extra
401
     * @param   string  $default
402
     * @return  array
403
     */
404
    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...
405
    {
406
        $this->currentRequest = $request ?: request();
407
408
        $searchable  = $this->getInputsFromModel('searchable', 'searchableInputs', true);
409
410
        $filterable  = $this->getInputsFromModel('filterable', 'filterableInputs', true);
411
412
        $aditionable = $this->getInputsFromModel('aditionable', 'aditionableInputs', true);
413
414
        $extra   = Arr::wrap($extra);
415
416
        $keyword = Arr::wrap(config('searchzy.keyword'));
417
418
        $inputs = array_merge($searchable, $filterable, $aditionable, $keyword, $extra);
419
420
        $inputs = array_filler($this->currentRequest->all(), $inputs, $default);
421
422
        return $inputs;
423
    }
424
}
425