Passed
Push β€” feature/optimize ( d14460...219eef )
by Fu
03:23
created

RequestCriteria::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
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->parserSearchable();
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 parserSearch()
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 parserSearchable()
200
    {
201
        if ($this->search && is_array($this->fieldsSearchable) && count($this->fieldsSearchable)) {
202
203
            $this->parserFieldsSearch();
204
            $this->parserSearchData();
205
            $this->parserSearch();
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->parserSearchAndRelationClosure($value, $relation, $field, $condition);
265
            } else {
266
                $this->parserSearchAndClosure($value, $field, $condition);
267
            }
268
            $this->isFirstField = false;
269
        }
270
    }
271
272
    private function parserSearchOrWhere($value, $relation, $field, $condition)
273
    {
274
        if (!is_null($value)) {
275
            if (!is_null($relation)) {
276
                $this->parserSearchOrRelationClosure($value, $relation, $field, $condition);
277
            } else {
278
                $this->parserSearchOrClosure($value, $field, $condition);
279
            }
280
        }
281
    }
282
283
    protected function parserValue($condition, $field)
284
    {
285
        $condition = trim(strtolower($condition));
286
287
        if (isset($this->searchData[$field])) {
288
            $value = $this->parserSearchDataValue($field, $condition);
289
        } else {
290
            $value = $this->parserSearchValue($condition);
291
        }
292
293
        return $value;
294
    }
295
296
    protected function parserSearchDataValue($field, $condition)
297
    {
298
        $value = $this->searchData[$field];
299
300
        if ($condition == "like" || $condition == "ilike") {
301
            $value = "%{$value}%";
302
        }
303
        if ($condition == "in" || $condition == "between" || $condition == "cross") {
304
            $value = explode(',', $value);
305
        }
306
307
        return $value;
308
    }
309
310
    protected function parserSearchValue($condition)
311
    {
312
        $value = null;
313
314
        if (!is_null($this->search)) {
315
            $value = $this->search;
316
            if ($condition == "like" || $condition == "ilike") {
317
                $value = "%{$value}%";
318
            }
319
            if ($condition == "in" || $condition == "between" || $condition == "cross") {
320
                $value = explode(',', $value);
321
            }
322
        }
323
324
        return $value;
325
    }
326
327
    protected function parserSearchAndRelationClosure($value, $relation, $field, $condition)
328
    {
329
        $this->model =
330
            $this->model->whereHas($relation, function (Builder $query) use ($field, $condition, $value) {
331
                switch ($condition) {
332
                    case 'in':
333
                        $query->whereIn($field, $value);
334
                        break;
335
                    case 'between':
336
                        $query->whereBetween($field, $value);
337
                        break;
338
                    case 'cross':
339
                        $query->where(function (Builder $query) use ($field, $value) {
340
                            $query->where(function (Builder $query) use ($field, $value) {
341
                                $query->where("{$field}_min", '<=', $value[0])
342
                                      ->where("{$field}_max", '>=', $value[1]);
343
                            })->orWhere(function (Builder $query) use ($field, $value) {
344
                                $query->where("{$field}_min", '<=', $value[0])
345
                                      ->where("{$field}_max", '>=', $value[0]);
346
                            })->orWhere(function (Builder $query) use ($field, $value) {
347
                                $query->where("{$field}_min", '>=', $value[0])
348
                                      ->where("{$field}_max", '<=', $value[1]);
349
                            })->orWhere(function (Builder $query) use ($field, $value) {
350
                                $query->where("{$field}_min", '>=', $value[0])
351
                                      ->where("{$field}_max", '>=', $value[1])
352
                                      ->where("{$field}_min", '<=', $value[1]);
353
                            });
354
                        });
355
                        break;
356
                    default:
357
                        $query->where($field, $condition, $value);
358
                }
359
            });
360
    }
361
362
    protected function parserSearchAndClosure($value, $field, $condition)
