• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

miaoxing / plugin / 6734185729

02 Nov 2023 02:41PM UTC coverage: 39.147% (-0.02%) from 39.166%
6734185729

push

github

semantic-release-bot
chore(release): publish

See CHANGELOG.md for more details.

927 of 2368 relevant lines covered (39.15%)

17.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.41
/src/Model/ReqQueryTrait.php
1
<?php
2

3
namespace Miaoxing\Plugin\Model;
4

5
use Wei\BaseModel;
6
use Wei\Req;
7

8
/**
9
 * @mixin \ReqMixin
10
 * @mixin \IsPresentMixin
11
 * @mixin \IsDateMixin
12
 * @experimental 待整理方法命名和参数
13
 */
14
trait ReqQueryTrait
15
{
16
    protected $reqMaps = [];
17

18
    /**
19
     * The strings used to separate search name and search type
20
     *
21
     * @var string[]
22
     */
23
    protected $reqSeparators = [':', '$'];
24

25
    /**
26
     * The default sort column name
27
     *
28
     * @var string|array
29
     */
30
    protected $defaultSortColumn = 'id';
31

32
    /**
33
     * The default sort direction, optional values are "ASC" and "DESC"
34
     *
35
     * @var string|array
36
     */
37
    protected $defaultOrder = 'DESC';
38

39
    /**
40
     * The columns and orders that allowed to be sorted by request parameters
41
     *
42
     * eg:
43
     * [
44
     *   [['id'], ['DESC']],
45
     *   [['sort', 'created_at'], ['DESC', null]],
46
     * ]
47
     *
48
     * @var array|false
49
     */
50
    protected $reqOrderBy = [];
51

52
    /**
53
     * The conditions that can be used for search
54
     *
55
     * eg:
56
     * [
57
     *   'name',
58
     *   'age$gt',
59
     *   'profile' => [
60
     *     'city',
61
     *   ]
62
     * ]
63
     *
64
     * @var array|false
65
     */
66
    protected $reqSearch = [];
67

68
    /**
69
     * The relation names that available for relationship searches
70
     *
71
     * eg:
72
     * [
73
     *   'user',
74
     *   'user.profile',
75
     * ]
76
     *
77
     * Note: Currently only the first level relationship is supported
78
     *
79
     * @var array
80
     */
81
    protected $reqRelations = [];
82

83
    /**
84
     * @param Req $req
85
     * @return $this
86
     */
87
    public function setReq($req): self
88
    {
89
        $this->req = $req;
75✔
90
        return $this;
75✔
91
    }
92

93
    /**
94
     * Set default sort column and optional sort direction
95
     *
96
     * @param string|array $column
97
     * @param string|array $order
98
     * @return $this
99
     */
100
    public function setDefaultSortColumn($column, $order = null): self
101
    {
102
        $this->defaultSortColumn = $column;
12✔
103
        func_num_args() > 1 && $this->setDefaultOrder($order);
12✔
104
        return $this;
12✔
105
    }
106

107
    /**
108
     * Set default sort direction
109
     *
110
     * @param string|array $order
111
     * @return $this
112
     */
113
    public function setDefaultOrder($order): self
114
    {
115
        $this->defaultOrder = $order;
9✔
116
        return $this;
9✔
117
    }
118

119
    /**
120
     * Set sortable columns and orders
121
     *
122
     * @param array|false $orderBy
123
     * @return $this
124
     */
125
    public function setReqOrderBy($orderBy): self
126
    {
127
        $this->reqOrderBy = $orderBy;
39✔
128
        return $this;
39✔
129
    }
130

131
    /**
132
     * Add one sortable columns and orders item
133
     *
134
     * @param string|array $orderByItem
135
     * @return $this
136
     */
137
    public function addReqOrderBy($orderByItem): self
138
    {
139
        if (!is_array($this->reqOrderBy)) {
6✔
140
            $this->reqOrderBy = [];
3✔
141
        }
142
        $this->reqOrderBy[] = (array) $orderByItem;
6✔
143
        return $this;
6✔
144
    }
145

146
    /**
147
     * Return sortable columns and orders
148
     *
149
     * @return array|false
150
     */
151
    public function getReqOrderBy()
152
    {
153
        if (false === $this->reqOrderBy) {
30✔
154
            return false;
3✔
155
        }
156

157
        foreach ($this->reqOrderBy as $i => &$item) {
27✔
158
            if (!is_array($item)) {
27✔
159
                $item = [[$item]];
3✔
160
                continue;
3✔
161
            }
162

163
            if (!isset($item[0])) {
27✔
164
                throw new \RuntimeException(
3✔
165
                    'Expected the order by value contains 0-index value, given: ' . json_encode($item)
3✔
166
                );
2✔
167
            }
168

169
            if (isset($item[0]) && !is_array($item[0])) {
24✔
170
                $item[0] = [$item[0]];
6✔
171
            }
172

173
            if (isset($item[1]) && !is_array($item[1])) {
24✔
174
                $item[1] = [$item[1]];
3✔
175
            }
176
        }
177
        return $this->reqOrderBy;
24✔
178
    }
179

180
    /**
181
     * Set the conditions that can be used for search
182
     *
183
     * @param array|false $reqSearch
184
     * @return $this
185
     */
186
    public function setReqSearch($reqSearch): self
187
    {
188
        $this->reqSearch = $reqSearch;
30✔
189
        return $this;
30✔
190
    }
191

192
    /**
193
     * Return the conditions that can be used for search
194
     *
195
     * @return array|false
196
     */
197
    public function getReqSearch()
198
    {
199
        return $this->reqSearch;
36✔
200
    }
201

202
    /**
203
     * Add paging, sorting and search query based on request parameters
204
     *
205
     * @return $this
206
     */
207
    public function reqQuery(): self
208
    {
209
        return $this->reqPage()->reqOrderBy()->reqSearch();
×
210
    }
211

212
    /**
213
     * Add paging query based on request parameters
214
     *
215
     * @return $this
216
     */
217
    public function reqPage(): self
218
    {
219
        $limit = $this->req['limit'] ?: 10;
×
220
        $page = $this->req['page'] ?: 1;
×
221
        $this->limit($limit)->page($page);
×
222
        return $this;
×
223
    }
224

225
    /**
226
     * Add sorting query based on request parameters
227
     *
228
     * @return $this
229
     */
230
    public function reqOrderBy(): self
231
    {
232
        if (false === $this->reqOrderBy) {
54✔
233
            return $this;
3✔
234
        }
235

236
        [$sortColumns, $orders] = $this->detectReqSortAndOrder();
51✔
237

238
        foreach ($sortColumns as $i => $column) {
51✔
239
            if (!$this->hasColumn($column)) {
51✔
240
                unset($sortColumns[$i], $orders[$i]);
×
241
            }
242
        }
243

244
        $hasJoin = $this->getQueryPart('join');
51✔
245
        foreach ($sortColumns as $i => $column) {
51✔
246
            if ($hasJoin) {
51✔
247
                $sort = $this->getTable() . '.' . $column;
×
248
            } else {
249
                $sort = $column;
51✔
250
            }
251

252
            $order = strtoupper($orders[$i] ?? 'DESC');
51✔
253
            if (!in_array($order, ['ASC', 'DESC'], true)) {
51✔
254
                $order = $this->defaultOrder;
×
255
            }
256

257
            $this->orderBy($sort, $order);
51✔
258
        }
259

260
        return $this;
51✔
261
    }
262

263
    /**
264
     * Add search query based on request parameters
265
     *
266
     * @return $this
267
     */
268
    public function reqSearch(): self
269
    {
270
        $reqSearch = $this->getReqSearch();
36✔
271
        if (false === $reqSearch) {
36✔
272
            return $this;
3✔
273
        }
274
        return $this->processReqSearch($this, (array) ($this->req['search'] ?? []), $reqSearch);
33✔
275
    }
276

277
    /**
278
     * 指定请求值映射
279
     *
280
     * @param string $name
281
     * @param array $values
282
     * @return $this
283
     */
284
    public function reqMap(string $name, array $values): self
285
    {
286
        $this->reqMaps[$name] = $values;
×
287
        return $this;
×
288
    }
289

290
    /**
291
     * Set the relation names that available for relationship searches
292
     *
293
     * @param array $relations
294
     * @return $this
295
     */
296
    public function setReqRelations(array $relations): self
297
    {
298
        $this->reqRelations = $relations;
30✔
299
        return $this;
30✔
300
    }
301

302
    /**
303
     * Get the request
304
     *
305
     * @return array
306
     */
307
    public function getReqRelations(): array
308
    {
309
        return $this->reqRelations;
×
310
    }
311

312
    /**
313
     * Add search query based on request parameters and allowed search conditions
314
     *
315
     * @param array $search
316
     * @param array $allows
317
     * @param BaseModel $model
318
     * @return $this
319
     */
320
    protected function processReqSearch(BaseModel $model, array $search, array $allows): self
321
    {
322
        foreach ($search as $name => $value) {
33✔
323
            if (!$this->isPresent($value)) {
33✔
324
                continue;
×
325
            }
326

327
            // @deprecated: call `isRelation` to check relation
328
            if (is_array($value) && ($this->isReqRelation($name) || $model->isRelation($name))) {
33✔
329
                $this->selectMain()->leftJoinRelation($name);
12✔
330
                $relation = $model->getRelationModel($name);
12✔
331
                $this->processReqSearch($relation, $value, $allows[$name] ?? []);
12✔
332
            }
333

334
            if ($allows) {
33✔
335
                $name = str_replace($this->reqSeparators, $this->reqSeparators[0], $name);
21✔
336
                if (!in_array($name, $allows, true)) {
21✔
337
                    continue;
21✔
338
                }
339
            }
340

341
            $this->addReqColumnQuery($model, $name, $value);
33✔
342
        }
343
        return $this;
33✔
344
    }
345

346
    /**
347
     * Check if the specified name is available for relationship searches
348
     *
349
     * @param string $name
350
     * @return bool
351
     */
352
    protected function isReqRelation(string $name): bool
353
    {
354
        foreach ($this->reqRelations as $relation) {
12✔
355
            if ($name === $relation) {
9✔
356
                return true;
3✔
357
            }
358
            // For nested relation like "user.order"
359
            if (0 === strpos($relation, $name . '.')) {
6✔
360
                return true;
×
361
            }
362
        }
363
        return false;
9✔
364
    }
365

366
    /**
367
     * Add query based on model and column name
368
     *
369
     * @param BaseModel $model
370
     * @param string $name
371
     * @param mixed $value
372
     */
373
    protected function addReqColumnQuery(BaseModel $model, string $name, $value): void
374
    {
375
        [$name, $op] = $this->parseReqNameAndOp($name);
33✔
376
        if (!$model->hasColumn($name)) {
33✔
377
            return;
3✔
378
        }
379

380
        if ($model !== $this) {
33✔
381
            $name = $model->getTable() . '.' . $name;
12✔
382
        }
383

384
        if (isset($this->reqMaps[$name][$value])) {
33✔
385
            $value = $this->reqMaps[$name][$value];
×
386
        }
387

388
        $this->whereReqOp($name, $op, $value);
33✔
389
    }
11✔
390

391
    /**
392
     * Parse the column name and operator from the request name
393
     *
394
     * @param string $name
395
     * @return array
396
     */
397
    protected function parseReqNameAndOp(string $name): array
398
    {
399
        foreach ($this->reqSeparators as $separator) {
33✔
400
            if (false !== strpos($name, $separator)) {
33✔
401
                return explode($separator, $name, 2);
24✔
402
            }
403
        }
404
        return [$name, ''];
27✔
405
    }
406

407
    /**
408
     * Add a query based on the request operator
409
     *
410
     * @param string $column
411
     * @param string $op
412
     * @param mixed $value
413
     * @return $this
414
     */
415
    protected function whereReqOp(string $column, string $op, $value): self
416
    {
417
        switch ($op) {
11✔
418
            case '':
33✔
419
                return $this->where($column, '=', $value);
24✔
420

421
            case 'ct':
24✔
422
                return $this->whereContains($column, $value);
24✔
423

424
            case 'ge':
3✔
425
                return $this->where($column, '>=', $value);
3✔
426

427
            case 'le':
3✔
428
                return $this->where($column, '<=', $this->processReqDate($column, $value));
3✔
429

430
            case 'gt':
3✔
431
                return $this->where($column, '>', $value);
3✔
432

433
            case 'lt':
3✔
434
                return $this->where($column, '<', $this->processReqDate($column, $value));
3✔
435

436
            case 'hs':
3✔
437
                return $this->whereHas($column, $value);
3✔
438

439
            default:
440
                return $this;
×
441
        }
442
    }
443

444
    /**
445
     * Add "23:59:59" to the date value
446
     *
447
     * @param string $column
448
     * @param mixed $value
449
     * @return mixed
450
     */
451
    protected function processReqDate(string $column, $value)
452
    {
453
        if ('datetime' === $this->getColumnCast($column) && $this->isDate($value)) {
3✔
454
            return $value . ' 23:59:59';
×
455
        }
456
        return $value;
3✔
457
    }
458

459
    /**
460
     * Detect the request sort columns and orders
461
     *
462
     * @return array[]
463
     */
464
    protected function detectReqSortAndOrder(): array
465
    {
466
        $sortColumns = (array) $this->req['sort'];
51✔
467
        $orders = (array) $this->req['order'];
51✔
468

469
        if ($this->reqOrderBy) {
51✔
470
            $orderBy = $this->getReqOrderBy();
18✔
471
            $match = false;
18✔
472
            foreach ($orderBy as $item) {
18✔
473
                if (
474
                    $this->isArrayStartWiths($item[0], $sortColumns)
18✔
475
                    && $this->isOrderContains($item[1], $orders, count($sortColumns))
18✔
476
                ) {
477
                    $match = true;
15✔
478
                    break;
15✔
479
                }
480
            }
481
            if (!$match) {
18✔
482
                $sortColumns = [];
3✔
483
                $orders = [];
3✔
484
            }
485
        }
486

487
        return [
34✔
488
            $sortColumns ?: (array) $this->defaultSortColumn,
51✔
489
            $orders ?: (array) $this->defaultOrder,
51✔
490
        ];
34✔
491
    }
492

493
    private function isArrayStartWiths($arr1, $arr2): bool
494
    {
495
        if ($arr1 === $arr2) {
18✔
496
            return true;
15✔
497
        }
498

499
        if (count($arr1) < count($arr2)) {
18✔
500
            return false;
12✔
501
        }
502

503
        return array_slice($arr1, 0, count($arr2)) === $arr2;
6✔
504
    }
505

506
    private function isOrderContains(array $allows, array $reqs, int $length): bool
507
    {
508
        // 没有排序限制,返回包含
509
        if (!$allows) {
15✔
510
            return true;
×
511
        }
512

513
        // 补齐请求的排序方向,以便逐个判断
514
        $reqs = array_pad($reqs, $length, 'DESC');
15✔
515

516
        foreach ($reqs as $i => $req) {
15✔
517
            // 没有排序限制,跳过去检查下一个
518
            if (!isset($allows[$i]) || !$allows[$i]) {
15✔
519
                continue;
12✔
520
            }
521

522
            // 有排序限制,必须完全一样,否则不通过
523
            if (strtoupper($allows[$i]) !== strtoupper($req)) {
15✔
524
                return false;
×
525
            }
526
        }
527
        return true;
15✔
528
    }
529
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc