Passed
Push β€” feature/optimize ( 55feec...6201d6 )
by Fu
03:45
created

RequestCriteria::parserFieldsSearch()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 14
rs 10
c 0
b 0
f 0
cc 3
nc 2
nop 2
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: Govern Fu
5
 * Date: 2019/3/11
6
 * Time: 上午10:11
7
 */
8
9
namespace Modules\Core\Criteria;
10
11
use Exception;
12
use Illuminate\Database\Eloquent\Builder;
13
use Illuminate\Database\Eloquent\Model;
14
use Illuminate\Http\Request;
15
use Illuminate\Support\Str;
16
use Prettus\Repository\Contracts\CriteriaInterface;
17
use Prettus\Repository\Contracts\RepositoryInterface;
18
19
class RequestCriteria implements CriteriaInterface
20
{
21
    /**
22
     * @var \Illuminate\Http\Request
23
     */
24
    protected $request;
25
26
    public function __construct(Request $request)
27
    {
28
        $this->request = $request;
29
    }
30
31
    /**
32
     * Apply criteria in query repository
33
     *
34
     * @param Builder|Model $model
35
     * @param RepositoryInterface $repository
36
     *
37
     * @return mixed
38
     * @throws \Exception
39
     */
40
    public function apply($model, RepositoryInterface $repository)
41
    {
42
        $model = $this->setSearch($model, $repository);
0 ignored issues
show
Bug introduced by
It seems like $model can also be of type Illuminate\Database\Eloquent\Model; however, parameter $model of Modules\Core\Criteria\RequestCriteria::setSearch() does only seem to accept Illuminate\Database\Eloquent\Builder, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

42
        $model = $this->setSearch(/** @scrutinizer ignore-type */ $model, $repository);
Loading history...
43
        $model = $this->setOrderBy($model);
44
        $model = $this->setFilter($model);
45
        $model = $this->setWith($model);
46
47
        return $model;
48
    }
49
50
    /**
51
     * @param $search
52
     *
53
     * @return array
54
     */
55
    protected function parserSearchData($search)
56
    {
57
        $searchData = [];
58
59
        if (stripos($search, ':')) {
60
            $fields = explode(';', $search);
61
62
            foreach ($fields as $row) {
63
                try {
64
                    list($field, $value) = explode(':', $row);
65
                    $searchData[$field] = $value;
66
                } catch (Exception $e) {
67
                    //Surround offset error
68
                }
69
            }
70
        }
71
72
        return $searchData;
73
    }
74
75
    /**
76
     * @param $search
77
     * @return string|null
78
     */
79
    protected function parserSearchValue($search)
80
    {
81
82
        if (stripos($search, ';') || stripos($search, ':')) {
83
            $values = explode(';', $search);
84
            foreach ($values as $value) {
85
                $s = explode(':', $value);
86
                if (count($s) == 1) {
87
                    return $s[0];
88
                }
89
            }
90
91
            return null;
92
        }
93
94
        return $search;
95
    }
96
97
    /**
98
     * @param array $fields
99
     * @param array|null $searchFields
100
     * @return array
101
     * @throws \Exception
102
     */
103
    protected function parserFieldsSearch(array $fields = [], array $searchFields = null)
104
    {
105
        if (!is_null($searchFields) && count($searchFields)) {
106
            $acceptedConditions = config('repository.criteria.acceptedConditions', [
107
                '=',
108
                'like',
109
            ]);
110
111
            $fields = $this->parserSearchFields($fields, $searchFields, $acceptedConditions);
112
113
            $this->fieldsAccept($fields);
114
        }
115
116
        return $fields;
117
    }
118
119
    /**
120
     * @param $fields
121
     * @throws \Exception
122
     */
123
    protected function fieldsAccept($fields)
124
    {
125
        if (count($fields) == 0) {
126
            throw new Exception((string) trans('repository::criteria.fields_not_accepted', ['field' => implode(',', $searchFields)]));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $searchFields seems to be never defined.
Loading history...
127
        }
128
    }
129
130
    protected function getSubqueryClosure($modelTableName, $field, $condition, $value)
131
    {
132
        switch ($condition) {
133
            case 'in':
134
                return function (Builder $query) use ($modelTableName, $field, $value) {
135
                    $query->whereIn($modelTableName.'.'.$field, $value);
136
                };
137
            case 'between':
138
                return function (Builder $query) use ($modelTableName, $field, $value) {
139
                    $query->whereBetween($modelTableName.'.'.$field, $value);
140
                };
141
            case 'cross':
142
                return function (Builder $query) use ($modelTableName, $field, $value) {
143
                    $query->where(function (Builder $query) use ($modelTableName, $field, $value) {
144
                        $query->where(function (Builder $query) use ($modelTableName, $field, $value) {
145
                            $query->where("{$modelTableName}.{$field}_min", '<=', $value[0])
146
                                  ->where("{$modelTableName}.{$field}_max", '>=', $value[1]);
147
                        })->orWhere(function (Builder $query) use ($modelTableName, $field, $value) {
148
                            $query->where("{$modelTableName}.{$field}_min", '<=', $value[0])
149
                                  ->where("{$modelTableName}.{$field}_max", '>=', $value[0]);
150
                        })->orWhere(function (Builder $query) use ($modelTableName, $field, $value) {
151
                            $query->where("{$modelTableName}.{$field}_min", '>=', $value[0])
152
                                  ->where("{$modelTableName}.{$field}_max", '<=', $value[1]);
153
                        })->orWhere(function (Builder $query) use ($modelTableName, $field, $value) {
154
                            $query->where("{$modelTableName}.{$field}_min", '>=', $value[0])
155
                                  ->where("{$modelTableName}.{$field}_max", '>=', $value[1])
156
                                  ->where("{$modelTableName}.{$field}_min", '<=', $value[1]);
157
                        });
158
                    });
159
                };
160
            default:
161
                return function (Builder $query) use ($modelTableName, $field, $condition, $value) {
162
                    $query->orWhere($modelTableName.'.'.$field, $condition, $value);
163
                };
164
        }
165
    }
166
167
    protected function getQueryClosure($field, $condition, $value)
168
    {
169
        switch ($condition) {
170
            case 'in':
171
                return function (Builder $query) use ($field, $value) {
172
                    $query->whereIn($field, $value);
173
                };
174
            case 'between':
175
                return function (Builder $query) use ($field, $value) {
176
                    $query->whereBetween($field, $value);
177
                };
178
            case 'cross':
179
                return function (Builder $query) use ($field, $value) {
180
                    $query->where(function (Builder $query) use ($field, $value) {
181
                        $query->where(function (Builder $query) use ($field, $value) {
182
                            $query->where("{$field}_min", '<=', $value[0])
183
                                  ->where("{$field}_max", '>=', $value[1]);
184
                        })->orWhere(function (Builder $query) use ($field, $value) {
185
                            $query->where("{$field}_min", '<=', $value[0])
186
                                  ->where("{$field}_max", '>=', $value[0]);
187
                        })->orWhere(function (Builder $query) use ($field, $value) {
188
                            $query->where("{$field}_min", '>=', $value[0])
189
                                  ->where("{$field}_max", '<=', $value[1]);
190
                        })->orWhere(function (Builder $query) use ($field, $value) {
191
                            $query->where("{$field}_min", '>=', $value[0])
192
                                  ->where("{$field}_max", '>=', $value[1])
193
                                  ->where("{$field}_min", '<=', $value[1]);
194
                        });
195
                    });
196
                };
197
            default:
198
                return function (Builder $query) use ($field, $condition, $value) {
199
                    $query->where($field, $condition, $value);
200
                };
201
        }
202
    }
203
204
    protected function getRelationQueryClosure($field, $condition, $value)
205
    {
206
        return function (Builder $query) use (
207
            $field,
208
            $condition,
209
            $value
210
        ) {
211
            $query->where($this->getQueryClosure($field, $condition, $value));
212
        };
213
    }
214
215
    protected function setValue($condition, $field, $search, $searchData)
216
    {
217
        if (isset($searchData[$field])) {
218
            $value = $this->setSearchDataValue($condition, $field, $searchData);
219
        } else {
220
            $value = $this->setSearchValue($condition, $search);
221
        }
222
223
        return $value;
224
    }
225
226
    /**
227
     * @param \Illuminate\Database\Eloquent\Builder $model
228
     * @return mixed
229
     */
230
    protected function setOrderBy($model)
231
    {
232
        $orderBy = $this->request->get(config('repository.criteria.params.orderBy', 'orderBy'), null);
233
        $sortedBy = $this->request->get(config('repository.criteria.params.sortedBy', 'sortedBy'), 'asc');
234
        $sortedBy = !empty($sortedBy) ? $sortedBy : 'asc';
235
236
        if (isset($orderBy) && !empty($orderBy)) {
237
            $split = explode('|', $orderBy);
238
            if (count($split) > 1) {
239
                $model = $this->sort($model, $split, $sortedBy);
240
            } else {
241
                $model = $model->orderBy($orderBy, $sortedBy);
242
            }
243
        }
244
245
        return $model;
246
    }
247
248
    /**
249
     * @param \Illuminate\Database\Eloquent\Builder $model
250
     * @return mixed
251
     */
252
    protected function setFilter($model)
253
    {
254
        $filter = $this->request->get(config('repository.criteria.params.filter', 'filter'), null);
255
256
        if (isset($filter) && !empty($filter)) {
257
            if (is_string($filter)) {
258
                $filter = explode(';', $filter);
259
            }
260
261
            $model = $model->select($filter);
262
        }
263
264
        return $model;
265
    }
266
267
    /**
268
     * @param \Illuminate\Database\Eloquent\Builder $model
269
     * @return mixed
270
     */
271
    protected function setWith($model)
272
    {
273
        $with = $this->request->get(config('repository.criteria.params.with', 'with'), null);
274
275
        if ($with) {
276
            $with = explode(';', $with);
277
            $model = $model->with($with);
278
        }
279
280
        return $model;
281
    }
282
283
    /**
284
     * @param \Illuminate\Database\Eloquent\Builder $model
285
     * @param \Prettus\Repository\Contracts\RepositoryInterface $repository
286
     * @return \Illuminate\Database\Eloquent\Builder
287
     * @throws \Exception
288
     */
289
    protected function setSearch($model, RepositoryInterface $repository)
290
    {
291
        $search = $this->request->get(config('repository.criteria.params.search', 'search'), null);
292
        $searchFields = $this->request->get(config('repository.criteria.params.searchFields', 'searchFields'), null);
293
        $searchJoin = $this->request->get(config('repository.criteria.params.searchJoin', 'searchJoin'), null);
294
295
        $fieldsSearchable = $repository->getFieldsSearchable();
296
297
        if ($search && is_array($fieldsSearchable) && count($fieldsSearchable)) {
298
299
            $searchFields =
300
                is_array($searchFields) || is_null($searchFields) ? $searchFields : explode(';', $searchFields);
301
            $fields = $this->parserFieldsSearch($fieldsSearchable, $searchFields);
302
            $isFirstField = true;
303
            $searchData = $this->parserSearchData($search);
304
            $search = $this->parserSearchValue($search);
305
            $modelForceAndWhere = strtolower($searchJoin) === 'and';
306
307
            $model =
308
                $model->where(function (Builder $query) use (
309
                    $fields,
310
                    $search,
311
                    $searchData,
312
                    $isFirstField,
313
                    $modelForceAndWhere
314
                ) {
315
                    foreach ($fields as $field => $condition) {
316
                        if (is_numeric($field)) {
317
                            $field = $condition;
318
                            $condition = "=";
319
                        }
320
                        $condition = trim(strtolower($condition));
321
                        $value = $this->setValue($condition, $field, $search, $searchData);
322
                        $relation = null;
323
                        if (stripos($field, '.')) {
324
                            $explode = explode('.', $field);
325
                            $field = array_pop($explode);
326
                            $relation = implode('.', $explode);
327
                        }
328
329
                        $query->where($this->searchQueryClosure($isFirstField, $modelForceAndWhere, $relation, $field, $condition, $value));
330
                    }
331
                });
332
        }
333
334
        return $model;
335
    }
336
337
    protected function setSearchDataValue($condition, $field, $searchData)
338
    {
339
        $value = $searchData[$field];
340
        if ($condition == "like" || $condition == "ilike") {
341
            $value = "%{$value}%";
342
        }
343
        if ($condition == "in" || $condition == "between" || $condition == "cross") {
344
            $value = explode(',', $value);
345
        }
346
347
        return $value;
348
    }
349
350
    protected function setSearchValue($condition, $search)
351
    {
352
        $value = null;
353
354
        if (!is_null($search)) {
355
            $value = $search;
356
            if ($condition == "like" || $condition == "ilike") {
357
                $value = "%{$value}%";
358
            }
359
            if ($condition == "in" || $condition == "between" || $condition == "cross") {
360
                $value = explode(',', $value);
361
            }
362
        }
363
364
        return $value;
365
    }
366
367
    protected function parserSearchFields($fields, $searchFields, $acceptedConditions)
368
    {
369
        $originalFields = $fields;
370
        $fields = [];
371
372
        foreach ($searchFields as $index => $field) {
373
            $field_parts = explode(':', $field);
374
            $temporaryIndex = array_search($field_parts[0], $originalFields);
375
376
            if (count($field_parts) == 2 && in_array($field_parts[1], $acceptedConditions)) {
377
                unset($originalFields[$temporaryIndex]);
378
                $field = $field_parts[0];
379
                $condition = $field_parts[1];
380
                $originalFields[$field] = $condition;
381
                $searchFields[$index] = $field;
382
            }
383
        }
384
385
        foreach ($originalFields as $field => $condition) {
386
            if (is_numeric($field)) {
387
                $field = $condition;
388
                $condition = "=";
389
            }
390
            if (in_array($field, $searchFields)) {
391
                $fields[$field] = $condition;
392
            }
393
        }
394
395
        return $fields;
396
    }
397
398
    /**
399
     * @param \Illuminate\Database\Eloquent\Builder $model
400
     * @param $split
401
     * @param $sortedBy
402
     * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
403
     */
404
    protected function sort($model, $split, $sortedBy)
405
    {
406
        $table = $model->getModel()->getTable();
407
        $sortTable = $split[0];
408
        $sortColumn = $split[1];
409
410
        $split = explode(':', $sortTable);
411
        if (count($split) > 1) {
412
            $sortTable = $split[0];
413
            $keyName = $table.'.'.$split[1];
414
        } else {
415
            $prefix = Str::singular($sortTable);
416
            $keyName = $table.'.'.$prefix.'_id';
417
        }
418
419
        return $model
420
            ->leftJoin($sortTable, $keyName, '=', $sortTable.'.id')
421
            ->orderBy($sortColumn, $sortedBy)
422
            ->addSelect($table.'.*');
423
    }
424
425
    protected function searchQueryClosure(&$isFirstField, $modelForceAndWhere, $relation, $field, $condition, $value)
426
    {
427
        return function (Builder $query) use (
428
            $isFirstField,
429
            $modelForceAndWhere,
430
            $relation,
431
            $field,
432
            $condition,
433
            $value
434
        ) {
435
            $modelTableName = $query->getModel()->getTable();
436
            if ($isFirstField || $modelForceAndWhere) {
437
                if (!is_null($value)) {
438
                    if (!is_null($relation)) {
439
                        $query->whereHas($relation, $this->getRelationQueryClosure($field, $condition, $value));
440
                    } else {
441
                        $query->where($this->getQueryClosure($field, $condition, $value));
442
                    }
443
                    $isFirstField = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $isFirstField is dead and can be removed.
Loading history...
444
                }
445
            } else {
446
                if (!is_null($value)) {
447
                    if (!is_null($relation)) {
448
                        $query->orWhereHas($relation, $this->getRelationQueryClosure($field, $condition, $value));
449
                    } else {
450
                        $query->orWhere($this->getSubqueryClosure($modelTableName, $field, $condition, $value));
451
                    }
452
                }
453
            }
454
        };
455
    }
456
}
457