Passed
Push β€” feature/optimize ( 06281d...55feec )
by Fu
04:11
created

RequestCriteria::parserSearchData()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 18
rs 9.9666
c 0
b 0
f 0
cc 4
nc 2
nop 1
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
            $originalFields = $fields;
111
            $fields = [];
112
113
            foreach ($searchFields as $index => $field) {
114
                $field_parts = explode(':', $field);
115
                $temporaryIndex = array_search($field_parts[0], $originalFields);
116
117
                if (count($field_parts) == 2 && in_array($field_parts[1], $acceptedConditions)) {
118
                    unset($originalFields[$temporaryIndex]);
119
                    $field = $field_parts[0];
120
                    $condition = $field_parts[1];
121
                    $originalFields[$field] = $condition;
122
                    $searchFields[$index] = $field;
123
                }
124
            }
125
126
            foreach ($originalFields as $field => $condition) {
127
                if (is_numeric($field)) {
128
                    $field = $condition;
129
                    $condition = "=";
130
                }
131
                if (in_array($field, $searchFields)) {
132
                    $fields[$field] = $condition;
133
                }
134
            }
135
136
            $this->fieldsAccept($fields);
137
        }
138
139
        return $fields;
140
    }
141
142
    /**
143
     * @param $fields
144
     * @throws \Exception
145
     */
146
    protected function fieldsAccept($fields)
147
    {
148
        if (count($fields) == 0) {
149
            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...
150
        }
151
    }
152
153
    protected function getSubqueryClosure($modelTableName, $field, $condition, $value)
154
    {
155
        switch ($condition) {
156
            case 'in':
157
                return function (Builder $query) use ($modelTableName, $field, $value) {
158
                    $query->whereIn($modelTableName.'.'.$field, $value);
159
                };
160
            case 'between':
161
                return function (Builder $query) use ($modelTableName, $field, $value) {
162
                    $query->whereBetween($modelTableName.'.'.$field, $value);
163
                };
164
            case 'cross':
165
                return function (Builder $query) use ($modelTableName, $field, $value) {
166
                    $query->where(function (Builder $query) use ($modelTableName, $field, $value) {
167
                        $query->where(function (Builder $query) use ($modelTableName, $field, $value) {
168
                            $query->where("{$modelTableName}.{$field}_min", '<=', $value[0])
169
                                  ->where("{$modelTableName}.{$field}_max", '>=', $value[1]);
170
                        })->orWhere(function (Builder $query) use ($modelTableName, $field, $value) {
171
                            $query->where("{$modelTableName}.{$field}_min", '<=', $value[0])
172
                                  ->where("{$modelTableName}.{$field}_max", '>=', $value[0]);
173
                        })->orWhere(function (Builder $query) use ($modelTableName, $field, $value) {
174
                            $query->where("{$modelTableName}.{$field}_min", '>=', $value[0])
175
                                  ->where("{$modelTableName}.{$field}_max", '<=', $value[1]);
176
                        })->orWhere(function (Builder $query) use ($modelTableName, $field, $value) {
177
                            $query->where("{$modelTableName}.{$field}_min", '>=', $value[0])
178
                                  ->where("{$modelTableName}.{$field}_max", '>=', $value[1])
179
                                  ->where("{$modelTableName}.{$field}_min", '<=', $value[1]);
180
                        });
181
                    });
182
                };
183
            default:
184
                return function (Builder $query) use ($modelTableName, $field, $condition, $value) {
185
                    $query->orWhere($modelTableName.'.'.$field, $condition, $value);
186
                };
187
        }
188
    }
189
190
    protected function getQueryClosure($field, $condition, $value)
