Passed
Push β€” feature/optimize ( fc7b3f...d14460 )
by Fu
03:40
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 0
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: guoliang
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
     * @var \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|\Illuminate\Database\Query\Builder
27
     */
28
    private $model;
29
    private $search;
30
    private $searchData;
31
    private $searchFields;
32
    private $isFirstField = true;
33
    private $modelForceAndWhere;
34
    private $fieldsSearchable;
35
    private $fields;
36
    private $filter;
37
    private $orderBy;
38
    private $sortedBy;
39
    private $with;
40
    private $searchJoin;
41
    private $acceptedConditions;
42
    private $originalFields;
43
44
45
    public function __construct(Request $request)
46
    {
47
        $this->request = $request;
48
    }
49
50
    /**
51
     * Apply criteria in query repository
52
     *
53
     * @param Builder|Model $model
54
     * @param RepositoryInterface $repository
55
     *
56
     * @return mixed
57
     * @throws \Exception
58
     */
59
    public function apply($model, RepositoryInterface $repository)
60
    {
61
        $this->model = $model;
62
        $this->fieldsSearchable = $repository->getFieldsSearchable();
63
        $this->search = $this->request->get(config('repository.criteria.params.search', 'search'), null);
64
        $this->searchFields =
65
            $this->request->get(config('repository.criteria.params.searchFields', 'searchFields'), null);
66
        $this->filter = $this->request->get(config('repository.criteria.params.filter', 'filter'), null);
67
        $this->orderBy = $this->request->get(config('repository.criteria.params.orderBy', 'orderBy'), null);
68
        $this->sortedBy = $this->request->get(config('repository.criteria.params.sortedBy', 'sortedBy'), 'asc');
69
        $this->with = $this->request->get(config('repository.criteria.params.with', 'with'), null);
70
        $this->searchJoin = $this->request->get(config('repository.criteria.params.searchJoin', 'searchJoin'), null);
71
        $this->sortedBy = !empty($this->sortedBy) ? $this->sortedBy : 'asc';
72
        $this->acceptedConditions = config('repository.criteria.acceptedConditions', ['=', 'like']);
73
74
        $this->parserSearch();
75
        $this->parserOrderBy();
76
        $this->parserFilter();
77
        $this->parserWith();
78
79
        return $this->model;
80
    }
81
82
    protected function parserSearchData()
83
    {
84
        $searchData = [];
85
86
        if (stripos($this->search, ':')) {
87
            $fields = explode(';', $this->search);
88
89
            foreach ($fields as $row) {
90
                try {
91
                    list($field, $value) = explode(':', $row);
92
                    $searchData[$field] = $value;
93
                } catch (Exception $e) {
94
                    //Surround offset error
95
                }
96
            }
97
        }
98
99
        $this->searchData = $searchData;
100
    }
101
102
    protected function parserSearchValue()
103
    {
104
        if (stripos($this->search, ';') || stripos($this->search, ':')) {
105
            $values = explode(';', $this->search);
106
            foreach ($values as $value) {
107
                $s = explode(':', $value);
108
                if (count($s) == 1) {
109
                    $this->search = $s[0];
110
                }
111
            }
112
113
            $this->search = null;
114
        }
115
    }
116
117
    /**
118
     * @throws \Exception
119
     */
120
    protected function parserFieldsSearch()
121
    {
122
        $this->parserSearchFields();
123
        $fields = $this->fieldsSearchable;
124
125
        if (!is_null($this->searchFields) && is_array($this->searchFields)) {
126
127
            $this->parserOriginalFields();
128
129
            $fields = [];
130
131
            foreach ($this->originalFields as $field => $condition) {
132
                if (is_numeric($field)) {
133
                    $field = $condition;
134
                    $condition = "=";
135
                }
136
                if (in_array($field, (array) $this->searchFields)) {
137
                    $fields[$field] = $condition;
138
                }
139
            }
140
141
            if (count($fields) == 0) {
142
                throw new Exception((string) trans('repository::criteria.fields_not_accepted', ['field' => implode(',', (array) $this->searchFields)]));
143
            }
144
145
        }
146
147
        $this->fields = $fields;
148
    }
149
150
    protected function parserOrderBy()
151
    {
152
        if (isset($this->orderBy) && !empty($this->orderBy)) {
153
            $split = explode('|', $this->orderBy);
154
            if (count($split) > 1) {
155
                $table = $this->model->getModel()->getTable();
156
                $sortTable = $split[0];
157
                $sortColumn = $split[1];
158
159
                $split = explode(':', $sortTable);
160
                if (count($split) > 1) {
161
                    $sortTable = $split[0];
162
                    $keyName = $table.'.'.$split[1];
163
                } else {
164
                    $prefix = Str::singular($sortTable);
165
                    $keyName = $table.'.'.$prefix.'_id';
166
                }
167
168
                $this->model = $this->model->leftJoin($sortTable, $keyName, '=', $sortTable.'.id')
169
                                           ->orderBy($sortColumn, $this->sortedBy)
170
                                           ->addSelect($table.'.*');
171
            } else {
172
                $this->model = $this->model->orderBy($this->orderBy, $this->sortedBy);
173
            }
174
        }
175
    }
