Completed
Push — 6.0 ( be0b6e...c47dd5 )
by liu
06:54 queued 10s
created

BaseQuery::tableField()   B

Complexity

Conditions 11
Paths 33

Size

Total Lines 34
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 0
Metric Value
cc 11
eloc 18
nc 33
nop 4
dl 0
loc 34
ccs 0
cts 19
cp 0
crap 132
rs 7.3166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

228
        return $this->connection->/** @scrutinizer ignore-call */ query($this, $sql, $bind);
Loading history...
229
    }
230
231
    /**
232
     * 执行语句
233
     * @access public
234
     * @param string $sql  sql指令
235
     * @param array  $bind 参数绑定
236
     * @return int
237
     * @throws BindParamException
238
     * @throws PDOException
239
     */
240
    public function execute(string $sql, array $bind = []): int
241
    {
242
        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

242
        return $this->connection->/** @scrutinizer ignore-call */ execute($this, $sql, $bind, true);
Loading history...
243
    }
244
245
    /**
246
     * 获取返回或者影响的记录数
247
     * @access public
248
     * @return integer
249
     */
250
    public function getNumRows(): int
251
    {
252
        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

252
        return $this->connection->/** @scrutinizer ignore-call */ getNumRows();
Loading history...
253
    }
254
255
    /**
256
     * 获取最近一次查询的sql语句
257
     * @access public
258
     * @return string
259
     */
260
    public function getLastSql(): string
261
    {
262
        return $this->connection->getLastSql();
263
    }
264
265
    /**
266
     * 获取最近插入的ID
267
     * @access public
268
     * @param string $sequence 自增序列名
269
     * @return mixed
270
     */
271
    public function getLastInsID(string $sequence = null)
272
    {
273
        return $this->connection->getLastInsID($this, $sequence);
274
    }
275
276
    /**
277
     * 批处理执行SQL语句
278
     * 批处理的指令都认为是execute操作
279
     * @access public
280
     * @param array $sql SQL批处理指令
281
     * @return bool
282
     */
283
    public function batchQuery(array $sql = []): bool
284
    {
285
        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

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

368
            /** @scrutinizer ignore-call */ 
369
            $fields = $this->getTableFields();
Loading history...
369
            $field  = $fields ?: ['*'];
370
        }
371
372
        if (isset($this->options['field'])) {
373
            $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

373
            $field = array_merge((array) $this->options['field'], /** @scrutinizer ignore-type */ $field);
Loading history...
374
        }
375
376
        $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

376
        $this->options['field'] = array_unique(/** @scrutinizer ignore-type */ $field);
Loading history...
377
378
        return $this;
379
    }
380
381
    /**
382
     * 指定要排除的查询字段
383
     * @access public
384
     * @param array|string $field 要排除的字段
385
     * @return $this
386
     */
387
    public function withoutField($field)
388
    {
389
        if (empty($field)) {
390
            return $this;
391
        }
392
393
        if (is_string($field)) {
394
            $field = array_map('trim', explode(',', $field));
395
        }
396
397
        // 字段排除
398
        $fields = $this->getTableFields();
399
        $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

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

447
            $field = array_merge((array) $this->options['field'], /** @scrutinizer ignore-type */ $field);
Loading history...
448
        }
449
450
        $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

450
        $this->options['field'] = array_unique(/** @scrutinizer ignore-type */ $field);
Loading history...
451
452
        return $this;
453
    }
454
455
    /**
456
     * 表达式方式指定查询字段
457
     * @access public
458
     * @param string $field 字段名
459
     * @return $this
460
     */
461
    public function fieldRaw(string $field)
462
    {
463
        $this->options['field'][] = new Raw($field);
464
465
        return $this;
466
    }
467
468
    /**
469
     * 设置数据
470
     * @access public
471
     * @param array $data 数据
472
     * @return $this
473
     */
474
    public function data(array $data)
475
    {
476
        $this->options['data'] = $data;
477
478
        return $this;
479
    }
480
481
    /**
482
     * 字段值增长
483
     * @access public
484
     * @param string  $field    字段名
485
     * @param float   $step     增长值
486
     * @param integer $lazyTime 延时时间(s)
487
     * @param string  $op       INC/DEC
488
     * @return $this
489
     */
