Completed
Push — 6.0 ( 9f7271...939c37 )
by liu
06:28
created

BaseQuery   F

Complexity

Total Complexity 238

Size/Duplication

Total Lines 1648
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 13
Bugs 0 Features 0
Metric Value
eloc 513
dl 0
loc 1648
ccs 0
cts 547
cp 0
rs 2
c 13
b 0
f 0
wmc 238

79 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
B paginate() 0 47 9
A getConnection() 0 3 1
A batchQuery() 0 3 1
A removeOption() 0 10 3
A getConfig() 0 3 1
A page() 0 5 1
A union() 0 11 3
A data() 0 5 1
A inc() 0 19 3
A newQuery() 0 15 3
A value() 0 3 1
A fieldRaw() 0 5 1
A query() 0 3 1
A column() 0 3 1
A dec() 0 3 1
A withoutField() 0 21 5
A name() 0 4 1
A getName() 0 3 2
B tableField() 0 34 11
A getTable() 0 9 4
A getLastSql() 0 3 1
A limit() 0 5 2
A execute() 0 3 1
A exp() 0 4 1
A setConnection() 0 5 1
B __call() 0 27 7
A getLastInsID() 0 3 1
B field() 0 30 8
A getNumRows() 0 3 1
A unionAll() 0 3 1
B table() 0 44 10
A find() 0 26 5
A group() 0 4 1
A setOption() 0 4 1
A select() 0 23 5
A tableRaw() 0 5 1
A insertAll() 0 11 5
A cache() 0 14 6
A via() 0 5 1
A getOptions() 0 7 2
A lock() 0 9 2
A options() 0 4 1
B delete() 0 30 9
C order() 0 40 12
A buildSql() 0 3 2
A selectByLastId() 0 27 5
A duplicate() 0 4 1
A strict() 0 4 1
A update() 0 20 6
A insertGetId() 0 3 1
A replace() 0 4 1
A orderRand() 0 4 1
A sequence() 0 4 1
A force() 0 4 1
B chunk() 0 47 10
A comment() 0 4 1
A having() 0 4 1
A master() 0 4 1
A insert() 0 7 2
A extra() 0 4 1
A orderField() 0 9 2
A parseUpdateData() 0 24 6
A pk() 0 4 1
F parseOptions() 0 54 16
A json() 0 5 1
A orderRaw() 0 9 2
A getModelUpdateCondition() 0 3 1
A selectInsert() 0 3 1
A distinct() 0 4 1
A procedure() 0 4 1
A alias() 0 11 2
C paginateX() 0 57 11
A partition() 0 4 1
A using() 0 4 1
B parsePkWhere() 0 23 7
A fetchSql() 0 9 2
A save() 0 15 4
A getPk() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like BaseQuery often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BaseQuery, and based on these observations, apply Extract Interface, too.

1
<?php
2
// +----------------------------------------------------------------------
3
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
6
// +----------------------------------------------------------------------
7
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
// +----------------------------------------------------------------------
9
// | Author: liu21st <[email protected]>
10
// +----------------------------------------------------------------------
11
declare (strict_types = 1);
12
13
namespace think\db;
14
15
use think\App;
16
use think\Collection;
17
use think\db\exception\BindParamException;
18
use think\db\exception\DataNotFoundException;
19
use think\db\exception\ModelNotFoundException;
20
use think\Exception;
21
use think\exception\DbException;
22
use think\exception\PDOException;
23
use think\Model;
24
use think\Paginator;
25
26
/**
27
 * 数据查询类
28
 */
29
class BaseQuery
30
{
31
    use concern\TimeFieldQuery;
32
    use concern\AggregateQuery;
33
    use concern\ModelRelationQuery;
34
    use concern\ResultOperation;
35
    use concern\Transaction;
36
    use concern\WhereQuery;
37
38
    /**
39
     * 当前数据库连接对象
40
     * @var Connection
41
     */
42
    protected $connection;
43
44
    /**
45
     * 当前数据表名称(不含前缀)
46
     * @var string
47
     */
48
    protected $name = '';
49
50
    /**
51
     * 当前数据表主键
52
     * @var string|array
53
     */
54
    protected $pk;
55
56
    /**
57
     * 当前数据表前缀
58
     * @var string
59
     */
60
    protected $prefix = '';
61
62
    /**
63
     * 当前查询参数
64
     * @var array
65
     */
66
    protected $options = [];
67
68
    /**
69
     * 架构函数
70
     * @access public
71
     * @param Connection $connection 数据库连接对象
72
     */
73
    public function __construct(Connection $connection)
74
    {
75
        $this->connection = $connection;
76
77
        $this->prefix = $this->connection->getConfig('prefix');
78
    }
79
80
    /**
81
     * 创建一个新的查询对象
82
     * @access public
83
     * @return BaseQuery
84
     */
85
    public function newQuery(): BaseQuery
86
    {
87
        $query = new static($this->connection);
88
89
        if ($this->model) {
90
            $query->model($this->model);
91
        }
92
93
        if (isset($this->options['table'])) {
94
            $query->table($this->options['table']);
95
        } else {
96
            $query->name($this->name);
97
        }
98
99
        return $query;
100
    }
101
102
    /**
103
     * 利用__call方法实现一些特殊的Model方法
104
     * @access public
105
     * @param string $method 方法名称
106
     * @param array  $args   调用参数
107
     * @return mixed
108
     * @throws DbException
109
     * @throws Exception
110
     */
111
    public function __call(string $method, array $args)
112
    {
113
        if (strtolower(substr($method, 0, 5)) == 'getby') {
114
            // 根据某个字段获取记录
115
            $field = App::parseName(substr($method, 5));
116
            return $this->where($field, '=', $args[0])->find();
117
        } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
118
            // 根据某个字段获取记录的某个值
119
            $name = App::parseName(substr($method, 10));
120
            return $this->where($name, '=', $args[0])->value($args[1]);
121
        } elseif (strtolower(substr($method, 0, 7)) == 'whereor') {
122
            $name = App::parseName(substr($method, 7));
123
            array_unshift($args, $name);
124
            return call_user_func_array([$this, 'whereOr'], $args);
125
        } elseif (strtolower(substr($method, 0, 5)) == 'where') {
126
            $name = App::parseName(substr($method, 5));
127
            array_unshift($args, $name);
128
            return call_user_func_array([$this, 'where'], $args);
129
        } elseif ($this->model && method_exists($this->model, 'scope' . $method)) {
130
            // 动态调用命名范围
131
            $method = 'scope' . $method;
132
            array_unshift($args, $this);
133
134
            call_user_func_array([$this->model, $method], $args);
135
            return $this;
136
        } else {
137
            throw new Exception('method not exist:' . static::class . '->' . $method);
138
        }
139
    }
140
141
    /**
142
     * 获取当前的数据库Connection对象
143
     * @access public
144
     * @return Connection
145
     */