191
    {
192
        switch ($condition) {
193
            case 'in':
194
                return function (Builder $query) use ($field, $value) {
195
                    $query->whereIn($field, $value);
196
                };
197
            case 'between':
198
                return function (Builder $query) use ($field, $value) {
199
                    $query->whereBetween($field, $value);
200
                };
201
            case 'cross':
202
                return function (Builder $query) use ($field, $value) {
203
                    $query->where(function (Builder $query) use ($field, $value) {
204
                        $query->where(function (Builder $query) use ($field, $value) {
205
                            $query->where("{$field}_min", '<=', $value[0])
206
                                  ->where("{$field}_max", '>=', $value[1]);
207
                        })->orWhere(function (Builder $query) use ($field, $value) {
208
                            $query->where("{$field}_min", '<=', $value[0])
209
                                  ->where("{$field}_max", '>=', $value[0]);
210
                        })->orWhere(function (Builder $query) use ($field, $value) {
211
                            $query->where("{$field}_min", '>=', $value[0])
212
                                  ->where("{$field}_max", '<=', $value[1]);
213
                        })->orWhere(function (Builder $query) use ($field, $value) {
214
                            $query->where("{$field}_min", '>=', $value[0])
215
                                  ->where("{$field}_max", '>=', $value[1])
216
                                  ->where("{$field}_min", '<=', $value[1]);
217
                        });
218
                    });
219
                };
220
            default:
221
                return function (Builder $query) use ($field, $condition, $value) {
222
                    $query->where($field, $condition, $value);
223
                };
224
        }
225
    }
226
227
    protected function getRelationQueryClosure($field, $condition, $value)
228
    {
229
        return function (Builder $query) use (
230
            $field,
231
            $condition,
232
            $value
233
        ) {
234
            $query->where($this->getQueryClosure($field, $condition, $value));
235
        };
236
    }
237
238
    protected function setValue($condition, $field, $search, $searchData)
239
    {
240
        $value = null;
241
242
        if (isset($searchData[$field])) {
243
            $value = $searchData[$field];
244
            if ($condition == "like" || $condition == "ilike") {
245
                $value = "%{$value}%";
246
            }
247
            if ($condition == "in" || $condition == "between" || $condition == "cross") {
248
                $value = explode(',', $value);
249
            }
250
        } else {
251
            if (!is_null($search)) {
252
                $value = $search;
253
                if ($condition == "like" || $condition == "ilike") {
254
                    $value = "%{$value}%";
255
                }
256
                if ($condition == "in" || $condition == "between" || $condition == "cross") {
257
                    $value = explode(',', $value);
258
                }
259
            }
260
        }
261
262
        return $value;
263
    }
264
265
    /**
266
     * @param \Illuminate\Database\Eloquent\Builder $model
267
     * @return mixed
268
     */
269
    protected function setOrderBy($model)
270
    {
271
        $orderBy = $this->request->get(config('repository.criteria.params.orderBy', 'orderBy'), null);
272
        $sortedBy = $this->request->get(config('repository.criteria.params.sortedBy', 'sortedBy'), 'asc');
273
        $sortedBy = !empty($sortedBy) ? $sortedBy : 'asc';
274
275
        if (isset($orderBy) && !empty($orderBy)) {
276
            $split = explode('|', $orderBy);
277
            if (count($split) > 1) {
278
                $table = $model->getModel()->getTable();
279
                $sortTable = $split[0];
280
                $sortColumn = $split[1];
281
282
                $split = explode(':', $sortTable);
283
                if (count($split) > 1) {
284
                    $sortTable = $split[0];
285
                    $keyName = $table.'.'.$split[1];
286
                } else {
287
                    $prefix = Str::singular($sortTable);
288
                    $keyName = $table.'.'.$prefix.'_id';
289
                }
290
291
                $model = $model
292
                    ->leftJoin($sortTable, $keyName, '=', $sortTable.'.id')
293
                    ->orderBy($sortColumn, $sortedBy)
294
                    ->addSelect($table.'.*');
295
            } else {
296
                $model = $model->orderBy($orderBy, $sortedBy);
297
            }
298
        }
299
300
        return $model;
301
    }