490
    public function inc(string $field, float $step = 1, int $lazyTime = 0, string $op = 'INC')
491
    {
492
        if ($lazyTime > 0) {
493
            // 延迟写入
494
            $condition = $this->options['where'] ?? [];
495
496
            $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition));
497
            $step = $this->connection->lazyWrite($op, $guid, $step, $lazyTime);
498
499
            if (false === $step) {
500
                return $this;
501
            }
502
503
            $op = 'INC';
504
        }
505
506
        $this->options['data'][$field] = [$op, $step];
507
508
        return $this;
509
    }
510
511
    /**
512
     * 字段值减少
513
     * @access public
514
     * @param string  $field    字段名
515
     * @param float   $step     增长值
516
     * @param integer $lazyTime 延时时间(s)
517
     * @return $this
518
     */
519
    public function dec(string $field, float $step = 1, int $lazyTime = 0)
520
    {
521
        return $this->inc($field, $step, $lazyTime, 'DEC');
522
    }
523
524
    /**
525
     * 使用表达式设置数据
526
     * @access public
527
     * @param string $field 字段名
528
     * @param string $value 字段值
529
     * @return $this
530
     */
531
    public function exp(string $field, string $value)
532
    {
533
        $this->options['data'][$field] = new Raw($value);
534
        return $this;
535
    }
536
537
    /**
538
     * 去除查询参数
539
     * @access public
540
     * @param string $option 参数名 留空去除所有参数
541
     * @return $this
542
     */
543
    public function removeOption(string $option = '')
544
    {
545
        if ('' === $option) {
546
            $this->options = [];
547
            $this->bind    = [];
548
        } elseif (isset($this->options[$option])) {
549
            unset($this->options[$option]);
550
        }
551
552
        return $this;
553
    }
554
555
    /**
556
     * 指定查询数量
557
     * @access public
558
     * @param int $offset 起始位置
559
     * @param int $length 查询数量
560
     * @return $this
561
     */
562
    public function limit(int $offset, int $length = null)
563
    {
564
        $this->options['limit'] = $offset . ($length ? ',' . $length : '');
565
566
        return $this;
567
    }
568
569
    /**
570
     * 指定分页
571
     * @access public
572
     * @param int $page     页数
573
     * @param int $listRows 每页数量
574
     * @return $this
575
     */
576
    public function page(int $page, int $listRows = null)
577
    {
578
        $this->options['page'] = [$page, $listRows];
579
580
        return $this;
581
    }
582
583
    /**
584
     * 分页查询
585
     * @access public
586
     * @param int|array $listRows 每页数量 数组表示配置参数
587
     * @param int|bool  $simple   是否简洁模式或者总记录数
588
     * @param array     $config   配置参数
589
     * @return Paginator
590
     * @throws DbException
591
     */
592
    public function paginate($listRows = null, $simple = false, $config = [])
593
    {
594
        if (is_int($simple)) {
595
            $total  = $simple;
596
            $simple = false;
597
        }
598
599
        $defaultConfig = [
600
            'query'     => [], //url额外参数
601
            'fragment'  => '', //url锚点
602
            'var_page'  => 'page', //分页变量
603
            'list_rows' => 15, //每页数量
604
        ];
605
606
        if (is_array($listRows)) {
607
            $config   = array_merge($defaultConfig, $listRows);
608
            $listRows = intval($config['list_rows']);
609
        } else {
610
            $config   = array_merge($defaultConfig, $config);
611
            $listRows = intval($listRows ?: $config['list_rows']);
612
        }
613
614
        $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']);
615
616
        $page = $page < 1 ? 1 : $page;
617
618
        $config['path'] = $config['path'] ?? Paginator::getCurrentPath();
619
620
        if (!isset($total) && !$simple) {
621
            $options = $this->getOptions();
622
623
            unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']);
624
625
            $bind    = $this->bind;
626
            $total   = $this->count();
627
            $results = $this->options($options)->bind($bind)->page($page, $listRows)->select();
628
        } elseif ($simple) {
629
            $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
630
            $total   = null;
631
        } else {
632
            $results = $this->page($page, $listRows)->select();
633
        }
634
635
        $this->removeOption('limit');
636
        $this->removeOption('page');
637
638
        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...
639
    }