146
    public function getConnection(): Connection
147
    {
148
        return $this->connection;
149
    }
150
151
    /**
152
     * 设置当前的数据库Connection对象
153
     * @access public
154
     * @param Connection $connection 数据库连接对象
155
     * @return $this
156
     */
157
    public function setConnection(Connection $connection)
158
    {
159
        $this->connection = $connection;
160
161
        return $this;
162
    }
163
164
    /**
165
     * 指定当前数据表名(不含前缀)
166
     * @access public
167
     * @param string $name 不含前缀的数据表名字
168
     * @return $this
169
     */
170
    public function name(string $name)
171
    {
172
        $this->name = $name;
173
        return $this;
174
    }
175
176
    /**
177
     * 获取当前的数据表名称
178
     * @access public
179
     * @return string
180
     */
181
    public function getName(): string
182
    {
183
        return $this->name ?: $this->model->getName();
184
    }
185
186
    /**
187
     * 获取数据库的配置参数
188
     * @access public
189
     * @param string $name 参数名称
190
     * @return mixed
191
     */
192
    public function getConfig(string $name = '')
193
    {
194
        return $this->connection->getConfig($name);
195
    }
196
197
    /**
198
     * 得到当前或者指定名称的数据表
199
     * @access public
200
     * @param string $name 不含前缀的数据表名字
201
     * @return mixed
202
     */
203
    public function getTable(string $name = '')
204
    {
205
        if (empty($name) && isset($this->options['table'])) {
206
            return $this->options['table'];
207
        }
208
209
        $name = $name ?: $this->name;
210
211
        return $this->prefix . App::parseName($name);
212
    }
213
214
    /**
215
     * 执行查询 返回数据集
216
     * @access public
217
     * @param string $sql  sql指令
218
     * @param array  $bind 参数绑定
219
     * @return array
220
     * @throws BindParamException
221
     * @throws PDOException
222
     */
223
    public function query(string $sql, array $bind = []): array
224
    {
225
        return $this->connection->query($this, $sql, $bind);
0 ignored issues
show
Bug introduced by
The method query() does not exist on think\db\Connection. Since it exists in all sub-types, consider adding an abstract or default implementation to think\db\Connection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

225
        return $this->connection->/** @scrutinizer ignore-call */ query($this, $sql, $bind);
Loading history...
226
    }
227
228
    /**
229
     * 执行语句
230
     * @access public
231
     * @param string $sql  sql指令
232
     * @param array  $bind 参数绑定
233
     * @return int
234
     * @throws BindParamException
235
     * @throws PDOException
236
     */
237
    public function execute(string $sql, array $bind = []): int
238
    {
239
        return $this->connection->execute($this, $sql, $bind, true);
0 ignored issues
show
Bug introduced by
The method execute() does not exist on think\db\Connection. Since it exists in all sub-types, consider adding an abstract or default implementation to think\db\Connection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

239
        return $this->connection->/** @scrutinizer ignore-call */ execute($this, $sql, $bind, true);
Loading history...
240
    }
241
242
    /**
243
     * 获取返回或者影响的记录数
244
     * @access public
245
     * @return integer
246
     */
247
    public function getNumRows(): int
248
    {
249
        return $this->connection->getNumRows();
0 ignored issues
show
Bug introduced by
The method getNumRows() does not exist on think\db\Connection. Since it exists in all sub-types, consider adding an abstract or default implementation to think\db\Connection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

249
        return $this->connection->/** @scrutinizer ignore-call */ getNumRows();
Loading history...
250
    }
251
252
    /**
253
     * 获取最近一次查询的sql语句
254
     * @access public
255
     * @return string
256
     */
257
    public function getLastSql(): string
258
    {
259
        return $this->connection->getLastSql();
260
    }
261
262
    /**
263
     * 获取最近插入的ID
264
     * @access public
265
     * @param string $sequence 自增序列名
266
     * @return mixed
267
     */
268
    public function getLastInsID(string $sequence = null)
269
    {
270
        return $this->connection->getLastInsID($this, $sequence);
271
    }
272
273
    /**
274
     * 批处理执行SQL语句
275
     * 批处理的指令都认为是execute操作
276
     * @access public
277
     * @param array $sql SQL批处理指令
278
     * @return bool
279
     */
280
    public function batchQuery(array $sql = []): bool
281
    {
282
        return $this->connection->batchQuery($this, $sql);
0 ignored issues
show
Bug introduced by
The method batchQuery() does not exist on think\db\Connection. Since it exists in all sub-types, consider adding an abstract or default implementation to think\db\Connection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

282
        return $this->connection->/** @scrutinizer ignore-call */ batchQuery($this, $sql);
Loading history...
283
    }
284
285
    /**
286
     * 得到某个字段的值
287
     * @access public
288
     * @param string $field   字段名
289
     * @param mixed  $default 默认值
290
     * @return mixed
291
     */
292
    public function value(string $field, $default = null)
293
    {
294
        return $this->connection->value($this, $field, $default);
295
    }
296
297
    /**
298
     * 得到某个列的数组
299
     * @access public
300
     * @param string $field 字段名 多个字段用逗号分隔
301
     * @param string $key   索引
302
     * @return array
303
     */
304
    public function column(string $field, string $key = ''): array
305
    {
306
        return $this->connection->column($this, $field, $key);
307
    }
308
309
    /**
310
     * 查询SQL组装 union
311
     * @access public
312
     * @param mixed   $union UNION
313
     * @param boolean $all   是否适用UNION ALL
314
     * @return $this
315
     */
316
    public function union($union, bool $all = false)
317
    {
318
        $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION';
319
320
        if (is_array($union)) {
321
            $this->options['union'] = array_merge($this->options['union'], $union);
322
        } else {
323
            $this->options['union'][] = $union;
324
        }
325
326
        return $this;
327
    }
328
329
    /**
330
     * 查询SQL组装 union all
331
     * @access public
332
     * @param mixed $union UNION数据
333
     * @return $this
334
     */
335
    public function unionAll($union)
336
    {
337
        return $this->union($union, true);
338
    }
339
340
    /**
341
     * 指定查询字段
342
     * @access public
343
     * @param mixed $field 字段信息
344
     * @return $this
345
     */
346
    public function field($field)