363
    {
364
        switch ($condition) {
365
            case 'in':
366
                $this->model = $this->model->whereIn($field, $value);
367
                break;
368
            case 'between':
369
                $this->model = $this->model->whereBetween($field, $value);
370
                break;
371
            case 'cross':
372
                $this->model = $this->model->where(function (Builder $query) use ($field, $value) {
373
                    $query->where(function (Builder $query) use ($field, $value) {
374
                        $query->where("{$field}_min", '<=', $value[0])
375
                              ->where("{$field}_max", '>=', $value[1]);
376
                    })->orWhere(function (Builder $query) use ($field, $value) {
377
                        $query->where("{$field}_min", '<=', $value[0])
378
                              ->where("{$field}_max", '>=', $value[0]);
379
                    })->orWhere(function (Builder $query) use ($field, $value) {
380
                        $query->where("{$field}_min", '>=', $value[0])
381
                              ->where("{$field}_max", '<=', $value[1]);
382
                    })->orWhere(function (Builder $query) use ($field, $value) {
383
                        $query->where("{$field}_min", '>=', $value[0])
384
                              ->where("{$field}_max", '>=', $value[1])
385
                              ->where("{$field}_min", '<=', $value[1]);
386
                    });
387
                });
388
                break;
389
            default:
390
                $this->model = $this->model->where($field, $condition, $value);
391
        }
392
    }
393
394
    protected function parserSearchOrRelationClosure($value, $relation, $field, $condition)
395
    {
396
        $this->model = $this->model->orWhereHas($relation, function (Builder $query) use ($field, $condition, $value) {
397
            switch ($condition) {
398
                case 'in':
399
                    $query->whereIn($field, $value);
400
                    break;
401
                case 'between':
402
                    $query->whereBetween($field, $value);
403
                    break;
404
                case 'cross':
405
                    $query->where(function (Builder $query) use ($field, $value) {
406
                        $query->where(function (Builder $query) use ($field, $value) {
407
                            $query->where("{$field}_min", '<=', $value[0])
408
                                  ->where("{$field}_max", '>=', $value[1]);
409
                        })->orWhere(function (Builder $query) use ($field, $value) {
410
                            $query->where("{$field}_min", '<=', $value[0])
411
                                  ->where("{$field}_max", '>=', $value[0]);
412
                        })->orWhere(function (Builder $query) use ($field, $value) {
413
                            $query->where("{$field}_min", '>=', $value[0])
414
                                  ->where("{$field}_max", '<=', $value[1]);
415
                        })->orWhere(function (Builder $query) use ($field, $value) {
416
                            $query->where("{$field}_min", '>=', $value[0])
417
                                  ->where("{$field}_max", '>=', $value[1])
418
                                  ->where("{$field}_min", '<=', $value[1]);
419
                        });
420
                    });
421
                    break;
422
                default:
423
                    $query->where($field, $condition, $value);
424
            }
425
        });
426
    }
427
428
    protected function parserSearchOrClosure($value, $field, $condition)
429
    {
430
        $modelTableName = $this->model->getModel()->getTable();
431
        switch ($condition) {
432
            case 'in':
433
                $this->model = $this->model->orWhereIn($modelTableName.'.'.$field, $value);
434
                break;
435
            case 'between':
436
                $this->model = $this->model->orWhereBetween($modelTableName.'.'.$field, $value);
437
                break;
438
            case 'cross':
439
                $this->model = $this->model->orWhere(function (Builder $query) use ($field, $value) {
440
                    $query->where(function (Builder $query) use ($field, $value) {
441
                        $query->where("{$field}_min", '<=', $value[0])
442
                              ->where("{$field}_max", '>=', $value[1]);
443
                    })->orWhere(function (Builder $query) use ($field, $value) {
444
                        $query->where("{$field}_min", '<=', $value[0])
445
                              ->where("{$field}_max", '>=', $value[0]);
446
                    })->orWhere(function (Builder $query) use ($field, $value) {
447
                        $query->where("{$field}_min", '>=', $value[0])
448
                              ->where("{$field}_max", '<=', $value[1]);
449
                    })->orWhere(function (Builder $query) use ($field, $value) {
450
                        $query->where("{$field}_min", '>=', $value[0])
451
                              ->where("{$field}_max", '>=', $value[1])
452
                              ->where("{$field}_min", '<=', $value[1]);
453
                    });
454
                });
455
                break;
456
            default:
457
                $this->model = $this->model->orWhere($modelTableName.'.'.$field, $condition, $value);
458
        }
459
    }
460
}
461