640
641
    /**
642
     * 表达式方式指定当前操作的数据表
643
     * @access public
644
     * @param mixed $table 表名
645
     * @return $this
646
     */
647
    public function tableRaw(string $table)
648
    {
649
        $this->options['table'] = new Raw($table);
650
651
        return $this;
652
    }
653
654
    /**
655
     * 指定当前操作的数据表
656
     * @access public
657
     * @param mixed $table 表名
658
     * @return $this
659
     */
660
    public function table($table)
661
    {
662
        if (is_string($table)) {
663
            if (strpos($table, ')')) {
664
                // 子查询
665
            } elseif (false === strpos($table, ',')) {
666
                if (strpos($table, ' ')) {
667
                    list($item, $alias) = explode(' ', $table);
668
                    $table              = [];
669
                    $this->alias([$item => $alias]);
670
                    $table[$item] = $alias;
671
                }
672
            } else {
673
                $tables = explode(',', $table);
674
                $table  = [];
675
676
                foreach ($tables as $item) {
677
                    $item = trim($item);
678
                    if (strpos($item, ' ')) {
679
                        list($item, $alias) = explode(' ', $item);
680
                        $this->alias([$item => $alias]);
681
                        $table[$item] = $alias;
682
                    } else {
683
                        $table[] = $item;
684
                    }
685
                }
686
            }
687
        } elseif (is_array($table)) {
688
            $tables = $table;
689
            $table  = [];
690
691
            foreach ($tables as $key => $val) {
692
                if (is_numeric($key)) {
693
                    $table[] = $val;
694
                } else {
695
                    $this->alias([$key => $val]);
696
                    $table[$key] = $val;
697
                }
698
            }
699
        }
700
701
        $this->options['table'] = $table;
702
703
        return $this;
704
    }
705
706
    /**
707
     * USING支持 用于多表删除
708
     * @access public
709
     * @param mixed $using USING
710
     * @return $this
711
     */
712
    public function using($using)
713
    {
714
        $this->options['using'] = $using;
715
        return $this;
716
    }
717
718
    /**
719
     * 存储过程调用
720
     * @access public
721
     * @param bool $procedure 是否为存储过程查询
722
     * @return $this
723
     */
724
    public function procedure(bool $procedure = true)
725
    {
726
        $this->options['procedure'] = $procedure;
727
        return $this;
728
    }
729
730
    /**
731
     * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc'])
732
     * @access public
733
     * @param string|array|Raw $field 排序字段
734
     * @param string           $order 排序
735
     * @return $this
736
     */
737
    public function order($field, string $order = '')
738
    {
739
        if (empty($field)) {
740
            return $this;
741
        } elseif ($field instanceof Raw) {
742
            $this->options['order'][] = $field;
743
            return $this;
744
        }
745
746
        if (is_string($field)) {
747
            if (!empty($this->options['via'])) {
748
                $field = $this->options['via'] . '.' . $field;
749
            }
750
            if (strpos($field, ',')) {
751
                $field = array_map('trim', explode(',', $field));
752
            } else {
753
                $field = empty($order) ? $field : [$field => $order];
754
            }
755
        } elseif (!empty($this->options['via'])) {
756
            foreach ($field as $key => $val) {
757
                if (is_numeric($key)) {
758
                    $field[$key] = $this->options['via'] . '.' . $val;
759
                } else {
760
                    $field[$this->options['via'] . '.' . $key] = $val;
761
                    unset($field[$key]);
762
                }
763
            }
764
        }
765
766
        if (!isset($this->options['order'])) {
767
            $this->options['order'] = [];
768
        }
769
770
        if (is_array($field)) {
771
            $this->options['order'] = array_merge($this->options['order'], $field);
772
        } else {
773
            $this->options['order'][] = $field;
774
        }
775
776
        return $this;
777
    }