347
    {
348
        if (empty($field)) {
349
            return $this;
350
        } elseif ($field instanceof Raw) {
351
            $this->options['field'][] = $field;
352
            return $this;
353
        }
354
355
        if (is_string($field)) {
356
            if (preg_match('/[\<\'\"\(]/', $field)) {
357
                return $this->fieldRaw($field);
358
            }
359
360
            $field = array_map('trim', explode(',', $field));
361
        }
362
363
        if (true === $field) {
364
            // 获取全部字段
365
            $fields = $this->getTableFields();
0 ignored issues
show
Bug introduced by
The method getTableFields() does not exist on think\db\BaseQuery. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

365
            /** @scrutinizer ignore-call */ 
366
            $fields = $this->getTableFields();
Loading history...
366
            $field  = $fields ?: ['*'];
367
        }
368
369
        if (isset($this->options['field'])) {
370
            $field = array_merge((array) $this->options['field'], $field);
0 ignored issues
show
Bug introduced by
It seems like $field can also be of type think\db\BaseQuery; however, parameter $array2 of array_merge() does only seem to accept array|null, 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

370
            $field = array_merge((array) $this->options['field'], /** @scrutinizer ignore-type */ $field);
Loading history...
371
        }
372
373
        $this->options['field'] = array_unique($field);
0 ignored issues
show
Bug introduced by
It seems like $field can also be of type think\db\BaseQuery; however, parameter $array of array_unique() does only seem to accept array, 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

373
        $this->options['field'] = array_unique(/** @scrutinizer ignore-type */ $field);
Loading history...
374
375
        return $this;
376
    }
377
378
    /**
379
     * 指定要排除的查询字段
380
     * @access public
381
     * @param array|string $field 要排除的字段
382
     * @return $this
383
     */
384
    public function withoutField($field)
385
    {
386
        if (empty($field)) {
387
            return $this;
388
        }
389
390
        if (is_string($field)) {
391
            $field = array_map('trim', explode(',', $field));
392
        }
393
394
        // 字段排除
395
        $fields = $this->getTableFields();
396
        $field  = $fields ? array_diff($fields, $field) : $field;
0 ignored issues
show
Bug introduced by
It seems like $fields can also be of type think\db\BaseQuery; however, parameter $array1 of array_diff() does only seem to accept array, 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

396
        $field  = $fields ? array_diff(/** @scrutinizer ignore-type */ $fields, $field) : $field;
Loading history...
397
398
        if (isset($this->options['field'])) {
399
            $field = array_merge((array) $this->options['field'], $field);
400
        }
401
402
        $this->options['field'] = array_unique($field);
403
404
        return $this;
405
    }
406
407
    /**
408
     * 指定其它数据表的查询字段
409
     * @access public
410
     * @param mixed   $field     字段信息
411
     * @param string  $tableName 数据表名
412
     * @param string  $prefix    字段前缀
413
     * @param string  $alias     别名前缀
414
     * @return $this
415
     */
416
    public function tableField($field, string $tableName, string $prefix = '', string $alias = '')
417
    {
418
        if (empty($field)) {
419
            return $this;
420
        }
421
422
        if (is_string($field)) {
423
            $field = array_map('trim', explode(',', $field));
424
        }
425
426
        if (true === $field) {
427
            // 获取全部字段
428
            $fields = $this->getTableFields($tableName);
429
            $field  = $fields ?: ['*'];
430
        }
431
432
        // 添加统一的前缀
433
        $prefix = $prefix ?: $tableName;
434
        foreach ($field as $key => &$val) {
435
            if (is_numeric($key) && $alias) {
436
                $field[$prefix . '.' . $val] = $alias . $val;
437
                unset($field[$key]);
438
            } elseif (is_numeric($key)) {
439
                $val = $prefix . '.' . $val;
440
            }
441
        }
442
443
        if (isset($this->options['field'])) {
444
            $field = array_merge((array) $this->options['field'], $field);
0 ignored issues
show
Bug introduced by
It seems like $field can also be of type think\db\BaseQuery; however, parameter $array2 of array_merge() does only seem to accept array|null, 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

444
            $field = array_merge((array) $this->options['field'], /** @scrutinizer ignore-type */ $field);
Loading history...
445
        }
446
447
        $this->options['field'] = array_unique($field);
0 ignored issues
show
Bug introduced by
It seems like $field can also be of type think\db\BaseQuery; however, parameter $array of array_unique() does only seem to accept array, 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

447
        $this->options['field'] = array_unique(/** @scrutinizer ignore-type */ $field);
Loading history...
448
449
        return $this;
450
    }
451
452
    /**
453
     * 表达式方式指定查询字段
454
     * @access public
455
     * @param string $field 字段名
456
     * @return $this
457
     */
458
    public function fieldRaw(string $field)
459
    {
460
        $this->options['field'][] = new Raw($field);
461
462
        return $this;
463
    }
464
465
    /**
466
     * 设置数据
467
     * @access public
468
     * @param array $data 数据
469
     * @return $this
470
     */
471
    public function data(array $data)
472
    {
473
        $this->options['data'] = $data;
474
475
        return $this;
476
    }
477
478
    /**
479
     * 字段值增长
480
     * @access public
481
     * @param string  $field    字段名
482
     * @param float   $step     增长值
483
     * @param integer $lazyTime 延时时间(s)
484
     * @param string  $op       INC/DEC
485
     * @return $this
486
     */
487
    public function inc(string $field, float $step = 1, int $lazyTime = 0, string $op = 'INC')
488
    {
489
        if ($lazyTime > 0) {
490
            // 延迟写入
491
            $condition = $this->options['where'] ?? [];
492
493
            $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition));
494
            $step = $this->connection->lazyWrite($op, $guid, $step, $lazyTime);
495
496
            if (false === $step) {
497
                return $this;
498
            }
499
500
            $op = 'INC';
501
        }
502
503
        $this->options['data'][$field] = [$op, $step];
504
505
        return $this;
506
    }
507
508
    /**
509
     * 字段值减少
510
     * @access public
511
     * @param string  $field    字段名
512
     * @param float   $step     增长值
513
     * @param integer $lazyTime 延时时间(s)
514
     * @return $this
515
     */
516
    public function dec(string $field, float $step = 1, int $lazyTime = 0)
517
    {
518
        return $this->inc($field, $step, $lazyTime, 'DEC');
519
    }
520
521
    /**
522
     * 使用表达式设置数据
523
     * @access public
524
     * @param string $field 字段名
525
     * @param string $value 字段值
526
     * @return $this
527
     */
528
    public function exp(string $field, string $value)
529
    {
530
        $this->options['data'][$field] = new Raw($value);
531
        return $this;
532
    }
533
534
    /**
535
     * 去除查询参数
536
     * @access public
537
     * @param string $option 参数名 留空去除所有参数
538
     * @return $this
539
     */
540
    public function removeOption(string $option = '')
541
    {
542
        if ('' === $option) {
543
            $this->options = [];
544
            $this->bind    = [];
0 ignored issues
show
Bug Best Practice introduced by
The property bind does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
545
        } elseif (isset($this->options[$option])) {
546
            unset($this->options[$option]);
547
        }
548
549
        return $this;
550
    }
551
552
    /**
553
     * 指定查询数量
554
     * @access public
555
     * @param int $offset 起始位置
556
     * @param int $length 查询数量
557
     * @return $this
558
     */
559
    public function limit(int $offset, int $length = null)
560
    {
561
        $this->options['limit'] = $offset . ($length ? ',' . $length : '');
562
563
        return $this;
564
    }
