Passed
Push β€” feature/optimize ( 06281d )
by Fu
03:39
created

RequestCriteria::getQueryClosure()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 33
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 26
dl 0
loc 33
rs 9.504
c 0
b 0
f 0
cc 4
nc 4
nop 3
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
        /**
43
         * @var $search
44
         * @var $searchFields
45
         * @var $filter
46
         * @var $orderBy
47
         * @var $sortedBy
48
         * @var $with
49
         * @var $searchJoin
50
         */
51
        extract($this->getConfigs());
52
53
        $fieldsSearchable = $repository->getFieldsSearchable();
54
        $sortedBy = !empty($sortedBy) ? $sortedBy : 'asc';
55
56
        if ($search && is_array($fieldsSearchable) && count($fieldsSearchable)) {
57
58
            $searchFields =
59
                is_array($searchFields) || is_null($searchFields) ? $searchFields : explode(';', $searchFields);
60
            $fields = $this->parserFieldsSearch($fieldsSearchable, $searchFields);
61
            $isFirstField = true;
62
            $searchData = $this->parserSearchData($search);
63
            $search = $this->parserSearchValue($search);
64
            $modelForceAndWhere = strtolower($searchJoin) === 'and';
65
66
            $model =
67
                $model->where(function ($query) use (
68
                    $fields,
69
                    $search,
70
                    $searchData,
71
                    $isFirstField,
72
                    $modelForceAndWhere
73
                ) {
74
                    /** @var Builder $query */
75
76
                    foreach ($fields as $field => $condition) {
77
78
                        if (is_numeric($field)) {
79
                            $field = $condition;
80
                            $condition = "=";
81
                        }
82
83
                        $value = null;
84
85
                        $condition = trim(strtolower($condition));
86
87
                        if (isset($searchData[$field])) {
88
                            $value = $searchData[$field];
89
                            if ($condition == "like" || $condition == "ilike") {
90
                                $value = "%{$value}%";
91
                            }
92
                            if ($condition == "in" || $condition == "between" || $condition == "cross") {
93
                                $value = explode(',', $value);
94
                            }
95
                        } else {
96
                            if (!is_null($search)) {
97
                                $value = $search;
98
                                if ($condition == "like" || $condition == "ilike") {
99
                                    $value = "%{$value}%";
100
                                }
101
                                if ($condition == "in" || $condition == "between" || $condition == "cross") {
102
                                    $value = explode(',', $value);
103
                                }
104
                            }
105
                        }
106
107
                        $relation = null;
108
                        if (stripos($field, '.')) {
109
                            $explode = explode('.', $field);
110
                            $field = array_pop($explode);
111
                            $relation = implode('.', $explode);
112
                        }
113
                        $modelTableName = $query->getModel()->getTable();
114
                        if ($isFirstField || $modelForceAndWhere) {
115
                            if (!is_null($value)) {
116
                                if (!is_null($relation)) {
117
                                    $query->whereHas($relation, $this->getRelationQueryClosure($field, $condition, $value));
118
                                } else {
119
                                    $query->where($this->getQueryClosure($field, $condition, $value));
120
                                }
121
                                $isFirstField = false;
122
                            }
123
                        } else {
124
                            if (!is_null($value)) {
125
                                if (!is_null($relation)) {
126
                                    $query->orWhereHas($relation, $this->getRelationQueryClosure($field, $condition, $value));
127
                                } else {
128
                                    $query->orWhere($this->getSubqueryClosure($modelTableName, $field, $condition, $value));
129
                                }
130
                            }
131
                        }
132
                    }
133
                });
134
        }
135
136
        if (isset($orderBy) && !empty($orderBy)) {
137
            $split = explode('|', $orderBy);
138
            if (count($split) > 1) {
139
                /*
140
                 * ex.
141
                 * products|description -> join products on current_table.product_id = products.id order by description
142
                 *
143
                 * products:custom_id|products.description -> join products on current_table.custom_id = products.id order
144
                 * by products.description (in case both tables have same column name)
145
                 */
146
                $table = $model->getModel()->getTable();
147
                $sortTable = $split[0];
148
                $sortColumn = $split[1];
149
150
                $split = explode(':', $sortTable);
151
                if (count($split) > 1) {
152
                    $sortTable = $split[0];
153
                    $keyName = $table.'.'.$split[1];
154
                } else {
155
                    /*
156
                     * If you do not define which column to use as a joining column on current table, it will
157
                     * use a singular of a join table appended with _id
158
                     *
159
                     * ex.
160
                     * products -> product_id
161
                     */
162
                    $prefix = Str::singular($sortTable);
163
                    $keyName = $table.'.'.$prefix.'_id';
164
                }
165
166
                $model = $model
167
                    ->leftJoin($sortTable, $keyName, '=', $sortTable.'.id')
168
                    ->orderBy($sortColumn, $sortedBy)
169
                    ->addSelect($table.'.*');
170
            } else {
171
                $model = $model->orderBy($orderBy, $sortedBy);
172
            }
173
        }