778
779
    /**
780
     * 表达式方式指定Field排序
781
     * @access public
782
     * @param string $field 排序字段
783
     * @param array  $bind  参数绑定
784
     * @return $this
785
     */
786
    public function orderRaw(string $field, array $bind = [])
787
    {
788
        if (!empty($bind)) {
789
            $this->bindParams($field, $bind);
790
        }
791
792
        $this->options['order'][] = new Raw($field);
793
794
        return $this;
795
    }
796
797
    /**
798
     * 指定Field排序 orderField('id',[1,2,3],'desc')
799
     * @access public
800
     * @param string $field  排序字段
801
     * @param array  $values 排序值
802
     * @param string $order  排序 desc/asc
803
     * @return $this
804
     */
805
    public function orderField(string $field, array $values, string $order = '')
806
    {
807
        if (!empty($values)) {
808
            $values['sort'] = $order;
809
810
            $this->options['order'][$field] = $values;
811
        }
812
813
        return $this;
814
    }
815
816
    /**
817
     * 随机排序
818
     * @access public
819
     * @return $this
820
     */
821
    public function orderRand()
822
    {
823
        $this->options['order'][] = '[rand]';
824
        return $this;
825
    }
826
827
    /**
828
     * 查询缓存
829
     * @access public
830
     * @param mixed             $key    缓存key
831
     * @param integer|\DateTime $expire 缓存有效期
832
     * @param string            $tag    缓存标签
833
     * @return $this
834
     */
835
    public function cache($key = true, $expire = null, string $tag = null)
836
    {
837
        if (false === $key) {
838
            return $this;
839
        }
840
841
        if ($key instanceof \DateTimeInterface || $key instanceof \DateInterval || (is_int($key) && is_null($expire))) {
842
            $expire = $key;
843
            $key    = true;
844
        }
845
846
        $this->options['cache'] = [$key, $expire, $tag];
847
848
        return $this;
849
    }
850
851
    /**
852
     * 指定group查询
853
     * @access public
854
     * @param string|array $group GROUP
855
     * @return $this
856
     */
857
    public function group($group)
858
    {
859
        $this->options['group'] = $group;
860
        return $this;
861
    }
862
863
    /**
864
     * 指定having查询
865
     * @access public
866
     * @param string $having having
867
     * @return $this
868
     */
869
    public function having(string $having)
870
    {
871
        $this->options['having'] = $having;
872
        return $this;
873
    }
874
875
    /**
876
     * 指定查询lock
877
     * @access public
878
     * @param bool|string $lock 是否lock
879
     * @return $this
880
     */
881
    public function lock($lock = false)
882
    {
883
        $this->options['lock'] = $lock;
884
885
        if ($lock) {
886
            $this->options['master'] = true;
887
        }
888
889
        return $this;
890
    }
891
892
    /**
893
     * 指定distinct查询
894
     * @access public
895
     * @param bool $distinct 是否唯一
896
     * @return $this
897
     */
898
    public function distinct(bool $distinct = true)
899
    {
900
        $this->options['distinct'] = $distinct;
901
        return $this;
902
    }
903
904
    /**
905
     * 指定数据表别名
906
     * @access public
907
     * @param array|string $alias 数据表别名
908
     * @return $this
909
     */
910
    public function alias($alias)
911
    {
912
        if (is_array($alias)) {
913
            $this->options['alias'] = $alias;
914
        } else {
915
            $table = $this->getTable();
916
917
            $this->options['alias'][$table] = $alias;
918
        }
919
920
        return $this;
921
    }
922
923
    /**
924
     * 指定强制索引
925
     * @access public
926
     * @param string $force 索引名称
927
     * @return $this
928
     */
929
    public function force(string $force)
930
    {
931
        $this->options['force'] = $force;
932
        return $this;
933
    }
934
935
    /**
936
     * 查询注释
937
     * @access public
938
     * @param string $comment 注释
939
     * @return $this
940
     */
941
    public function comment(string $comment)
942
    {
943
        $this->options['comment'] = $comment;
944
        return $this;
945
    }