565
566
    /**
567
     * 指定分页
568
     * @access public
569
     * @param int $page     页数
570
     * @param int $listRows 每页数量
571
     * @return $this
572
     */
573
    public function page(int $page, int $listRows = null)
574
    {
575
        $this->options['page'] = [$page, $listRows];
576
577
        return $this;
578
    }
579
580
    /**
581
     * 分页查询
582
     * @access public
583
     * @param int|array $listRows 每页数量 数组表示配置参数
584
     * @param int|bool  $simple   是否简洁模式或者总记录数
585
     * @return Paginator
586
     * @throws DbException
587
     */
588
    public function paginate($listRows = null, $simple = false): Paginator
589
    {
590
        if (is_int($simple)) {
591
            $total  = $simple;
592
            $simple = false;
593
        }
594
595
        $defaultConfig = [
596
            'query'     => [], //url额外参数
597
            'fragment'  => '', //url锚点
598
            'var_page'  => 'page', //分页变量
599
            'list_rows' => 15, //每页数量
600
        ];
601
602
        if (is_array($listRows)) {
603
            $config   = array_merge($defaultConfig, $listRows);
604
            $listRows = intval($config['list_rows']);
605
        } else {
606
            $config   = $defaultConfig;
607
            $listRows = intval($listRows ?: $config['list_rows']);
608
        }
609
610
        $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']);
611
612
        $page = $page < 1 ? 1 : $page;
613
614
        $config['path'] = $config['path'] ?? Paginator::getCurrentPath();
615
616
        if (!isset($total) && !$simple) {
617
            $options = $this->getOptions();
618
619
            unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']);
620
621
            $bind    = $this->bind;
622
            $total   = $this->count();
623
            $results = $this->options($options)->bind($bind)->page($page, $listRows)->select();
0 ignored issues
show
Bug introduced by
The method bind() does not exist on think\db\BaseQuery. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

623
            $results = $this->options($options)->/** @scrutinizer ignore-call */ bind($bind)->page($page, $listRows)->select();
Loading history...
624
        } elseif ($simple) {
625
            $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
626
            $total   = null;
627
        } else {
628
            $results = $this->page($page, $listRows)->select();
629
        }
630
631
        $this->removeOption('limit');
632
        $this->removeOption('page');
633
634
        return Paginator::make($results, $listRows, $page, $total, $simple, $config);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $total does not seem to be defined for all execution paths leading up to this point.
Loading history...
635
    }
636
637
    /**
638
     * 根据数字类型字段进行分页查询(大数据)
639
     * @access public
640
     * @param int|array $listRows 每页数量或者分页配置
641
     * @param string    $key      分页索引键
642
     * @param string    $sort     索引键排序 asc|desc
643
     * @return Paginator
644
     * @throws DbException
645
     */
646
    public function paginateX($listRows = null, string $key = null, string $sort = null): Paginator
647
    {
648
        $defaultConfig = [
649
            'query'     => [], //url额外参数
650
            'fragment'  => '', //url锚点
651
            'var_page'  => 'page', //分页变量
652
            'list_rows' => 15, //每页数量
653
        ];
654
655
        $config   = is_array($listRows) ? array_merge($defaultConfig, $listRows) : $defaultConfig;
656
        $listRows = is_int($listRows) ? $listRows : (int) $config['list_rows'];
657
        $page     = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']);
658
        $page     = $page < 1 ? 1 : $page;
659
660
        $config['path'] = $config['path'] ?? Paginator::getCurrentPath();
661
662
        $key     = $key ?: $this->getPk();
663
        $options = $this->getOptions();
664
665
        if (is_null($sort)) {
666
            $order = $options['order'] ?? '';
667
            if (!empty($order)) {
668
                $sort = $order[$key] ?? 'desc';
669
            } else {
670
                $this->order($key, 'desc');
671
                $sort = 'desc';
672
            }
673
        } else {
674
            $this->order($key, $sort);
675
        }
676
677
        $newOption = $options;
678
        unset($newOption['field'], $newOption['page']);
679
680
        $data = $this->newQuery()
681
            ->options($newOption)
682
            ->field($key)
683
            ->where(true)
684
            ->order($key, $sort)
685
            ->limit(1)
686
            ->find();
687
688
        $result = $data[$key];
689
690
        if (is_numeric($result)) {
691
            $lastId = 'asc' == $sort ? ($result - 1) + ($page - 1) * $listRows : ($result + 1) - ($page - 1) * $listRows;
692
        }
693
694
        $results = $this->when($lastId, function ($query) use ($key, $sort, $lastId) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $lastId does not seem to be defined for all execution paths leading up to this point.
Loading history...
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
695
            $query->where($key, 'asc' == $sort ? '>' : '<', $lastId);
696
        })
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
697
            ->limit($listRows)
698
            ->select();
699
700
        $this->options($options);
701
702
        return Paginator::make($results, $listRows, $page, null, true, $config);
703
    }
704
705
    /**
706
     * 根据最后ID查询N个数据
707
     * @access public
708
     * @param int        $limit  LIMIT
709
     * @param int|string $lastId LastId
710
     * @param string     $key    分页索引键 默认为主键
711
     * @param string     $sort   索引键排序 asc|desc
712
     * @return array
713
     * @throws DbException
714
     */
715
    public function selectByLastId(int $limit, $lastId = null, string $key = null, string $sort = null): array
716
    {
717
        $key = $key ?: $this->getPk();
718
719
        if (is_null($sort)) {
720
            $order = $this->getOptions('order');
721
            if (!empty($order)) {
722
                $sort = $order[$key] ?? 'desc';
723
            } else {
724
                $this->order($key, 'desc');
725
                $sort = 'desc';
726
            }
727
        } else {
728
            $this->order($key, $sort);
729
        }
730
731
        $result = $this->when($lastId, function ($query) use ($key, $sort, $lastId) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
732
            $query->where($key, 'asc' == $sort ? '>' : '<', $lastId);
733
        })->limit($limit)->select();
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
734
735
        $last = $result->last();
736
737
        $result->first();
738
739
        return [
740
            'data'   => $result,
741
            'lastId' => $last[$key],
742
        ];
743
    }
744
745
    /**
746
     * 表达式方式指定当前操作的数据表
747
     * @access public
748
     * @param mixed $table 表名
749
     * @return $this
750
     */
751
    public function tableRaw(string $table)
752
    {
753
        $this->options['table'] = new Raw($table);
754
755
        return $this;
756
    }
757
758
    /**
759
     * 指定当前操作的数据表
760
     * @access public
761
     * @param mixed $table 表名
762
     * @return $this
763
     */
764
    public function table($table)
765
    {
766
        if (is_string($table)) {
767
            if (strpos($table, ')')) {
768
                // 子查询
769
            } elseif (false === strpos($table, ',')) {
770
                if (strpos($table, ' ')) {
771
                    list($item, $alias) = explode(' ', $table);
772
                    $table              = [];
773
                    $this->alias([$item => $alias]);
774
                    $table[$item] = $alias;
775
                }
776
            } else {
777
                $tables = explode(',', $table);
778
                $table  = [];
779
780
                foreach ($tables as $item) {
781
                    $item = trim($item);
782
                    if (strpos($item, ' ')) {
783
                        list($item, $alias) = explode(' ', $item);
784
                        $this->alias([$item => $alias]);
785
                        $table[$item] = $alias;
786
                    } else {
787
                        $table[] = $item;
788
                    }
789
                }
790
            }
791
        } elseif (is_array($table)) {
792
            $tables = $table;
793
            $table  = [];
794
795
            foreach ($tables as $key => $val) {
796
                if (is_numeric($key)) {
797
                    $table[] = $val;
798
                } else {
799
                    $this->alias([$key => $val]);
800
                    $table[$key] = $val;
801
                }
802
            }
803
        }
804
805
        $this->options['table'] = $table;
806
807
        return $this;
808
    }
809
810
    /**
811
     * USING支持 用于多表删除
812
     * @access public
813
     * @param mixed $using USING
814
     * @return $this
815
     */
816
    public function using($using)
817
    {
818
        $this->options['using'] = $using;
819
        return $this;
820
    }
821
822
    /**
823
     * 存储过程调用
824
     * @access public
825
     * @param bool $procedure 是否为存储过程查询
826
     * @return $this
827
     */
828
    public function procedure(bool $procedure = true)
829
    {
830
        $this->options['procedure'] = $procedure;
831
        return $this;
832
    }
833
834
    /**
835
     * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc'])
836
     * @access public
837
     * @param string|array|Raw $field 排序字段
838
     * @param string           $order 排序
839
     * @return $this
840
     */
841
    public function order($field, string $order = '')
842
    {
843
        if (empty($field)) {
844
            return $this;
845
        } elseif ($field instanceof Raw) {
846
            $this->options['order'][] = $field;
847
            return $this;
848
        }
849
850
        if (is_string($field)) {
851
            if (!empty($this->options['via'])) {
852
                $field = $this->options['via'] . '.' . $field;
853
            }
854
            if (strpos($field, ',')) {
855
                $field = array_map('trim', explode(',', $field));
856
            } else {
857
                $field = empty($order) ? $field : [$field => $order];
858
            }
859
        } elseif (!empty($this->options['via'])) {
860
            foreach ($field as $key => $val) {
861
                if (is_numeric($key)) {
862
                    $field[$key] = $this->options['via'] . '.' . $val;
863
                } else {
864
                    $field[$this->options['via'] . '.' . $key] = $val;
865
                    unset($field[$key]);
866
                }
867
            }
868
        }
869
870
        if (!isset($this->options['order'])) {
871
            $this->options['order'] = [];
872
        }
873
874
        if (is_array($field)) {
875
            $this->options['order'] = array_merge($this->options['order'], $field);
876
        } else {
877
            $this->options['order'][] = $field;
878
        }
879
880
        return $this;
881
    }
882
883
    /**
884
     * 表达式方式指定Field排序
885
     * @access public
886
     * @param string $field 排序字段
887
     * @param array  $bind  参数绑定
888
     * @return $this
889
     */
890
    public function orderRaw(string $field, array $bind = [])
891
    {
892
        if (!empty($bind)) {
893
            $this->bindParams($field, $bind);
0 ignored issues
show
Bug introduced by
The method bindParams() does not exist on think\db\BaseQuery. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

893
            $this->/** @scrutinizer ignore-call */ 
894
                   bindParams($field, $bind);
Loading history...
894
        }
895
896
        $this->options['order'][] = new Raw($field);
897
898
        return $this;
899
    }
900
901
    /**
902
     * 指定Field排序 orderField('id',[1,2,3],'desc')
903
     * @access public
904
     * @param string $field  排序字段
905
     * @param array  $values 排序值
906
     * @param string $order  排序 desc/asc
907
     * @return $this
908
     */
909
    public function orderField(string $field, array $values, string $order = '')
910
    {
911
        if (!empty($values)) {
912
            $values['sort'] = $order;
913
914
            $this->options['order'][$field] = $values;
915
        }
916
917
        return $this;
918
    }
919
920
    /**
921
     * 随机排序
922
     * @access public
923
     * @return $this
924
     */
925
    public function orderRand()
926
    {
927
        $this->options['order'][] = '[rand]';
928
        return $this;
929
    }
930
931
    /**
932
     * 查询缓存
933
     * @access public
934
     * @param mixed             $key    缓存key
935
     * @param integer|\DateTime $expire 缓存有效期
936
     * @param string            $tag    缓存标签
937
     * @return $this
938
     */
939
    public function cache($key = true, $expire = null, string $tag = null)
940
    {
941
        if (false === $key) {
942
            return $this;
943
        }
944
945
        if ($key instanceof \DateTimeInterface || $key instanceof \DateInterval || (is_int($key) && is_null($expire))) {
946
            $expire = $key;
947
            $key    = true;
948
        }
949
950
        $this->options['cache'] = [$key, $expire, $tag];
951
952
        return $this;
953
    }
954
955
    /**
956
     * 指定group查询
957
     * @access public
958
     * @param string|array $group GROUP
959
     * @return $this
960
     */
961
    public function group($group)
962
    {
963
        $this->options['group'] = $group;
964
        return $this;
965
    }
966
967
    /**
968
     * 指定having查询
969
     * @access public
970
     * @param string $having having
971
     * @return $this
972
     */
973
    public function having(string $having)
974
    {
975
        $this->options['having'] = $having;
976
        return $this;
977
    }
978
979
    /**
980
     * 指定查询lock
981
     * @access public
982
     * @param bool|string $lock 是否lock
983
     * @return $this
984
     */
985
    public function lock($lock = false)
986
    {
987
        $this->options['lock'] = $lock;
988
989
        if ($lock) {
990
            $this->options['master'] = true;
991
        }
992
993
        return $this;
994
    }
995
996
    /**
997
     * 指定distinct查询
998
     * @access public
999
     * @param bool $distinct 是否唯一
1000
     * @return $this
1001
     */
1002
    public function distinct(bool $distinct = true)
1003
    {
1004
        $this->options['distinct'] = $distinct;
1005
        return $this;
1006
    }
1007
1008
    /**
1009
     * 指定数据表别名
1010
     * @access public
1011
     * @param array|string $alias 数据表别名
1012
     * @return $this
1013
     */
1014
    public function alias($alias)
1015
    {
1016
        if (is_array($alias)) {
1017
            $this->options['alias'] = $alias;
1018
        } else {
1019
            $table = $this->getTable();
1020
1021
            $this->options['alias'][$table] = $alias;
1022
        }
1023
1024
        return $this;
1025
    }
1026
1027
    /**
1028
     * 指定强制索引
1029
     * @access public
1030
     * @param string $force 索引名称
1031
     * @return $this
1032
     */
1033
    public function force(string $force)
1034
    {
1035
        $this->options['force'] = $force;
1036
        return $this;
1037
    }
1038
1039
    /**
1040
     * 查询注释
1041
     * @access public
1042
     * @param string $comment 注释
1043
     * @return $this
1044
     */
1045
    public function comment(string $comment)
1046
    {
1047
        $this->options['comment'] = $comment;
1048
        return $this;
1049
    }
1050
1051
    /**
1052
     * 获取执行的SQL语句而不进行实际的查询
1053
     * @access public
1054
     * @param bool $fetch 是否返回sql
1055
     * @return $this|Fetch
1056
     */
1057
    public function fetchSql(bool $fetch = true)
1058
    {
1059
        $this->options['fetch_sql'] = $fetch;
1060
1061
        if ($fetch) {
1062
            return new Fetch($this);
1063
        }
1064
1065
        return $this;
1066
    }
1067
1068
    /**
1069
     * 设置从主服务器读取数据
1070
     * @access public
1071
     * @param bool $readMaster 是否从主服务器读取
1072
     * @return $this
1073
     */
1074
    public function master(bool $readMaster = true)
1075
    {
1076
        $this->options['master'] = $readMaster;
1077
        return $this;
1078
    }
1079
1080
    /**
1081
     * 设置是否严格检查字段名
1082
     * @access public
1083
     * @param bool $strict 是否严格检查字段
1084
     * @return $this
1085
     */
1086
    public function strict(bool $strict = true)
1087
    {
1088
        $this->options['strict'] = $strict;
1089
        return $this;
1090
    }
1091
1092
    /**
1093
     * 设置自增序列名
1094
     * @access public
1095
     * @param string $sequence 自增序列名
1096
     * @return $this
1097
     */
1098
    public function sequence(string $sequence = null)
1099
    {
1100
        $this->options['sequence'] = $sequence;
1101
        return $this;
1102
    }
1103
1104
    /**
1105
     * 设置是否REPLACE
1106
     * @access public
1107
     * @param bool $replace 是否使用REPLACE写入数据
1108
     * @return $this
1109
     */
1110
    public function replace(bool $replace = true)
1111
    {
1112
        $this->options['replace'] = $replace;
1113
        return $this;
1114
    }
1115
1116
    /**
1117
     * 设置当前查询所在的分区
1118
     * @access public
1119
     * @param string|array $partition 分区名称
1120
     * @return $this
1121
     */
1122
    public function partition($partition)
1123
    {
1124
        $this->options['partition'] = $partition;
1125
        return $this;
1126
    }
1127
1128
    /**
1129
     * 设置DUPLICATE
1130
     * @access public
1131
     * @param array|string|Raw $duplicate DUPLICATE信息
1132
     * @return $this
1133
     */
1134
    public function duplicate($duplicate)
1135
    {
1136
        $this->options['duplicate'] = $duplicate;
1137
        return $this;
1138
    }
1139
1140
    /**
1141
     * 设置查询的额外参数
1142
     * @access public
1143
     * @param string $extra 额外信息
1144
     * @return $this
1145
     */
1146
    public function extra(string $extra)
1147
    {
1148
        $this->options['extra'] = $extra;
1149
        return $this;
1150
    }
1151
1152
    /**
1153
     * 设置JSON字段信息
1154
     * @access public
1155
     * @param array $json  JSON字段
1156
     * @param bool  $assoc 是否取出数组
1157
     * @return $this
1158
     */
1159
    public function json(array $json = [], bool $assoc = false)
1160
    {
1161
        $this->options['json']       = $json;
1162
        $this->options['json_assoc'] = $assoc;
1163
        return $this;
1164
    }
1165
1166
    /**
1167
     * 指定数据表主键
1168
     * @access public
1169
     * @param string $pk 主键
1170
     * @return $this
1171
     */
1172
    public function pk(string $pk)
1173
    {
1174
        $this->pk = $pk;
1175
        return $this;
1176
    }
1177
1178
    /**
1179
     * 获取当前数据表的主键
1180
     * @access public
1181
     * @return string|array
1182
     */
1183
    public function getPk()
1184
    {
1185
        if (empty($this->pk)) {
1186
            $this->pk = $this->connection->getPk($this->getTable());
0 ignored issues
show
Bug introduced by
The method getPk() does not exist on think\db\Connection. Since it exists in all sub-types, consider adding an abstract or default implementation to think\db\Connection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1186
            /** @scrutinizer ignore-call */ 
1187
            $this->pk = $this->connection->getPk($this->getTable());
Loading history...
1187
        }
1188
1189
        return $this->pk;
1190
    }
1191
1192
    /**
1193
     * 查询参数批量赋值
1194
     * @access protected
1195
     * @param array $options 表达式参数
1196
     * @return $this
1197
     */
1198
    protected function options(array $options)
1199
    {
1200
        $this->options = $options;
1201
        return $this;
1202
    }
1203
1204
    /**
1205
     * 获取当前的查询参数
1206
     * @access public
1207
     * @param string $name 参数名
1208
     * @return mixed
1209
     */
1210
    public function getOptions(string $name = '')
1211
    {
1212
        if ('' === $name) {
1213
            return $this->options;
1214
        }
1215
1216
        return $this->options[$name] ?? null;
1217
    }
1218
1219
    /**
1220
     * 设置当前的查询参数
1221
     * @access public
1222
     * @param string $option 参数名
1223
     * @param mixed  $value  参数值
1224
     * @return $this
1225
     */
1226
    public function setOption(string $option, $value)
1227
    {
1228
        $this->options[$option] = $value;
1229
        return $this;
1230
    }
1231
1232
    /**
1233
     * 设置当前字段添加的表别名
1234
     * @access public
1235
     * @param string $via 临时表别名
1236
     * @return $this
1237
     */
1238
    public function via(string $via = '')
1239
    {
1240
        $this->options['via'] = $via;
1241
1242
        return $this;
1243
    }
1244
1245
    /**
1246
     * 保存记录 自动判断insert或者update
1247
     * @access public
1248
     * @param array $data        数据
1249
     * @param bool  $forceInsert 是否强制insert
1250
     * @return integer
1251
     */
1252
    public function save(array $data = [], bool $forceInsert = false)