174
175
        if (isset($filter) && !empty($filter)) {
176
            if (is_string($filter)) {
177
                $filter = explode(';', $filter);
178
            }
179
180
            $model = $model->select($filter);
181
        }
182
183
        if ($with) {
184
            $with = explode(';', $with);
185
            $model = $model->with($with);
186
        }
187
188
        return $model;
189
    }
190
191
    /**
192
     * @param $search
193
     *
194
     * @return array
195
     */
196
    protected function parserSearchData($search)
197
    {
198
        $searchData = [];
199
200
        if (stripos($search, ':')) {
201
            $fields = explode(';', $search);
202
203
            foreach ($fields as $row) {
204
                try {
205
                    list($field, $value) = explode(':', $row);
206
                    $searchData[$field] = $value;
207
                } catch (Exception $e) {
208
                    //Surround offset error
209
                }
210
            }
211
        }
212
213
        return $searchData;
214
    }
215
216
    /**
217
     * @param $search
218
     * @return string|null
219
     */
220
    protected function parserSearchValue($search)
221
    {
222
223
        if (stripos($search, ';') || stripos($search, ':')) {
224
            $values = explode(';', $search);
225
            foreach ($values as $value) {
226
                $s = explode(':', $value);
227
                if (count($s) == 1) {
228
                    return $s[0];
229
                }
230
            }
231
232
            return null;
233
        }
234
235
        return $search;
236
    }
237
238
    /**
239
     * @param array $fields
240
     * @param array|null $searchFields
241
     * @return array
242
     * @throws \Exception
243
     */
244
    protected function parserFieldsSearch(array $fields = [], array $searchFields = null)
245
    {
246
        if (!is_null($searchFields) && count($searchFields)) {
247
            $acceptedConditions = config('repository.criteria.acceptedConditions', [
248
                '=',
249
                'like',
250
            ]);
251
            $originalFields = $fields;
252
            $fields = [];
253
254
            foreach ($searchFields as $index => $field) {
255
                $field_parts = explode(':', $field);
256
                $temporaryIndex = array_search($field_parts[0], $originalFields);
257
258
                if (count($field_parts) == 2 && in_array($field_parts[1], $acceptedConditions)) {
259
                    unset($originalFields[$temporaryIndex]);
260
                    $field = $field_parts[0];
261
                    $condition = $field_parts[1];
262
                    $originalFields[$field] = $condition;
263
                    $searchFields[$index] = $field;
264
                }
265
            }
266
267
            foreach ($originalFields as $field => $condition) {
268
                if (is_numeric($field)) {
269
                    $field = $condition;
270
                    $condition = "=";
271
                }
272
                if (in_array($field, $searchFields)) {
273
                    $fields[$field] = $condition;
274
                }
275
            }
276
277
            if (count($fields) == 0) {
278
                throw new Exception((string) trans('repository::criteria.fields_not_accepted', ['field' => implode(',', $searchFields)]));
279
            }
280
281
        }
282
283
        return $fields;
284
    }
285
286
    protected function getConfigs()
287
    {
288
        $search = $this->request->get(config('repository.criteria.params.search', 'search'), null);
289
        $searchFields = $this->request->get(config('repository.criteria.params.searchFields', 'searchFields'), null);
290
        $filter = $this->request->get(config('repository.criteria.params.filter', 'filter'), null);
291
        $orderBy = $this->request->get(config('repository.criteria.params.orderBy', 'orderBy'), null);
292
        $sortedBy = $this->request->get(config('repository.criteria.params.sortedBy', 'sortedBy'), 'asc');
293
        $with = $this->request->get(config('repository.criteria.params.with', 'with'), null);
294
        $searchJoin = $this->request->get(config('repository.criteria.params.searchJoin', 'searchJoin'), null);
295
296
        return compact('search', 'searchFields', 'filter', 'orderBy', 'sortedBy', 'with', 'searchJoin');
297
    }