946
947
    /**
948
     * 获取执行的SQL语句而不进行实际的查询
949
     * @access public
950
     * @param bool $fetch 是否返回sql
951
     * @return $this|Fetch
952
     */
953
    public function fetchSql(bool $fetch = true)
954
    {
955
        $this->options['fetch_sql'] = $fetch;
956
957
        if ($fetch) {
958
            return new Fetch($this);
959
        }
960
961
        return $this;
962
    }
963
964
    /**
965
     * 设置从主服务器读取数据
966
     * @access public
967
     * @param bool $readMaster 是否从主服务器读取
968
     * @return $this
969
     */
970
    public function master(bool $readMaster = true)
971
    {
972
        $this->options['master'] = $readMaster;
973
        return $this;
974
    }
975
976
    /**
977
     * 设置是否严格检查字段名
978
     * @access public
979
     * @param bool $strict 是否严格检查字段
980
     * @return $this
981
     */
982
    public function strict(bool $strict = true)
983
    {
984
        $this->options['strict'] = $strict;
985
        return $this;
986
    }
987
988
    /**
989
     * 设置自增序列名
990
     * @access public
991
     * @param string $sequence 自增序列名
992
     * @return $this
993
     */
994
    public function sequence(string $sequence = null)
995
    {
996
        $this->options['sequence'] = $sequence;
997
        return $this;
998
    }
999
1000
    /**
1001
     * 设置是否REPLACE
1002
     * @access public
1003
     * @param bool $replace 是否使用REPLACE写入数据
1004
     * @return $this
1005
     */
1006
    public function replace(bool $replace = true)
1007
    {
1008
        $this->options['replace'] = $replace;
1009
        return $this;
1010
    }
1011
1012
    /**
1013
     * 设置当前查询所在的分区
1014
     * @access public
1015
     * @param string|array $partition 分区名称
1016
     * @return $this
1017
     */
1018
    public function partition($partition)
1019
    {
1020
        $this->options['partition'] = $partition;
1021
        return $this;
1022
    }
1023
1024
    /**
1025
     * 设置DUPLICATE
1026
     * @access public
1027
     * @param array|string|Raw $duplicate DUPLICATE信息
1028
     * @return $this
1029
     */
1030
    public function duplicate($duplicate)
1031
    {
1032
        $this->options['duplicate'] = $duplicate;
1033
        return $this;
1034
    }
1035
1036
    /**
1037
     * 设置查询的额外参数
1038
     * @access public
1039
     * @param string $extra 额外信息
1040
     * @return $this
1041
     */
1042
    public function extra(string $extra)
1043
    {
1044
        $this->options['extra'] = $extra;
1045
        return $this;
1046
    }
1047
1048
    /**
1049
     * 设置JSON字段信息
1050
     * @access public
1051
     * @param array $json  JSON字段
1052
     * @param bool  $assoc 是否取出数组
1053
     * @return $this
1054
     */
1055
    public function json(array $json = [], bool $assoc = false)
1056
    {
1057
        $this->options['json']       = $json;
1058
        $this->options['json_assoc'] = $assoc;
1059
        return $this;
1060
    }
1061
1062
    /**
1063
     * 指定数据表主键
1064
     * @access public
1065
     * @param string $pk 主键
1066
     * @return $this
1067
     */
1068
    public function pk(string $pk)
1069
    {
1070
        $this->pk = $pk;
1071
        return $this;
1072
    }
1073
1074
    /**
1075
     * 获取当前数据表的主键
1076
     * @access public
1077
     * @return string|array
1078
     */
1079
    public function getPk()
1080
    {
1081
        if (empty($this->pk)) {
1082
            $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

1082
            /** @scrutinizer ignore-call */ 
1083
            $this->pk = $this->connection->getPk($this->getTable());
Loading history...
1083
        }
1084
1085
        return $this->pk;
1086
    }
1087
1088
    /**
1089
     * 查询参数批量赋值
1090
     * @access protected
1091
     * @param array $options 表达式参数
1092
     * @return $this
1093
     */
1094
    protected function options(array $options)
1095
    {
1096
        $this->options = $options;
1097
        return $this;
1098
    }
1099
1100
    /**
1101
     * 获取当前的查询参数
1102
     * @access public
1103
     * @param string $name 参数名
1104
     * @return mixed
1105
     */
1106
    public function getOptions(string $name = '')
1107
    {
1108
        if ('' === $name) {
1109
            return $this->options;
1110
        }
1111
1112
        return $this->options[$name] ?? null;
1113
    }
1114
1115
    /**
1116
     * 设置当前的查询参数
1117
     * @access public
1118
     * @param string $option 参数名
1119
     * @param mixed  $value  参数值
1120
     * @return $this
1121
     */
1122
    public function setOption(string $option, $value)
1123
    {
1124
        $this->options[$option] = $value;
1125
        return $this;
1126
    }
1127
1128
    /**
1129
     * 设置当前字段添加的表别名
1130
     * @access public
1131
     * @param string $via 临时表别名
1132
     * @return $this
1133
     */
1134
    public function via(string $via = '')
1135
    {
1136
        $this->options['via'] = $via;
1137
1138
        return $this;
1139
    }
1140
1141
    /**
1142
     * 保存记录 自动判断insert或者update
1143
     * @access public
1144
     * @param array $data        数据
1145
     * @param bool  $forceInsert 是否强制insert
1146
     * @return integer
1147
     */
1148
    public function save(array $data = [], bool $forceInsert = false)
1149
    {
1150
        if ($forceInsert) {
1151
            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...
1152
        }
1153
1154
        $this->options['data'] = array_merge($this->options['data'] ?? [], $data);
1155
1156
        if (!empty($this->options['where'])) {
1157
            $isUpdate = true;
1158
        } else {
1159
            $isUpdate = $this->parseUpdateData($this->options['data']);
1160
        }
1161
1162
        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...
1163
    }
1164
1165
    /**
1166
     * 插入记录
1167
     * @access public
1168
     * @param array   $data         数据
1169
     * @param boolean $getLastInsID 返回自增主键
1170
     * @return integer|string
1171
     */
1172
    public function insert(array $data = [], bool $getLastInsID = false)
1173
    {
1174
        if (!empty($data)) {
1175
            $this->options['data'] = $data;
1176
        }
1177
1178
        return $this->connection->insert($this, $getLastInsID);
1179
    }
1180
1181
    /**
1182
     * 插入记录并获取自增ID
1183
     * @access public
1184
     * @param array $data 数据
1185
     * @return integer|string
1186
     */
1187
    public function insertGetId(array $data)
1188
    {
1189
        return $this->insert($data, true);
1190
    }
1191
1192
    /**
1193
     * 批量插入记录
1194
     * @access public
1195
     * @param array   $dataSet 数据集
1196
     * @param integer $limit   每次写入数据限制
1197
     * @return integer
1198
     */
1199
    public function insertAll(array $dataSet = [], int $limit = 0): int
1200
    {
1201
        if (empty($dataSet)) {
1202
            $dataSet = $this->options['data'] ?? [];
1203
        }
1204
1205
        if (empty($limit) && !empty($this->options['limit']) && is_numeric($this->options['limit'])) {
1206
            $limit = (int) $this->options['limit'];
1207
        }
1208
1209
        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

1209
        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...
1210
    }
1211
1212
    /**
1213
     * 通过Select方式插入记录
1214
     * @access public
1215
     * @param array  $fields 要插入的数据表字段名
1216
     * @param string $table  要插入的数据表名
1217
     * @return integer
1218
     * @throws PDOException
1219
     */
1220
    public function selectInsert(array $fields, string $table): int
1221
    {
1222
        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

1222
        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...
1223
    }
1224
1225
    /**
1226
     * 更新记录
1227
     * @access public
1228
     * @param mixed $data 数据
1229
     * @return integer
1230
     * @throws Exception
1231
     * @throws PDOException
1232
     */
1233
    public function update(array $data = []): int