1253
    {
1254
        if ($forceInsert) {
1255
            return $this->insert($data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->insert($data) also could return the type string which is incompatible with the documented return type integer.
Loading history...
1256
        }
1257
1258
        $this->options['data'] = array_merge($this->options['data'] ?? [], $data);
1259
1260
        if (!empty($this->options['where'])) {
1261
            $isUpdate = true;
1262
        } else {
1263
            $isUpdate = $this->parseUpdateData($this->options['data']);
1264
        }
1265
1266
        return $isUpdate ? $this->update() : $this->insert();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $isUpdate ? $this...ate() : $this->insert() also could return the type string which is incompatible with the documented return type integer.
Loading history...
1267
    }
1268
1269
    /**
1270
     * 插入记录
1271
     * @access public
1272
     * @param array   $data         数据
1273
     * @param boolean $getLastInsID 返回自增主键
1274
     * @return integer|string
1275
     */
1276
    public function insert(array $data = [], bool $getLastInsID = false)
1277
    {
1278
        if (!empty($data)) {
1279
            $this->options['data'] = $data;
1280
        }
1281
1282
        return $this->connection->insert($this, $getLastInsID);
1283
    }
1284
1285
    /**
1286
     * 插入记录并获取自增ID
1287
     * @access public
1288
     * @param array $data 数据
1289
     * @return integer|string
1290
     */
1291
    public function insertGetId(array $data)
1292
    {
1293
        return $this->insert($data, true);
1294
    }
1295
1296
    /**
1297
     * 批量插入记录
1298
     * @access public
1299
     * @param array   $dataSet 数据集
1300
     * @param integer $limit   每次写入数据限制
1301
     * @return integer
1302
     */
1303
    public function insertAll(array $dataSet = [], int $limit = 0): int
1304
    {
1305
        if (empty($dataSet)) {
1306
            $dataSet = $this->options['data'] ?? [];
1307
        }
1308
1309
        if (empty($limit) && !empty($this->options['limit']) && is_numeric($this->options['limit'])) {
1310
            $limit = (int) $this->options['limit'];
1311
        }
1312
1313
        return $this->connection->insertAll($this, $dataSet, $limit);
0 ignored issues
show
Unused Code introduced by
The call to think\db\Connection::insertAll() has too many arguments starting with $limit. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1313
        return $this->connection->/** @scrutinizer ignore-call */ insertAll($this, $dataSet, $limit);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1314
    }
1315
1316
    /**
1317
     * 通过Select方式插入记录
1318
     * @access public
1319
     * @param array  $fields 要插入的数据表字段名
1320
     * @param string $table  要插入的数据表名
1321
     * @return integer
1322
     * @throws PDOException
1323
     */
1324
    public function selectInsert(array $fields, string $table): int
1325
    {
1326
        return $this->connection->selectInsert($this, $fields, $table);
0 ignored issues
show
Bug introduced by
The method selectInsert() does not exist on think\db\Connection. Did you maybe mean select()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1326
        return $this->connection->/** @scrutinizer ignore-call */ selectInsert($this, $fields, $table);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1327
    }
1328
1329
    /**
1330
     * 更新记录
1331
     * @access public
1332
     * @param mixed $data 数据
1333
     * @return integer
1334
     * @throws Exception
1335
     * @throws PDOException
1336
     */
1337
    public function update(array $data = []): int
1338
    {
1339
        if (!empty($data)) {
1340
            $this->options['data'] = array_merge($this->options['data'] ?? [], $data);
1341
        }
1342
1343
        if (empty($this->options['where'])) {
1344
            $this->parseUpdateData($this->options['data']);
1345
        }
1346
1347
        if (empty($this->options['where']) && $this->model) {
1348
            $this->where($this->model->getWhere());
1349
        }
1350
1351
        if (empty($this->options['where'])) {
1352
            // 如果没有任何更新条件则不执行
1353
            throw new Exception('miss update condition');
1354
        }
1355
1356
        return $this->connection->update($this);
1357
    }
1358
1359
    /**
1360
     * 删除记录
1361
     * @access public
1362
     * @param mixed $data 表达式 true 表示强制删除
1363
     * @return int
1364
     * @throws Exception
1365
     * @throws PDOException
1366
     */
1367
    public function delete($data = null): int
1368
    {
1369
        if (!is_null($data) && true !== $data) {
1370
            // AR模式分析主键条件
1371
            $this->parsePkWhere($data);
1372
        }
1373
1374
        if (empty($this->options['where']) && $this->model) {
1375
            $this->where($this->model->getWhere());
1376
        }
1377
1378
        if (true !== $data && empty($this->options['where'])) {
1379
            // 如果条件为空 不进行删除操作 除非设置 1=1
1380
            throw new Exception('delete without condition');
1381
        }
1382
1383
        if (!empty($this->options['soft_delete'])) {
1384
            // 软删除
1385
            list($field, $condition) = $this->options['soft_delete'];
1386
            if ($condition) {
1387
                unset($this->options['soft_delete']);
1388
                $this->options['data'] = [$field => $condition];
1389
1390
                return $this->connection->update($this);
1391
            }
1392
        }
1393
1394
        $this->options['data'] = $data;
1395
1396
        return $this->connection->delete($this);
1397
    }
1398
1399
    /**
1400
     * 查找记录
1401
     * @access public
1402
     * @param mixed $data 数据
1403
     * @return Collection
1404
     * @throws DbException
1405
     * @throws ModelNotFoundException
1406
     * @throws DataNotFoundException
1407
     */
1408
    public function select($data = null): Collection
1409
    {
1410
        if (!is_null($data)) {
1411
            // 主键条件分析
1412
            $this->parsePkWhere($data);
1413
        }
1414
1415
        $resultSet = $this->connection->select($this);
1416
1417
        // 返回结果处理
1418
        if (!empty($this->options['fail']) && count($resultSet) == 0) {
1419
            $this->throwNotFound();
1420
        }
1421
1422
        // 数据列表读取后的处理
1423
        if (!empty($this->model)) {
1424
            // 生成模型对象
1425
            $resultSet = $this->resultSetToModelCollection($resultSet);
1426
        } else {
1427
            $this->resultSet($resultSet);
1428
        }
1429
1430
        return $resultSet;
1431
    }
1432
1433
    /**
1434
     * 查找单条记录
1435
     * @access public
1436
     * @param mixed $data 查询数据
1437
     * @return array|Model|null
1438
     * @throws DbException
1439
     * @throws ModelNotFoundException
1440
     * @throws DataNotFoundException
1441
     */
1442
    public function find($data = null)
1443
    {
1444
        if (!is_null($data)) {
1445
            // AR模式分析主键条件
1446
            $this->parsePkWhere($data);
1447
        }
1448
1449
        if (empty($this->options['where'])) {
1450
            $result = [];
1451
        } else {
1452
            $result = $this->connection->find($this);
1453
        }
1454
1455
        // 数据处理
1456
        if (empty($result)) {
1457
            return $this->resultToEmpty();
1458
        }
1459
1460
        if (!empty($this->model)) {
1461
            // 返回模型对象
1462
            $this->resultToModel($result, $this->options);
1463
        } else {
1464
            $this->result($result);
1465
        }
1466
1467
        return $result;
1468
    }
1469
1470
    /**
1471
     * 分批数据返回处理
1472
     * @access public
1473
     * @param integer      $count    每次处理的数据数量
1474
     * @param callable     $callback 处理回调方法
1475
     * @param string|array $column   分批处理的字段名
1476
     * @param string       $order    字段排序
1477
     * @return bool
1478
     * @throws DbException
1479
     */
1480
    public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool
1481
    {
1482
        $options = $this->getOptions();
1483
        $column  = $column ?: $this->getPk();
1484
1485
        if (isset($options['order'])) {
1486
            unset($options['order']);
1487
        }
1488
1489
        $bind = $this->bind;
1490
1491
        if (is_array($column)) {
1492
            $times = 1;
1493
            $query = $this->options($options)->page($times, $count);
1494
        } else {
1495
            $query = $this->options($options)->limit($count);
1496
1497
            if (strpos($column, '.')) {
1498
                list($alias, $key) = explode('.', $column);
1499
            } else {
1500
                $key = $column;
1501
            }
1502
        }
1503
1504
        $resultSet = $query->order($column, $order)->select();
1505
1506
        while (count($resultSet) > 0) {
1507
            if (false === call_user_func($callback, $resultSet)) {
1508
                return false;
1509
            }
1510
1511
            if (isset($times)) {
1512
                $times++;
1513
                $query = $this->options($options)->page($times, $count);
1514
            } else {
1515
                $end    = $resultSet->pop();
1516
                $lastId = is_array($end) ? $end[$key] : $end->getData($key);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $key does not seem to be defined for all execution paths leading up to this point.
Loading history...
1517
1518
                $query = $this->options($options)
1519
                    ->limit($count)
1520
                    ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
1521
            }
1522
1523
            $resultSet = $query->bind($bind)->order($column, $order)->select();
1524
        }
1525
1526
        return true;
1527
    }
1528
1529
    /**
1530
     * 创建子查询SQL
1531
     * @access public
1532
     * @param bool $sub 是否添加括号
1533
     * @return string
1534
     * @throws DbException
1535
     */
1536
    public function buildSql(bool $sub = true): string
1537
    {
1538
        return $sub ? '( ' . $this->fetchSql()->select() . ' )' : $this->fetchSql()->select();
1539
    }
1540
1541
    /**
1542
     * 分析表达式(可用于查询或者写入操作)
1543
     * @access public
1544
     * @return array
1545
     */
1546
    public function parseOptions(): array
1547
    {
1548
        $options = $this->getOptions();
1549
1550
        // 获取数据表
1551
        if (empty($options['table'])) {
1552
            $options['table'] = $this->getTable();
1553
        }
1554
1555
        if (!isset($options['where'])) {
1556
            $options['where'] = [];
1557
        } elseif (isset($options['view'])) {
1558
            // 视图查询条件处理
1559
            $this->parseView($options);
0 ignored issues
show
Bug introduced by
The method parseView() does not exist on think\db\BaseQuery. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1559
            $this->/** @scrutinizer ignore-call */ 
1560
                   parseView($options);
Loading history...
1560
        }
1561
1562
        if (!isset($options['field'])) {
1563
            $options['field'] = '*';
1564
        }
1565
1566
        foreach (['data', 'order', 'join', 'union'] as $name) {
1567
            if (!isset($options[$name])) {
1568
                $options[$name] = [];
1569
            }
1570
        }
1571
1572
        if (!isset($options['strict'])) {
1573
            $options['strict'] = $this->connection->getConfig('fields_strict');
1574
        }
1575
1576
        foreach (['master', 'lock', 'fetch_sql', 'array', 'distinct', 'procedure'] as $name) {
1577
            if (!isset($options[$name])) {
1578
                $options[$name] = false;
1579
            }
1580
        }
1581
1582
        foreach (['group', 'having', 'limit', 'force', 'comment', 'partition', 'duplicate', 'extra'] as $name) {
1583
            if (!isset($options[$name])) {
1584
                $options[$name] = '';
1585
            }
1586
        }
1587
1588
        if (isset($options['page'])) {
1589
            // 根据页数计算limit
1590
            list($page, $listRows) = $options['page'];
1591
            $page                  = $page > 0 ? $page : 1;
1592
            $listRows              = $listRows ?: (is_numeric($options['limit']) ? $options['limit'] : 20);
1593
            $offset                = $listRows * ($page - 1);
1594
            $options['limit']      = $offset . ',' . $listRows;
1595
        }
1596
1597
        $this->options = $options;
1598
1599
        return $options;
1600
    }
1601
1602
    /**
1603
     * 分析数据是否存在更新条件
1604
     * @access public
1605
     * @param array $data 数据
1606
     * @return bool
1607
     * @throws Exception
1608
     */
1609
    public function parseUpdateData(&$data): bool
1610
    {
1611
        $pk       = $this->getPk();
1612
        $isUpdate = false;
1613
        // 如果存在主键数据 则自动作为更新条件
1614
        if (is_string($pk) && isset($data[$pk])) {
1615
            $this->where($pk, '=', $data[$pk]);
1616
            $this->options['key'] = $data[$pk];
1617
            unset($data[$pk]);
1618
            $isUpdate = true;
1619
        } elseif (is_array($pk)) {
1620
            foreach ($pk as $field) {
1621
                if (isset($data[$field])) {
1622
                    $this->where($field, '=', $data[$field]);
1623
                    $isUpdate = true;
1624
                } else {
1625
                    // 如果缺少复合主键数据则不执行
1626
                    throw new Exception('miss complex primary data');
1627
                }
1628
                unset($data[$field]);
1629
            }
1630
        }
1631
1632
        return $isUpdate;
1633
    }
1634
1635
    /**
1636
     * 把主键值转换为查询条件 支持复合主键
1637
     * @access public
1638
     * @param array|string $data 主键数据
1639
     * @return void
1640
     * @throws Exception
1641
     */
1642
    public function parsePkWhere($data): void
1643
    {
1644
        $pk = $this->getPk();
1645
1646
        if (is_string($pk)) {
1647
            // 获取数据表
1648
            if (empty($this->options['table'])) {
1649
                $this->options['table'] = $this->getTable();
1650
            }
1651
1652
            $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table'];
1653
1654
            if (!empty($this->options['alias'][$table])) {
1655
                $alias = $this->options['alias'][$table];
1656
            }
1657
1658
            $key = isset($alias) ? $alias . '.' . $pk : $pk;
1659
            // 根据主键查询
1660
            if (is_array($data)) {
1661
                $this->where($key, 'in', $data);
1662
            } else {
1663
                $this->where($key, '=', $data);
1664
                $this->options['key'] = $data;
1665
            }
1666
        }
1667
    }
1668
1669
    /**
1670
     * 获取模型的更新条件
1671
     * @access protected
1672
     * @param array $options 查询参数
1673
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1674
    protected function getModelUpdateCondition(array $options)
1675
    {
1676
        return $options['where']['AND'] ?? null;
1677
    }
1678
}
1679