302
303
    /**
304
     * @param \Illuminate\Database\Eloquent\Builder $model
305
     * @return mixed
306
     */
307
    protected function setFilter($model)
308
    {
309
        $filter = $this->request->get(config('repository.criteria.params.filter', 'filter'), null);
310
311
        if (isset($filter) && !empty($filter)) {
312
            if (is_string($filter)) {
313
                $filter = explode(';', $filter);
314
            }
315
316
            $model = $model->select($filter);
317
        }
318
319
        return $model;
320
    }
321
322
    /**
323
     * @param \Illuminate\Database\Eloquent\Builder $model
324
     * @return mixed
325
     */
326
    protected function setWith($model)
327
    {
328
        $with = $this->request->get(config('repository.criteria.params.with', 'with'), null);
329
330
        if ($with) {
331
            $with = explode(';', $with);
332
            $model = $model->with($with);
333
        }
334
335
        return $model;
336
    }
337
338
    /**
339
     * @param \Illuminate\Database\Eloquent\Builder $model
340
     * @param \Prettus\Repository\Contracts\RepositoryInterface $repository
341
     * @return \Illuminate\Database\Eloquent\Builder
342
     * @throws \Exception
343
     */
344
    protected function setSearch($model, RepositoryInterface $repository)
345
    {
346
        $search = $this->request->get(config('repository.criteria.params.search', 'search'), null);
347
        $searchFields = $this->request->get(config('repository.criteria.params.searchFields', 'searchFields'), null);
348
        $searchJoin = $this->request->get(config('repository.criteria.params.searchJoin', 'searchJoin'), null);
349
350
        $fieldsSearchable = $repository->getFieldsSearchable();
351
352
        if ($search && is_array($fieldsSearchable) && count($fieldsSearchable)) {
353
354
            $searchFields =
355
                is_array($searchFields) || is_null($searchFields) ? $searchFields : explode(';', $searchFields);
356
            $fields = $this->parserFieldsSearch($fieldsSearchable, $searchFields);
357
            $isFirstField = true;
358
            $searchData = $this->parserSearchData($search);
359
            $search = $this->parserSearchValue($search);
360
            $modelForceAndWhere = strtolower($searchJoin) === 'and';
361
362
            $model =
363
                $model->where(function (Builder $query) use (
364
                    $fields,
365
                    $search,
366
                    $searchData,
367
                    $isFirstField,
368
                    $modelForceAndWhere
369
                ) {
370
                    foreach ($fields as $field => $condition) {
371
372
                        if (is_numeric($field)) {
373
                            $field = $condition;
374
                            $condition = "=";
375
                        }
376
377
                        $condition = trim(strtolower($condition));
378
379
                        $value = $this->setValue($condition, $field, $search, $searchData);
380
381
                        $relation = null;
382
                        if (stripos($field, '.')) {
383
                            $explode = explode('.', $field);
384
                            $field = array_pop($explode);
385
                            $relation = implode('.', $explode);
386
                        }
387
                        $modelTableName = $query->getModel()->getTable();
388
                        if ($isFirstField || $modelForceAndWhere) {
389
                            if (!is_null($value)) {
390
                                if (!is_null($relation)) {
391
                                    $query->whereHas($relation, $this->getRelationQueryClosure($field, $condition, $value));
392
                                } else {
393
                                    $query->where($this->getQueryClosure($field, $condition, $value));
394
                                }
395
                                $isFirstField = false;
396
                            }
397
                        } else {
398
                            if (!is_null($value)) {
399
                                if (!is_null($relation)) {
400
                                    $query->orWhereHas($relation, $this->getRelationQueryClosure($field, $condition, $value));
401
                                } else {
402
                                    $query->orWhere($this->getSubqueryClosure($modelTableName, $field, $condition, $value));
403
                                }
404
                            }
405
                        }
406
                    }
407
                });
408
        }
409
410
        return $model;
411
    }
412
}
413