176
177
    protected function parserFilter()
178
    {
179
        if (isset($this->filter) && !empty($this->filter)) {
180
            if (is_string($this->filter)) {
181
                $this->filter = explode(';', $this->filter);
182
            }
183
184
            $this->model = $this->model->select($this->filter);
185
        }
186
    }
187
188
    protected function parserWith()
189
    {
190
        if ($this->with) {
191
            $this->with = explode(';', $this->with);
192
            $this->model = $this->model->with($this->with);
193
        }
194
    }
195
196
    /**
197
     * @throws \Exception
198
     */
199
    protected function parserSearch()
200
    {
201
        if ($this->search && is_array($this->fieldsSearchable) && count($this->fieldsSearchable)) {
202
203
            $this->parserFieldsSearch();
204
            $this->parserSearchData();
205
            $this->parserSearchValue();
206
207
            $this->modelForceAndWhere = strtolower($this->searchJoin) === 'and';
208
209
            foreach ($this->fields as $field => $condition) {
210
211
                if (is_numeric($field)) {
212
                    $field = $condition;
213
                    $condition = "=";
214
                }
215
216
                $value = $this->parserValue($condition, $field);
217
218
                $relation = null;
219
                if (stripos($field, '.')) {
220
                    $explode = explode('.', $field);
221
                    $field = array_pop($explode);
222
                    $relation = implode('.', $explode);
223
                }
224
                if ($this->isFirstField || $this->modelForceAndWhere) {
225
                    $this->parserSearchAndWhere($value, $relation, $field, $condition);
226
                } else {
227
                    $this->parserSearchOrWhere($value, $relation, $field, $condition);
228
                }
229
            }
230
        }
231
    }
232
233
    protected function parserSearchFields()
234
    {
235
        if (!is_array($this->searchFields) && !is_null($this->searchFields)) {
236
            $this->searchFields = explode(';', $this->searchFields);
237
        }
238
    }
239
240
    protected function parserOriginalFields()
241
    {
242
        $this->originalFields = $this->fieldsSearchable;
243
244
        foreach ($this->searchFields as $index => $field) {
245
            $field_parts = explode(':', $field);
246
            $temporaryIndex = array_search($field_parts[0], $this->originalFields);
247
248
            if (count($field_parts) == 2) {
249
                if (in_array($field_parts[1], $this->acceptedConditions)) {
250
                    unset($this->originalFields[$temporaryIndex]);
251
                    $field = $field_parts[0];
252
                    $condition = $field_parts[1];
253
                    $this->originalFields[$field] = $condition;
254
                    $this->searchFields[$index] = $field;
255
                }
256
            }
257
        }
258
    }
259
260
    protected function parserSearchAndWhere($value, $relation, $field, $condition)
261
    {
262
        if (!is_null($value)) {
263
            if (!is_null($relation)) {
264
                $this->model =
265
                    $this->model->whereHas($relation, function (Builder $query) use ($field, $condition, $value) {
266
                        switch ($condition) {
267
                            case 'in':
268
                                $query->whereIn($field, $value);
269
                                break;
270
                            case 'between':
271
                                $query->whereBetween($field, $value);
272
                                break;
273
                            case 'cross':
274
                                $query->where(function (Builder $query) use ($field, $value) {
275
                                    $query->where(function (Builder $query) use ($field, $value) {
276
                                        $query->where("{$field}_min", '<=', $value[0])
277
                                              ->where("{$field}_max", '>=', $value[1]);
278
                                    })->orWhere(function (Builder $query) use ($field, $value) {
279
                                        $query->where("{$field}_min", '<=', $value[0])
280
                                              ->where("{$field}_max", '>=', $value[0]);
281
                                    })->orWhere(function (Builder $query) use ($field, $value) {
282
                                        $query->where("{$field}_min", '>=', $value[0])
283
                                              ->where("{$field}_max", '<=', $value[1]);
284
                                    })->orWhere(function (Builder $query) use ($field, $value) {
285
                                        $query->where("{$field}_min", '>=', $value[0])
286
                                              ->where("{$field}_max", '>=', $value[1])
287
                                              ->where("{$field}_min", '<=', $value[1]);
288
                                    });
289
                                });
290
                                break;
291
                            default:
292
                                $query->where($field, $condition, $value);
293
                        }
294
                    });
295
            } else {
296
                switch ($condition) {
297
                    case 'in':
298
                        $this->model = $this->model->whereIn($field, $value);
299
                        break;
300
                    case 'between':
301
                        $this->model = $this->model->whereBetween($field, $value);
302
                        break;
303
                    case 'cross':
304
                        $this->model = $this->model->where(function (Builder $query) use ($field, $value) {
305
                            $query->where(function (Builder $query) use ($field, $value) {
306
                                $query->where("{$field}_min", '<=', $value[0])
307
                                      ->where("{$field}_max", '>=', $value[1]);
308
                            })->orWhere(function (Builder $query) use ($field, $value) {
309
                                $query->where("{$field}_min", '<=', $value[0])
310
                                      ->where("{$field}_max", '>=', $value[0]);
311
                            })->orWhere(function (Builder $query) use ($field, $value) {
312
                                $query->where("{$field}_min", '>=', $value[0])
313
                                      ->where("{$field}_max", '<=', $value[1]);
314
                            })->orWhere(function (Builder $query) use ($field, $value) {
315
                                $query->where("{$field}_min", '>=', $value[0])
316
                                      ->where("{$field}_max", '>=', $value[1])
317
                                      ->where("{$field}_min", '<=', $value[1]);
318
                            });
319
                        });
320
                        break;
321
                    default:
322
                        $this->model = $this->model->where($field, $condition, $value);
323
                }
324
            }
325
            $this->isFirstField = false;
326
        }
327
    }