298
299
    protected function getSubqueryClosure($modelTableName, $field, $condition, $value)
300
    {
301
        switch ($condition) {
302
            case 'in':
303
                return function (Builder $query) use ($modelTableName, $field, $value) {
304
                    $query->whereIn($modelTableName.'.'.$field, $value);
305
                };
306
            case 'between':
307
                return function (Builder $query) use ($modelTableName, $field, $value) {
308
                    $query->whereBetween($modelTableName.'.'.$field, $value);
309
                };
310
            case 'cross':
311
                return function (Builder $query) use ($modelTableName, $field, $value) {
312
                    $query->where(function (Builder $query) use ($modelTableName, $field, $value) {
313
                        $query->where(function (Builder $query) use ($modelTableName, $field, $value) {
314
                            $query->where("{$modelTableName}.{$field}_min", '<=', $value[0])
315
                                  ->where("{$modelTableName}.{$field}_max", '>=', $value[1]);
316
                        })->orWhere(function (Builder $query) use ($modelTableName, $field, $value) {
317
                            $query->where("{$modelTableName}.{$field}_min", '<=', $value[0])
318
                                  ->where("{$modelTableName}.{$field}_max", '>=', $value[0]);
319
                        })->orWhere(function (Builder $query) use ($modelTableName, $field, $value) {
320
                            $query->where("{$modelTableName}.{$field}_min", '>=', $value[0])
321
                                  ->where("{$modelTableName}.{$field}_max", '<=', $value[1]);
322
                        })->orWhere(function (Builder $query) use ($modelTableName, $field, $value) {
323
                            $query->where("{$modelTableName}.{$field}_min", '>=', $value[0])
324
                                  ->where("{$modelTableName}.{$field}_max", '>=', $value[1])
325
                                  ->where("{$modelTableName}.{$field}_min", '<=', $value[1]);
326
                        });
327
                    });
328
                };
329
            default:
330
                return function (Builder $query) use ($modelTableName, $field, $condition, $value) {
331
                    $query->orWhere($modelTableName.'.'.$field, $condition, $value);
332
                };
333
        }
334
    }
335
336
    protected function getQueryClosure($field, $condition, $value)
337
    {
338
        switch ($condition) {
339
            case 'in':
340
                return function (Builder $query) use ($field, $value) {
341
                    $query->whereIn($field, $value);
342
                };
343
            case 'between':
344
                return function (Builder $query) use ($field, $value) {
345
                    $query->whereBetween($field, $value);
346
                };
347
            case 'cross':
348
                return function (Builder $query) use ($field, $value) {
349
                    $query->where(function (Builder $query) use ($field, $value) {
350
                        $query->where(function (Builder $query) use ($field, $value) {
351
                            $query->where("{$field}_min", '<=', $value[0])
352
                                  ->where("{$field}_max", '>=', $value[1]);
353
                        })->orWhere(function (Builder $query) use ($field, $value) {
354
                            $query->where("{$field}_min", '<=', $value[0])
355
                                  ->where("{$field}_max", '>=', $value[0]);
356
                        })->orWhere(function (Builder $query) use ($field, $value) {
357
                            $query->where("{$field}_min", '>=', $value[0])
358
                                  ->where("{$field}_max", '<=', $value[1]);
359
                        })->orWhere(function (Builder $query) use ($field, $value) {
360
                            $query->where("{$field}_min", '>=', $value[0])
361
                                  ->where("{$field}_max", '>=', $value[1])
362
                                  ->where("{$field}_min", '<=', $value[1]);
363
                        });
364
                    });
365
                };
366
            default:
367
                return function (Builder $query) use ($field, $condition, $value) {
368
                    $query->where($field, $condition, $value);
369
                };
370
        }
371
    }
372
373
    protected function getRelationQueryClosure($field, $condition, $value)
374
    {
375
        return function (Builder $query) use (
376
            $field,
377
            $condition,
378
            $value
379
        ) {
380
            $query->where($this->getQueryClosure($field, $condition, $value));
381
        };
382
    }
383
}
384