1234
    {
1235
        if (!empty($data)) {
1236
            $this->options['data'] = array_merge($this->options['data'] ?? [], $data);
1237
        }
1238
1239
        if (empty($this->options['where'])) {
1240
            $this->parseUpdateData($this->options['data']);
1241
        }
1242
1243
        if (empty($this->options['where']) && $this->model) {
1244
            $this->where($this->model->getWhere());
1245
        }
1246
1247
        if (empty($this->options['where'])) {
1248
            // 如果没有任何更新条件则不执行
1249
            throw new Exception('miss update condition');
1250
        }
1251
1252
        return $this->connection->update($this);
1253
    }
1254
1255
    /**
1256
     * 删除记录
1257
     * @access public
1258
     * @param mixed $data 表达式 true 表示强制删除
1259
     * @return int
1260
     * @throws Exception
1261
     * @throws PDOException
1262
     */
1263
    public function delete($data = null): int
1264
    {
1265
        if (!is_null($data) && true !== $data) {
1266
            // AR模式分析主键条件
1267
            $this->parsePkWhere($data);
1268
        }
1269
1270
        if (empty($this->options['where']) && $this->model) {
1271
            $this->where($this->model->getWhere());
1272
        }
1273
1274
        if (true !== $data && empty($this->options['where'])) {
1275
            // 如果条件为空 不进行删除操作 除非设置 1=1
1276
            throw new Exception('delete without condition');
1277
        }
1278
1279
        if (!empty($this->options['soft_delete'])) {
1280
            // 软删除
1281
            list($field, $condition) = $this->options['soft_delete'];
1282
            if ($condition) {
1283
                unset($this->options['soft_delete']);
1284
                $this->options['data'] = [$field => $condition];
1285
1286
                return $this->connection->update($this);
1287
            }
1288
        }
1289
1290
        $this->options['data'] = $data;
1291
1292
        return $this->connection->delete($this);
1293
    }
1294
1295
    /**
1296
     * 查找记录
1297
     * @access public
1298
     * @param mixed $data 数据
1299
     * @return Collection
1300
     * @throws DbException
1301
     * @throws ModelNotFoundException
1302
     * @throws DataNotFoundException
1303
     */
1304
    public function select($data = null): Collection
1305
    {
1306
        if (!is_null($data)) {
1307
            // 主键条件分析
1308
            $this->parsePkWhere($data);
1309
        }
1310
1311
        $resultSet = $this->connection->select($this);
1312
1313
        // 返回结果处理
1314
        if (!empty($this->options['fail']) && count($resultSet) == 0) {
1315
            $this->throwNotFound();
1316
        }
1317
1318
        // 数据列表读取后的处理
1319
        if (!empty($this->model)) {
1320
            // 生成模型对象
1321
            $resultSet = $this->resultSetToModelCollection($resultSet);
1322
        } else {
1323
            $this->resultSet($resultSet);
1324
        }
1325
1326
        return $resultSet;
1327
    }
1328
1329
    /**
1330
     * 查找单条记录
1331
     * @access public
1332
     * @param mixed $data 查询数据
1333
     * @return array|Model|null
1334
     * @throws DbException
1335
     * @throws ModelNotFoundException
1336
     * @throws DataNotFoundException
1337
     */
1338
    public function find($data = null)
1339
    {
1340
        if (!is_null($data)) {
1341
            // AR模式分析主键条件
1342
            $this->parsePkWhere($data);
1343
        }
1344
1345
        $result = $this->connection->find($this);
1346
1347
        // 数据处理
1348
        if (empty($result)) {
1349
            return $this->resultToEmpty();
1350
        }
1351
1352
        if (!empty($this->model)) {
1353
            // 返回模型对象
1354
            $this->resultToModel($result, $this->options);
1355
        } else {
1356
            $this->result($result);
1357
        }
1358
1359
        return $result;
1360
    }
1361
1362
    /**
1363
     * 分批数据返回处理
1364
     * @access public
1365
     * @param integer      $count    每次处理的数据数量
1366
     * @param callable     $callback 处理回调方法
1367
     * @param string|array $column   分批处理的字段名
1368
     * @param string       $order    字段排序
1369
     * @return bool
1370
     * @throws DbException
1371
     */
1372
    public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool
1373
    {
1374
        $options = $this->getOptions();
1375
        $column  = $column ?: $this->getPk();
1376
1377
        if (isset($options['order'])) {
1378
            unset($options['order']);
1379
        }
1380
1381
        $bind = $this->bind;
1382
1383
        if (is_array($column)) {
1384
            $times = 1;
1385
            $query = $this->options($options)->page($times, $count);
1386
        } else {
1387
            $query = $this->options($options)->limit($count);
1388
1389
            if (strpos($column, '.')) {
1390
                list($alias, $key) = explode('.', $column);
1391
            } else {
1392
                $key = $column;
1393
            }
1394
        }
1395
1396
        $resultSet = $query->order($column, $order)->select();
1397
1398
        while (count($resultSet) > 0) {
1399
            if (false === call_user_func($callback, $resultSet)) {
1400
                return false;
1401
            }
1402
1403
            if (isset($times)) {
1404
                $times++;
1405
                $query = $this->options($options)->page($times, $count);
1406
            } else {
1407
                $end    = end($resultSet);
0 ignored issues
show
Bug introduced by
$resultSet of type think\Collection is incompatible with the type array expected by parameter $array of end(). ( Ignorable by Annotation )

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

1407
                $end    = end(/** @scrutinizer ignore-type */ $resultSet);
Loading history...
1408
                $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...
1409
1410
                $query = $this->options($options)
1411
                    ->limit($count)
1412
                    ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
1413
            }
1414
1415
            $resultSet = $query->bind($bind)->order($column, $order)->select();
1416
        }
1417
1418
        return true;
1419
    }
1420
1421
    /**
1422
     * 创建子查询SQL
1423
     * @access public
1424
     * @param bool $sub 是否添加括号
1425
     * @return string
1426
     * @throws DbException
1427
     */
1428
    public function buildSql(bool $sub = true): string
1429
    {
1430
        return $sub ? '( ' . $this->fetchSql()->select() . ' )' : $this->fetchSql()->select();
1431
    }
1432
1433
    /**
1434
     * 分析表达式(可用于查询或者写入操作)
1435
     * @access public
1436
     * @return array
1437
     */
1438
    public function parseOptions(): array
1439
    {
1440
        $options = $this->getOptions();
1441
1442
        // 获取数据表
1443
        if (empty($options['table'])) {
1444
            $options['table'] = $this->getTable();
1445
        }
1446
1447
        if (!isset($options['where'])) {
1448
            $options['where'] = [];
1449
        } elseif (isset($options['view'])) {
1450
            // 视图查询条件处理
1451
            $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

1451
            $this->/** @scrutinizer ignore-call */ 
1452
                   parseView($options);
Loading history...
1452
        }
1453
1454
        if (!isset($options['field'])) {
1455
            $options['field'] = '*';
1456
        }
1457
1458
        foreach (['data', 'order', 'join', 'union'] as $name) {
1459
            if (!isset($options[$name])) {
1460
                $options[$name] = [];
1461
            }
1462
        }
1463
1464
        if (!isset($options['strict'])) {
1465
            $options['strict'] = $this->connection->getConfig('fields_strict');
1466
        }
1467
1468
        foreach (['master', 'lock', 'fetch_sql', 'array', 'distinct', 'procedure'] as $name) {
1469
            if (!isset($options[$name])) {
1470
                $options[$name] = false;
1471
            }
1472
        }
1473
1474
        foreach (['group', 'having', 'limit', 'force', 'comment', 'partition', 'duplicate', 'extra'] as $name) {
1475
            if (!isset($options[$name])) {
1476
                $options[$name] = '';
1477
            }
1478
        }
1479
1480
        if (isset($options['page'])) {
1481
            // 根据页数计算limit
1482
            list($page, $listRows) = $options['page'];
1483
            $page                  = $page > 0 ? $page : 1;
1484
            $listRows              = $listRows ?: (is_numeric($options['limit']) ? $options['limit'] : 20);
1485
            $offset                = $listRows * ($page - 1);
1486
            $options['limit']      = $offset . ',' . $listRows;
1487
        }
1488
1489
        $this->options = $options;
1490
1491
        return $options;
1492
    }
1493
1494
}
1495