328
329
    private function parserSearchOrWhere($value, $relation, $field, $condition)
330
    {
331
        $modelTableName = $this->model->getModel()->getTable();
332
        if (!is_null($value)) {
333
            if (!is_null($relation)) {
334
                $this->model = $this->model->orWhereHas($relation, function (Builder $query) use (
335
                    $field,
336
                    $condition,
337
                    $value
338
                ) {
339
                    if ($condition == 'in') {
340
                        $query->whereIn($field, $value);
341
                    } elseif ($condition == 'between') {
342
                        $query->whereBetween($field, $value);
343
                    } elseif ($condition == 'cross') {
344
                        $query->where(function (Builder $query) use ($field, $value) {
345
                            $query->where(function (Builder $query) use ($field, $value) {
346
                                $query->where("{$field}_min", '<=', $value[0])
347
                                      ->where("{$field}_max", '>=', $value[1]);
348
                            })->orWhere(function (Builder $query) use ($field, $value) {
349
                                $query->where("{$field}_min", '<=', $value[0])
350
                                      ->where("{$field}_max", '>=', $value[0]);
351
                            })->orWhere(function (Builder $query) use ($field, $value) {
352
                                $query->where("{$field}_min", '>=', $value[0])
353
                                      ->where("{$field}_max", '<=', $value[1]);
354
                            })->orWhere(function (Builder $query) use ($field, $value) {
355
                                $query->where("{$field}_min", '>=', $value[0])
356
                                      ->where("{$field}_max", '>=', $value[1])
357
                                      ->where("{$field}_min", '<=', $value[1]);
358
                            });
359
                        });
360
                    } else {
361
                        $query->where($field, $condition, $value);
362
                    }
363
                });
364
            } else {
365
                if ($condition == 'in') {
366
                    $this->model = $this->model->orWhereIn($modelTableName.'.'.$field, $value);
367
                } elseif ($condition == 'between') {
368
                    $this->model = $this->model->orWhereBetween($modelTableName.'.'.$field, $value);
369
                } elseif ($condition == 'cross') {
370
                    $this->model = $this->model->orWhere(function (Builder $query) use ($field, $value) {
371
                        $query->where(function (Builder $query) use ($field, $value) {
372
                            $query->where("{$field}_min", '<=', $value[0])
373
                                  ->where("{$field}_max", '>=', $value[1]);
374
                        })->orWhere(function (Builder $query) use ($field, $value) {
375
                            $query->where("{$field}_min", '<=', $value[0])
376
                                  ->where("{$field}_max", '>=', $value[0]);
377
                        })->orWhere(function (Builder $query) use ($field, $value) {
378
                            $query->where("{$field}_min", '>=', $value[0])
379
                                  ->where("{$field}_max", '<=', $value[1]);
380
                        })->orWhere(function (Builder $query) use ($field, $value) {
381
                            $query->where("{$field}_min", '>=', $value[0])
382
                                  ->where("{$field}_max", '>=', $value[1])
383
                                  ->where("{$field}_min", '<=', $value[1]);
384
                        });
385
                    });
386
                } else {
387
                    $this->model = $this->model->orWhere($modelTableName.'.'.$field, $condition, $value);
388
                }
389
            }
390
        }
391
    }
392
393
    protected function parserValue($condition, $field)
394
    {
395
        $value = null;
396
397
        $condition = trim(strtolower($condition));
398
399
        if (isset($this->searchData[$field])) {
400
            $value = $this->searchData[$field];
401
            if ($condition == "like" || $condition == "ilike") {
402
                $value = "%{$value}%";
403
            }
404
            if ($condition == "in" || $condition == "between" || $condition == "cross") {
405
                $value = explode(',', $value);
406
            }
407
        } else {
408
            if (!is_null($this->search)) {
409
                $value = $this->search;
410
                if ($condition == "like" || $condition == "ilike") {
411
                    $value = "%{$value}%";
412
                }
413
                if ($condition == "in" || $condition == "between" || $condition == "cross") {
414
                    $value = explode(',', $value);
415
                }
416
            }
417
        }
418
419
        return $value;
420
    }
421
}
422