Completed
Push — 6.0 ( 7f1370...dfc5cb )
by liu
03:40
created

Query::whereTime()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 2
nop 4
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
// +----------------------------------------------------------------------
1 ignored issue
show
Coding Style introduced by
You must use "/**" style comments for a file comment
Loading history...
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 Closure;
16
use PDO;
17
use PDOStatement;
18
use think\App;
19
use think\Collection;
20
use think\Config;
21
use think\Db;
22
use think\db\exception\BindParamException;
23
use think\db\exception\DataNotFoundException;
24
use think\db\exception\ModelNotFoundException;
25
use think\Event;
26
use think\Exception;
27
use think\exception\DbException;
28
use think\exception\PDOException;
29
use think\Model;
30
use think\model\Collection as ModelCollection;
31
use think\model\Relation;
32
use think\model\relation\OneToOne;
33
34
class Query
1 ignored issue
show
Coding Style introduced by
Missing doc comment for class Query
Loading history...
35
{
36
    /**
37
     * 当前数据库连接对象
38
     * @var Connection
39
     */
40
    protected $connection;
41
42
    /**
43
     * 当前模型对象
44
     * @var Model
45
     */
46
    protected $model;
47
48
    /**
49
     * 事件对象
50
     * @var Event
51
     */
52
    protected $event;
53
54
    /**
55
     * 配置对象
56
     * @var Config
57
     */
58
    protected $config;
59
60
    /**
61
     * Db对象
62
     * @var Db
63
     */
64
    protected $db;
65
66
    /**
67
     * 当前数据表名称(不含前缀)
68
     * @var string
69
     */
70
    protected $name = '';
71
72
    /**
73
     * 当前数据表主键
74
     * @var string|array
75
     */
76
    protected $pk;
77
78
    /**
79
     * 当前数据表前缀
80
     * @var string
81
     */
82
    protected $prefix = '';
83
84
    /**
85
     * 当前查询参数
86
     * @var array
87
     */
88
    protected $options = [];
89
90
    /**
91
     * 分页查询配置
92
     * @var array
93
     */
94
    protected $paginateConfig = [
95
        'query'     => [], //url额外参数
96
        'fragment'  => '', //url锚点
97
        'type'      => 'bootstrap', //分页类名
98
        'var_page'  => 'page', //分页变量
99
        'list_rows' => 15, //每页数量
100
    ];
101
102
    /**
103
     * 当前参数绑定
104
     * @var array
105
     */
106
    protected $bind = [];
107
108
    /**
109
     * 日期查询表达式
110
     * @var array
111
     */
112
    protected $timeRule = [
113
        'today'      => ['today', 'tomorrow'],
114
        'yesterday'  => ['yesterday', 'today'],
115
        'week'       => ['this week 00:00:00', 'next week 00:00:00'],
116
        'last week'  => ['last week 00:00:00', 'this week 00:00:00'],
117
        'month'      => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00'],
118
        'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00'],
119
        'year'       => ['this year 1/1', 'next year 1/1'],
120
        'last year'  => ['last year 1/1', 'this year 1/1'],
121
    ];
122
123
    /**
124
     * 架构函数
125
     * @access public
126
     * @param  Connection      $connection 数据库连接对象
127
     */
128
    public function __construct(Connection $connection)
129
    {
130
        $this->connection = $connection;
131
132
        $this->prefix = $this->connection->getConfig('prefix');
133
    }
134
135
    /**
136
     * 创建一个新的查询对象
137
     * @access public
138
     * @return Query
139
     */
140
    public function newQuery()
141
    {
142
        $query = new static($this->connection);
143
144
        if ($this->model) {
145
            $query->model($this->model);
146
        }
147
148
        if (isset($this->options['table'])) {
149
            $query->table($this->options['table']);
150
        } else {
151
            $query->name($this->name);
152
        }
153
154
        return $query;
155
    }
156
157
    /**
158
     * 利用__call方法实现一些特殊的Model方法
159
     * @access public
160
     * @param  string $method 方法名称
161
     * @param  array  $args   调用参数
162
     * @return mixed
163
     * @throws DbException
164
     * @throws Exception
165
     */
166
    public function __call(string $method, array $args)
167
    {
168
        if (strtolower(substr($method, 0, 5)) == 'getby') {
169
            // 根据某个字段获取记录
170
            $field = App::parseName(substr($method, 5));
171
            return $this->where($field, '=', $args[0])->find();
172
        } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
173
            // 根据某个字段获取记录的某个值
174
            $name = App::parseName(substr($method, 10));
175
            return $this->where($name, '=', $args[0])->value($args[1]);
176
        } elseif (strtolower(substr($method, 0, 7)) == 'whereor') {
177
            $name = App::parseName(substr($method, 7));
178
            array_unshift($args, $name);
179
            return call_user_func_array([$this, 'whereOr'], $args);
180
        } elseif (strtolower(substr($method, 0, 5)) == 'where') {
181
            $name = App::parseName(substr($method, 5));
182
            array_unshift($args, $name);
183
            return call_user_func_array([$this, 'where'], $args);
184
        } elseif ($this->model && method_exists($this->model, 'scope' . $method)) {
185
            // 动态调用命名范围
186
            $method = 'scope' . $method;
187
            array_unshift($args, $this);
188
189
            call_user_func_array([$this->model, $method], $args);
190
            return $this;
191
        } else {
192
            throw new Exception('method not exist:' . static::class . '->' . $method);
193
        }
194
    }
195
196
    /**
197
     * 获取当前的数据库Connection对象
198
     * @access public
199
     * @return Connection
200
     */
201
    public function getConnection()
202
    {
203
        return $this->connection;
204
    }
205
206
    /**
207
     * 设置当前的数据库Connection对象
208
     * @access public
209
     * @param  Connection      $connection 数据库连接对象
210
     * @return $this
211
     */
212
    public function setConnection($connection)
213
    {
214
        $this->connection = $connection;
215
216
        return $this;
217
    }
218
219
    /**
220
     * 设置配置对象
221
     * @access public
222
     * @param  Config $config
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
223
     * @return $this
224
     */
225
    public function setConfig(Config $config)
226
    {
227
        $this->config = $config;
228
229
        return $this;
230
    }
231
232
    /**
233
     * 设置Event对象
234
     * @access public
235
     * @param  Event $event
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
236
     * @return $this
237
     */
238
    public function setEvent(Event $event)
239
    {
240
        $this->event = $event;
241
242
        return $this;
243
    }
244
245
    /**
246
     * 设置Db对象
247
     * @access public
248
     * @param  Db $db
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
249
     * @return $this
250
     */
251
    public function setDb(Db $db)
252
    {
253
        $this->db = $db;
254
        $this->connection->setDb($db);
255
        return $this;
256
    }
257
258
    /**
259
     * 获取Db对象
260
     * @access public
261
     * @return Db
262
     */
263
    public function getDb()
264
    {
265
        return $this->db;
266
    }
267
268
    /**
269
     * 指定模型
270
     * @access public
271
     * @param  Model $model 模型对象实例
272
     * @return $this
273
     */
274
    public function model(Model $model)
275
    {
276
        $this->model = $model;
277
        return $this;
278
    }
279
280
    /**
281
     * 获取当前的模型对象
282
     * @access public
283
     * @return Model|null
284
     */
285
    public function getModel()
286
    {
287
        return $this->model ? $this->model->setQuery($this) : null;
288
    }
289
290
    /**
291
     * 指定当前数据表名(不含前缀)
292
     * @access public
293
     * @param  string $name 不含前缀的数据表名字
294
     * @return $this
295
     */
296
    public function name(string $name)
297
    {
298
        $this->name = $name;
299
        return $this;
300
    }
301
302
    /**
303
     * 获取当前的数据表名称
304
     * @access public
305
     * @return string
306
     */
307
    public function getName(): string
308
    {
309
        return $this->name ?: $this->model->getName();
310
    }
311
312
    /**
313
     * 获取数据库的配置参数
314
     * @access public
315
     * @param  string $name 参数名称
316
     * @return mixed
317
     */
318
    public function getConfig(string $name = '')
319
    {
320
        return $this->connection->getConfig($name);
321
    }
322
323
    /**
324
     * 得到当前或者指定名称的数据表
325
     * @access public
326
     * @param  string $name 不含前缀的数据表名字
327
     * @return mixed
328
     */
329
    public function getTable(string $name = '')
330
    {
331
        if (empty($name) && isset($this->options['table'])) {
332
            return $this->options['table'];
333
        }
334
335
        $name = $name ?: $this->name;
336
337
        return $this->prefix . App::parseName($name);
338
    }
339
340
    /**
341
     * 获取数据表字段信息
342
     * @access public
343
     * @param  string $tableName 数据表名
344
     * @return array
345
     */
346
    public function getTableFields($tableName = ''): array
347
    {
348
        if ('' == $tableName) {
349
            $tableName = $this->getTable();
350
        }
351
352
        return $this->connection->getTableFields($tableName);
353
    }
354
355
    /**
356
     * 设置字段类型信息
357
     * @access public
358
     * @param  array $type 字段类型信息
359
     * @return $this
360
     */
361
    public function setFieldType(array $type)
362
    {
363
        $this->options['field_type'] = $type;
364
        return $this;
365
    }
366
367
    /**
368
     * 获取字段类型信息
369
     * @access public
370
     * @return array
371
     */
372
    public function getFieldsType(): array
373
    {
374
        if (!empty($this->options['field_type'])) {
375
            return $this->options['field_type'];
376
        }
377
378
        return $this->connection->getFieldsType($this->getTable());
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->connection...Type($this->getTable()) could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
379
    }
380
381
    /**
382
     * 获取字段类型信息
383
     * @access public
384
     * @param  string $field 字段名
385
     * @return string|null
386
     */
387
    public function getFieldType(string $field)
388
    {
389
        $fieldType = $this->getFieldsType();
390
391
        return $fieldType[$field] ?? null;
392
    }
393
394
    /**
395
     * 获取字段类型信息
396
     * @access public
397
     * @return array
398
     */
399
    public function getFieldsBindType(): array
400
    {
401
        $fieldType = $this->getFieldsType();
402
403
        return array_map([$this->connection, 'getFieldBindType'], $fieldType);
404
    }
405
406
    /**
407
     * 获取字段类型信息
408
     * @access public
409
     * @param  string $field 字段名
410
     * @return int
411
     */
412
    public function getFieldBindType(string $field): int
413
    {
414
        $fieldType = $this->getFieldType($field);
415
416
        return $this->connection->getFieldBindType($fieldType ?: '');
417
    }
418
419
    /**
420
     * 执行查询 返回数据集
421
     * @access public
422
     * @param  string $sql    sql指令
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 4 found
Loading history...
423
     * @param  array  $bind   参数绑定
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
424
     * @return array
425
     * @throws BindParamException
426
     * @throws PDOException
427
     */
428
    public function query(string $sql, array $bind = []): array
429
    {
430
        return $this->connection->query($this, $sql, $bind, true);
431
    }
432
433
    /**
434
     * 执行语句
435
     * @access public
436
     * @param  string $sql  sql指令
437
     * @param  array  $bind 参数绑定
438
     * @return int
439
     * @throws BindParamException
440
     * @throws PDOException
441
     */
442
    public function execute(string $sql, array $bind = []): int
443
    {
444
        return $this->connection->execute($this, $sql, $bind, true);
445
    }
446
447
    /**
448
     * 监听SQL执行
449
     * @access public
450
     * @param  callable $callback 回调方法
451
     * @return void
452
     */
453
    public function listen(callable $callback): void
454
    {
455
        $this->connection->listen($callback);
456
    }
457
458
    /**
459
     * 获取最近插入的ID
460
     * @access public
461
     * @param  string $sequence 自增序列名
462
     * @return string
463
     */
464
    public function getLastInsID(string $sequence = null): string
465
    {
466
        return $this->connection->getLastInsID($sequence);
467
    }
468
469
    /**
470
     * 获取返回或者影响的记录数
471
     * @access public
472
     * @return integer
473
     */
474
    public function getNumRows(): int
475
    {
476
        return $this->connection->getNumRows();
477
    }
478
479
    /**
480
     * 获取最近一次查询的sql语句
481
     * @access public
482
     * @return string
483
     */
484
    public function getLastSql(): string
485
    {
486
        return $this->connection->getLastSql();
487
    }
488
489
    /**
490
     * 执行数据库事务
491
     * @access public
492
     * @param  callable $callback 数据操作方法回调
493
     * @return mixed
494
     */
495
    public function transaction(callable $callback)
496
    {
497
        return $this->connection->transaction($callback);
498
    }
499
500
    /**
501
     * 启动事务
502
     * @access public
503
     * @return void
504
     */
505
    public function startTrans(): void
506
    {
507
        $this->connection->startTrans();
508
    }
509
510
    /**
511
     * 用于非自动提交状态下面的查询提交
512
     * @access public
513
     * @return void
514
     * @throws PDOException
515
     */
516
    public function commit(): void
517
    {
518
        $this->connection->commit();
519
    }
520
521
    /**
522
     * 事务回滚
523
     * @access public
524
     * @return void
525
     * @throws PDOException
526
     */
527
    public function rollback(): void
528
    {
529
        $this->connection->rollback();
530
    }
531
532
    /**
533
     * 批处理执行SQL语句
534
     * 批处理的指令都认为是execute操作
535
     * @access public
536
     * @param  array $sql SQL批处理指令
537
     * @return bool
538
     */
539
    public function batchQuery(array $sql = []): bool
540
    {
541
        return $this->connection->batchQuery($this, $sql);
542
    }
543
544
    /**
545
     * 得到某个字段的值
546
     * @access public
547
     * @param  string $field   字段名
548
     * @param  mixed  $default 默认值
549
     * @return mixed
550
     */
551
    public function value(string $field, $default = null)
552
    {
553
        return $this->connection->value($this, $field, $default);
554
    }
555
556
    /**
557
     * 得到某个列的数组
558
     * @access public
559
     * @param  string $field 字段名 多个字段用逗号分隔
560
     * @param  string $key   索引
561
     * @return array
562
     */
563
    public function column(string $field, string $key = ''): array
564
    {
565
        return $this->connection->column($this, $field, $key);
566
    }
567
568
    /**
569
     * 聚合查询
570
     * @access protected
571
     * @param  string     $aggregate    聚合方法
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
572
     * @param  string|Raw $field        字段名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 8 found
Loading history...
573
     * @param  bool       $force        强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 8 found
Loading history...
574
     * @return mixed
575
     */
576
    protected function aggregate(string $aggregate, $field, bool $force = false)
577
    {
578
        return $this->connection->aggregate($this, $aggregate, $field, $force);
579
    }
580
581
    /**
582
     * COUNT查询
583
     * @access public
584
     * @param  string|Raw $field 字段名
585
     * @return int
586
     */
587
    public function count(string $field = '*'): int
588
    {
589
        if (!empty($this->options['group'])) {
590
            // 支持GROUP
591
            $options = $this->getOptions();
592
            $subSql  = $this->options($options)->field('count(' . $field . ') AS think_count')->bind($this->bind)->buildSql();
593
594
            $query = $this->newQuery()->table([$subSql => '_group_count_']);
595
596
            $count = $query->aggregate('COUNT', '*');
597
        } else {
598
            $count = $this->aggregate('COUNT', $field);
599
        }
600
601
        return (int) $count;
602
    }
603
604
    /**
605
     * SUM查询
606
     * @access public
607
     * @param  string|Raw $field 字段名
608
     * @return float
609
     */
610
    public function sum($field): float
611
    {
612
        return $this->aggregate('SUM', $field, true);
613
    }
614
615
    /**
616
     * MIN查询
617
     * @access public
618
     * @param  string|Raw $field    字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
619
     * @param  bool       $force    强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
620
     * @return mixed
621
     */
622
    public function min($field, bool $force = true)
623
    {
624
        return $this->aggregate('MIN', $field, $force);
625
    }
626
627
    /**
628
     * MAX查询
629
     * @access public
630
     * @param  string|Raw $field    字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
631
     * @param  bool       $force    强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
632
     * @return mixed
633
     */
634
    public function max($field, bool $force = true)
635
    {
636
        return $this->aggregate('MAX', $field, $force);
637
    }
638
639
    /**
640
     * AVG查询
641
     * @access public
642
     * @param  string|Raw $field 字段名
643
     * @return float
644
     */
645
    public function avg($field): float
646
    {
647
        return $this->aggregate('AVG', $field, true);
648
    }
649
650
    /**
651
     * 查询SQL组装 join
652
     * @access public
653
     * @param  mixed  $join      关联的表名
654
     * @param  mixed  $condition 条件
655
     * @param  string $type      JOIN类型
656
     * @param  array  $bind      参数绑定
657
     * @return $this
658
     */
659
    public function join($join, string $condition = null, string $type = 'INNER', array $bind = [])
660
    {
661
        $table = $this->getJoinTable($join);
662
663
        if (!empty($bind) && $condition) {
664
            $this->bindParams($condition, $bind);
665
        }
666
667
        $this->options['join'][] = [$table, strtoupper($type), $condition];
668
669
        return $this;
670
    }
671
672
    /**
673
     * LEFT JOIN
674
     * @access public
675
     * @param  mixed  $join      关联的表名
676
     * @param  mixed  $condition 条件
677
     * @param  array  $bind      参数绑定
678
     * @return $this
679
     */
680
    public function leftJoin($join, string $condition = null, array $bind = [])
681
    {
682
        return $this->join($join, $condition, 'LEFT', $bind);
683
    }
684
685
    /**
686
     * RIGHT JOIN
687
     * @access public
688
     * @param  mixed  $join      关联的表名
689
     * @param  mixed  $condition 条件
690
     * @param  array  $bind      参数绑定
691
     * @return $this
692
     */
693
    public function rightJoin($join, string $condition = null, array $bind = [])
694
    {
695
        return $this->join($join, $condition, 'RIGHT', $bind);
696
    }
697
698
    /**
699
     * FULL JOIN
700
     * @access public
701
     * @param  mixed  $join      关联的表名
702
     * @param  mixed  $condition 条件
703
     * @param  array  $bind      参数绑定
704
     * @return $this
705
     */
706
    public function fullJoin($join, string $condition = null, array $bind = [])
707
    {
708
        return $this->join($join, $condition, 'FULL');
709
    }
710
711
    /**
712
     * 获取Join表名及别名 支持
713
     * ['prefix_table或者子查询'=>'alias'] 'table alias'
714
     * @access protected
715
     * @param  array|string|Raw $join  JION表名
716
     * @param  string           $alias 别名
717
     * @return string|array
718
     */
719
    protected function getJoinTable($join, &$alias = null)
720
    {
721
        if (is_array($join)) {
722
            $table = $join;
723
            $alias = array_shift($join);
724
            return $table;
725
        } elseif ($join instanceof Raw) {
726
            return $join;
727
        }
728
729
        $join = trim($join);
730
731
        if (false !== strpos($join, '(')) {
732
            // 使用子查询
733
            $table = $join;
734
        } else {
735
            // 使用别名
736
            if (strpos($join, ' ')) {
737
                // 使用别名
738
                list($table, $alias) = explode(' ', $join);
739
            } else {
740
                $table = $join;
741
                if (false === strpos($join, '.')) {
742
                    $alias = $join;
743
                }
744
            }
745
746
            if ($this->prefix && false === strpos($table, '.') && 0 !== strpos($table, $this->prefix)) {
747
                $table = $this->getTable($table);
748
            }
749
        }
750
751
        if (!empty($alias) && $table != $alias) {
752
            $table = [$table => $alias];
753
        }
754
755
        return $table;
756
    }
757
758
    /**
759
     * 查询SQL组装 union
760
     * @access public
761
     * @param  mixed   $union UNION
762
     * @param  boolean $all   是否适用UNION ALL
763
     * @return $this
764
     */
765
    public function union($union, bool $all = false)
766
    {
767
        $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION';
768
769
        if (is_array($union)) {
770
            $this->options['union'] = array_merge($this->options['union'], $union);
771
        } else {
772
            $this->options['union'][] = $union;
773
        }
774
775
        return $this;
776
    }
777
778
    /**
779
     * 查询SQL组装 union all
780
     * @access public
781
     * @param  mixed   $union UNION数据
782
     * @return $this
783
     */
784
    public function unionAll($union)
785
    {
786
        return $this->union($union, true);
787
    }
788
789
    /**
790
     * 指定查询字段 支持字段排除和指定数据表
791
     * @access public
792
     * @param  mixed   $field     字段信息
793
     * @param  boolean $except    是否排除
794
     * @param  string  $tableName 数据表名
795
     * @param  string  $prefix    字段前缀
796
     * @param  string  $alias     别名前缀
797
     * @return $this
798
     */
799
    public function field($field, bool $except = false, string $tableName = '', string $prefix = '', string $alias = '')
800
    {
801
        if (empty($field)) {
802
            return $this;
803
        } elseif ($field instanceof Raw) {
804
            $this->options['field'][] = $field;
805
            return $this;
806
        }
807
808
        if (is_string($field)) {
809
            if (preg_match('/[\<\'\"\(]/', $field)) {
810
                return $this->fieldRaw($field);
811
            }
812
813
            $field = array_map('trim', explode(',', $field));
814
        }
815
816
        if (true === $field) {
817
            // 获取全部字段
818
            $fields = $this->getTableFields($tableName);
819
            $field  = $fields ?: ['*'];
0 ignored issues
show
introduced by
$fields is an empty array, thus is always false.
Loading history...
820
        } elseif ($except) {
821
            // 字段排除
822
            $fields = $this->getTableFields($tableName);
823
            $field  = $fields ? array_diff($fields, $field) : $field;
0 ignored issues
show
introduced by
$fields is an empty array, thus is always false.
Loading history...
824
        }
825
826
        if ($tableName) {
827
            // 添加统一的前缀
828
            $prefix = $prefix ?: $tableName;
829
            foreach ($field as $key => &$val) {
830
                if (is_numeric($key) && $alias) {
831
                    $field[$prefix . '.' . $val] = $alias . $val;
832
                    unset($field[$key]);
833
                } elseif (is_numeric($key)) {
834
                    $val = $prefix . '.' . $val;
835
                }
836
            }
837
        }
838
839
        if (isset($this->options['field'])) {
840
            $field = array_merge((array) $this->options['field'], $field);
841
        }
842
843
        $this->options['field'] = array_unique($field);
844
845
        return $this;
846
    }
847
848
    /**
849
     * 表达式方式指定查询字段
850
     * @access public
851
     * @param  string $field 字段名
852
     * @return $this
853
     */
854
    public function fieldRaw(string $field)
855
    {
856
        $this->options['field'][] = new Raw($field);
857
858
        return $this;
859
    }
860
861
    /**
862
     * 设置数据
863
     * @access public
864
     * @param  array $data 数据
865
     * @return $this
866
     */
867
    public function data(array $data)
868
    {
869
        $this->options['data'] = $data;
870
871
        return $this;
872
    }
873
874
    /**
875
     * 字段值增长
876
     * @access public
877
     * @param  string  $field 字段名
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
878
     * @param  integer $step  增长值
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 2 found
Loading history...
879
     * @param  integer $lazyTime 延时时间(s)
880
     * @param  string  $op INC/DEC
0 ignored issues
show
Coding Style introduced by
Expected 7 spaces after parameter name; 1 found
Loading history...
881
     * @return $this
882
     */
883
    public function inc(string $field, int $step = 1, int $lazyTime = 0, string $op = 'INC')
884
    {
885
        if ($lazyTime > 0) {
886
            // 延迟写入
887
            $condition = $this->options['where'] ?? [];
888
889
            $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition));
890
            $step = $this->connection->lazyWrite($op, $guid, $step, $lazyTime);
891
892
            if (false === $step) {
893
                return $this;
894
            }
895
896
            $op = 'INC';
897
        }
898
899
        $this->options['data'][$field] = [$op, $step];
900
901
        return $this;
902
    }
903
904
    /**
905
     * 字段值减少
906
     * @access public
907
     * @param  string  $field 字段名
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
908
     * @param  integer $step  增长值
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 2 found
Loading history...
909
     * @param  integer $lazyTime 延时时间(s)
910
     * @return $this
911
     */
912
    public function dec(string $field, int $step = 1, int $lazyTime = 0)
913
    {
914
        return $this->inc($field, $step, $lazyTime, 'DEC');
915
    }
916
917
    /**
918
     * 使用表达式设置数据
919
     * @access public
920
     * @param  string $field 字段名
921
     * @param  string $value 字段值
922
     * @return $this
923
     */
924
    public function exp(string $field, string $value)
925
    {
926
        $this->options['data'][$field] = new Raw($value);
927
        return $this;
928
    }
929
930
    /**
931
     * 指定JOIN查询字段
932
     * @access public
933
     * @param  string|array $join  数据表
934
     * @param  string|array $field 查询字段
935
     * @param  string       $on    JOIN条件
936
     * @param  string       $type  JOIN类型
937
     * @param  array        $bind  参数绑定
938
     * @return $this
939
     */
940
    public function view($join, $field = true, $on = null, string $type = 'INNER', array $bind = [])
941
    {
942
        $this->options['view'] = true;
943
944
        $fields = [];
945
        $table  = $this->getJoinTable($join, $alias);
946
947
        if (true === $field) {
948
            $fields = $alias . '.*';
949
        } else {
950
            if (is_string($field)) {
951
                $field = explode(',', $field);
952
            }
953
954
            foreach ($field as $key => $val) {
955
                if (is_numeric($key)) {
956
                    $fields[] = $alias . '.' . $val;
957
958
                    $this->options['map'][$val] = $alias . '.' . $val;
959
                } else {
960
                    if (preg_match('/[,=\.\'\"\(\s]/', $key)) {
961
                        $name = $key;
962
                    } else {
963
                        $name = $alias . '.' . $key;
964
                    }
965
966
                    $fields[] = $name . ' AS ' . $val;
967
968
                    $this->options['map'][$val] = $name;
969
                }
970
            }
971
        }
972
973
        $this->field($fields);
974
975
        if ($on) {
976
            $this->join($table, $on, $type, $bind);
977
        } else {
978
            $this->table($table);
979
        }
980
981
        return $this;
982
    }
983
984
    /**
985
     * 指定AND查询条件
986
     * @access public
987
     * @param  mixed $field     查询字段
988
     * @param  mixed $op        查询表达式
989
     * @param  mixed $condition 查询条件
990
     * @return $this
991
     */
992
    public function where($field, $op = null, $condition = null)
993
    {
994
        if ($field instanceof $this) {
995
            $this->options['where'] = $field->getOptions('where');
996
            return $this;
997
        }
998
999
        $param = func_get_args();
1000
        array_shift($param);
1001
        return $this->parseWhereExp('AND', $field, $op, $condition, $param);
1002
    }
1003
1004
    /**
1005
     * 指定OR查询条件
1006
     * @access public
1007
     * @param  mixed $field     查询字段
1008
     * @param  mixed $op        查询表达式
1009
     * @param  mixed $condition 查询条件
1010
     * @return $this
1011
     */
1012
    public function whereOr($field, $op = null, $condition = null)
1013
    {
1014
        $param = func_get_args();
1015
        array_shift($param);
1016
        return $this->parseWhereExp('OR', $field, $op, $condition, $param);
1017
    }
1018
1019
    /**
1020
     * 指定XOR查询条件
1021
     * @access public
1022
     * @param  mixed $field     查询字段
1023
     * @param  mixed $op        查询表达式
1024
     * @param  mixed $condition 查询条件
1025
     * @return $this
1026
     */
1027
    public function whereXor($field, $op = null, $condition = null)
1028
    {
1029
        $param = func_get_args();
1030
        array_shift($param);
1031
        return $this->parseWhereExp('XOR', $field, $op, $condition, $param);
1032
    }
1033
1034
    /**
1035
     * 指定Null查询条件
1036
     * @access public
1037
     * @param  mixed  $field 查询字段
1038
     * @param  string $logic 查询逻辑 and or xor
1039
     * @return $this
1040
     */
1041
    public function whereNull(string $field, string $logic = 'AND')
1042
    {
1043
        return $this->parseWhereExp($logic, $field, 'NULL', null, [], true);
1044
    }
1045
1046
    /**
1047
     * 指定NotNull查询条件
1048
     * @access public
1049
     * @param  mixed  $field 查询字段
1050
     * @param  string $logic 查询逻辑 and or xor
1051
     * @return $this
1052
     */
1053
    public function whereNotNull(string $field, string $logic = 'AND')
1054
    {
1055
        return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true);
1056
    }
1057
1058
    /**
1059
     * 指定Exists查询条件
1060
     * @access public
1061
     * @param  mixed  $condition 查询条件
1062
     * @param  string $logic     查询逻辑 and or xor
1063
     * @return $this
1064
     */
1065
    public function whereExists($condition, string $logic = 'AND')
1066
    {
1067
        if (is_string($condition)) {
1068
            $condition = new Raw($condition);
1069
        }
1070
1071
        $this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition];
1072
        return $this;
1073
    }
1074
1075
    /**
1076
     * 指定NotExists查询条件
1077
     * @access public
1078
     * @param  mixed  $condition 查询条件
1079
     * @param  string $logic     查询逻辑 and or xor
1080
     * @return $this
1081
     */
1082
    public function whereNotExists($condition, string $logic = 'AND')
1083
    {
1084
        if (is_string($condition)) {
1085
            $condition = new Raw($condition);
1086
        }
1087
1088
        $this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition];
1089
        return $this;
1090
    }
1091
1092
    /**
1093
     * 指定In查询条件
1094
     * @access public
1095
     * @param  mixed  $field     查询字段
1096
     * @param  mixed  $condition 查询条件
1097
     * @param  string $logic     查询逻辑 and or xor
1098
     * @return $this
1099
     */
1100
    public function whereIn(string $field, $condition, string $logic = 'AND')
1101
    {
1102
        return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true);
1103
    }
1104
1105
    /**
1106
     * 指定NotIn查询条件
1107
     * @access public
1108
     * @param  mixed  $field     查询字段
1109
     * @param  mixed  $condition 查询条件
1110
     * @param  string $logic     查询逻辑 and or xor
1111
     * @return $this
1112
     */
1113
    public function whereNotIn(string $field, $condition, string $logic = 'AND')
1114
    {
1115
        return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true);
1116
    }
1117
1118
    /**
1119
     * 指定Like查询条件
1120
     * @access public
1121
     * @param  mixed  $field     查询字段
1122
     * @param  mixed  $condition 查询条件
1123
     * @param  string $logic     查询逻辑 and or xor
1124
     * @return $this
1125
     */
1126
    public function whereLike(string $field, $condition, string $logic = 'AND')
1127
    {
1128
        return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true);
1129
    }
1130
1131
    /**
1132
     * 指定NotLike查询条件
1133
     * @access public
1134
     * @param  mixed  $field     查询字段
1135
     * @param  mixed  $condition 查询条件
1136
     * @param  string $logic     查询逻辑 and or xor
1137
     * @return $this
1138
     */
1139
    public function whereNotLike(string $field, $condition, string $logic = 'AND')
1140
    {
1141
        return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true);
1142
    }
1143
1144
    /**
1145
     * 指定Between查询条件
1146
     * @access public
1147
     * @param  mixed  $field     查询字段
1148
     * @param  mixed  $condition 查询条件
1149
     * @param  string $logic     查询逻辑 and or xor
1150
     * @return $this
1151
     */
1152
    public function whereBetween(string $field, $condition, string $logic = 'AND')
1153
    {
1154
        return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true);
1155
    }
1156
1157
    /**
1158
     * 指定NotBetween查询条件
1159
     * @access public
1160
     * @param  mixed  $field     查询字段
1161
     * @param  mixed  $condition 查询条件
1162
     * @param  string $logic     查询逻辑 and or xor
1163
     * @return $this
1164
     */
1165
    public function whereNotBetween(string $field, $condition, string $logic = 'AND')
1166
    {
1167
        return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true);
1168
    }
1169
1170
    /**
1171
     * 指定FIND_IN_SET查询条件
1172
     * @access public
1173
     * @param  mixed  $field     查询字段
1174
     * @param  mixed  $condition 查询条件
1175
     * @param  string $logic     查询逻辑 and or xor
1176
     * @return $this
1177
     */
1178
    public function whereFindInSet(string $field, $condition, string $logic = 'AND')
1179
    {
1180
        return $this->parseWhereExp($logic, $field, 'FIND IN SET', $condition, [], true);
1181
    }
1182
1183
    /**
1184
     * 比较两个字段
1185
     * @access public
1186
     * @param  string $field1     查询字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 5 found
Loading history...
1187
     * @param  string $operator   比较操作符
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
1188
     * @param  string $field2     比较字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 5 found
Loading history...
1189
     * @param  string $logic      查询逻辑 and or xor
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 6 found
Loading history...
1190
     * @return $this
1191
     */
1192
    public function whereColumn(string $field1, string $operator, string $field2 = null, string $logic = 'AND')
1193
    {
1194
        if (is_null($field2)) {
1195
            $field2   = $operator;
1196
            $operator = '=';
1197
        }
1198
1199
        return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true);
1200
    }
1201
1202
    /**
1203
     * 设置软删除字段及条件
1204
     * @access public
1205
     * @param  string       $field     查询字段
1206
     * @param  mixed        $condition 查询条件
1207
     * @return $this
1208
     */
1209
    public function useSoftDelete(string $field, $condition = null)
1210
    {
1211
        if ($field) {
1212
            $this->options['soft_delete'] = [$field, $condition];
1213
        }
1214
1215
        return $this;
1216
    }
1217
1218
    /**
1219
     * 指定Exp查询条件
1220
     * @access public
1221
     * @param  mixed  $field 查询字段
1222
     * @param  string $where 查询条件
1223
     * @param  array  $bind  参数绑定
1224
     * @param  string $logic 查询逻辑 and or xor
1225
     * @return $this
1226
     */
1227
    public function whereExp(string $field, string $where, array $bind = [], string $logic = 'AND')
1228
    {
1229
        if (!empty($bind)) {
1230
            $this->bindParams($where, $bind);
1231
        }
1232
1233
        $this->options['where'][$logic][] = [$field, 'EXP', new Raw($where)];
1234
1235
        return $this;
1236
    }
1237
1238
    /**
1239
     * 指定字段Raw查询
1240
     * @access public
1241
     * @param  string $field     查询字段表达式
1242
     * @param  mixed  $op        查询表达式
1243
     * @param  string $condition 查询条件
1244
     * @param  string $logic     查询逻辑 and or xor
1245
     * @return $this
1246
     */
1247
    public function whereFieldRaw(string $field, $op, $condition = null, string $logic = 'AND')
1248
    {
1249
        if (is_null($condition)) {
1250
            $condition = $op;
1251
            $op        = '=';
1252
        }
1253
1254
        $this->options['where'][$logic][] = [new Raw($field), $op, $condition];
1255
        return $this;
1256
    }
1257
1258
    /**
1259
     * 指定表达式查询条件
1260
     * @access public
1261
     * @param  string $where 查询条件
1262
     * @param  array  $bind  参数绑定
1263
     * @param  string $logic 查询逻辑 and or xor
1264
     * @return $this
1265
     */
1266
    public function whereRaw(string $where, array $bind = [], string $logic = 'AND')
1267
    {
1268
        if (!empty($bind)) {
1269
            $this->bindParams($where, $bind);
1270
        }
1271
1272
        $this->options['where'][$logic][] = new Raw($where);
1273
1274
        return $this;
1275
    }
1276
1277
    /**
1278
     * 指定表达式查询条件 OR
1279
     * @access public
1280
     * @param  string $where 查询条件
1281
     * @param  array  $bind  参数绑定
1282
     * @return $this
1283
     */
1284
    public function whereOrRaw(string $where, array $bind = [])
1285
    {
1286
        return $this->whereRaw($where, $bind, 'OR');
1287
    }
1288
1289
    /**
1290
     * 分析查询表达式
1291
     * @access protected
1292
     * @param  string $logic     查询逻辑 and or xor
1293
     * @param  mixed  $field     查询字段
1294
     * @param  mixed  $op        查询表达式
1295
     * @param  mixed  $condition 查询条件
1296
     * @param  array  $param     查询参数
1297
     * @param  bool   $strict    严格模式
1298
     * @return $this
1299
     */
1300
    protected function parseWhereExp(string $logic, $field, $op, $condition, array $param = [], bool $strict = false)
1301
    {
1302
        $logic = strtoupper($logic);
1303
1304
        if (is_string($field) && !empty($this->options['via']) && false === strpos($field, '.')) {
1305
            $field = $this->options['via'] . '.' . $field;
1306
        }
1307
1308
        if ($field instanceof Raw) {
1309
            return $this->whereRaw($field, is_array($op) ? $op : [], $logic);
1310
        } elseif ($strict) {
1311
            // 使用严格模式查询
1312
            if ('=' == $op) {
1313
                $where = $this->whereEq($field, $condition);
1314
            } else {
1315
                $where = [$field, $op, $condition, $logic];
1316
            }
1317
        } elseif (is_array($field)) {
1318
            // 解析数组批量查询
1319
            return $this->parseArrayWhereItems($field, $logic);
1320
        } elseif ($field instanceof Closure) {
1321
            $where = $field;
1322
        } elseif (is_string($field)) {
1323
            if (preg_match('/[,=\<\'\"\(\s]/', $field)) {
1324
                return $this->whereRaw($field, is_array($op) ? $op : [], $logic);
1325
            } elseif (is_string($op) && strtolower($op) == 'exp') {
1326
                $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : [];
1327
                return $this->whereExp($field, $condition, $bind, $logic);
1328
            }
1329
1330
            $where = $this->parseWhereItem($logic, $field, $op, $condition, $param);
1331
        }
1332
1333
        if (!empty($where)) {
1334
            $this->options['where'][$logic][] = $where;
1335
        }
1336
1337
        return $this;
1338
    }
1339
1340
    /**
1341
     * 分析查询表达式
1342
     * @access protected
1343
     * @param  string   $logic     查询逻辑 and or xor
1344
     * @param  mixed    $field     查询字段
1345
     * @param  mixed    $op        查询表达式
1346
     * @param  mixed    $condition 查询条件
1347
     * @param  array    $param     查询参数
1348
     * @return array
1349
     */
1350
    protected function parseWhereItem(string $logic, $field, $op, $condition, array $param = []): array
1351
    {
1352
        if (is_array($op)) {
1353
            // 同一字段多条件查询
1354
            array_unshift($param, $field);
1355
            $where = $param;
1356
        } elseif ($field && is_null($condition)) {
1357
            if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) {
1358
                // null查询
1359
                $where = [$field, $op, ''];
1360
            } elseif ('=' === $op || is_null($op)) {
1361
                $where = [$field, 'NULL', ''];
1362
            } elseif ('<>' === $op) {
1363
                $where = [$field, 'NOTNULL', ''];
1364
            } else {
1365
                // 字段相等查询
1366
                $where = $this->whereEq($field, $op);
1367
            }
1368
        } elseif (in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) {
1369
            $where = [$field, $op, is_string($condition) ? new Raw($condition) : $condition];
1370
        } else {
1371
            $where = $field ? [$field, $op, $condition, $param[2] ?? null] : [];
1372
        }
1373
1374
        return $where;
1375
    }
1376
1377
    /**
1378
     * 相等查询的主键处理
1379
     * @access protected
1380
     * @param  string $field  字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1381
     * @param  mixed  $value  字段值
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1382
     * @return array
1383
     */
1384
    protected function whereEq(string $field, $value): array
1385
    {
1386
        $where = [$field, '=', $value];
1387
        if ($this->getPk() == $field) {
1388
            $this->options['key'] = $value;
1389
        }
1390
1391
        return $where;
1392
    }
1393
1394
    /**
1395
     * 数组批量查询
1396
     * @access protected
1397
     * @param  array  $field 批量查询
1398
     * @param  string $logic 查询逻辑 and or xor
1399
     * @return $this
1400
     */
1401
    protected function parseArrayWhereItems(array $field, string $logic)
1402
    {
1403
        if (key($field) !== 0) {
1404
            $where = [];
1405
            foreach ($field as $key => $val) {
1406
                if ($val instanceof Raw) {
1407
                    $where[] = [$key, 'exp', $val];
1408
                } else {
1409
                    $where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, is_array($val) ? 'IN' : '=', $val];
1410
                }
1411
            }
1412
        } else {
1413
            // 数组批量查询
1414
            $where = $field;
1415
        }
1416
1417
        if (!empty($where)) {
1418
            $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? array_merge($this->options['where'][$logic], $where) : $where;
1419
        }
1420
1421
        return $this;
1422
    }
1423
1424
    /**
1425
     * 去除某个查询条件
1426
     * @access public
1427
     * @param  string $field 查询字段
1428
     * @param  string $logic 查询逻辑 and or xor
1429
     * @return $this
1430
     */
1431
    public function removeWhereField(string $field, string $logic = 'AND')
1432
    {
1433
        $logic = strtoupper($logic);
1434
1435
        if (isset($this->options['where'][$logic])) {
1436
            foreach ($this->options['where'][$logic] as $key => $val) {
1437
                if (is_array($val) && $val[0] == $field) {
1438
                    unset($this->options['where'][$logic][$key]);
1439
                }
1440
            }
1441
        }
1442
1443
        return $this;
1444
    }
1445
1446
    /**
1447
     * 去除查询参数
1448
     * @access public
1449
     * @param  string $option 参数名 留空去除所有参数
1450
     * @return $this
1451
     */
1452
    public function removeOption(string $option = '')
1453
    {
1454
        if ('' === $option) {
1455
            $this->options = [];
1456
            $this->bind    = [];
1457
        } elseif (isset($this->options[$option])) {
1458
            unset($this->options[$option]);
1459
        }
1460
1461
        return $this;
1462
    }
1463
1464
    /**
1465
     * 条件查询
1466
     * @access public
1467
     * @param  mixed         $condition  满足条件(支持闭包)
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1468
     * @param  Closure|array $query      满足条件后执行的查询表达式(闭包或数组)
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 6 found
Loading history...
1469
     * @param  Closure|array $otherwise  不满足条件后执行
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1470
     * @return $this
1471
     */
1472
    public function when($condition, $query, $otherwise = null)
1473
    {
1474
        if ($condition instanceof Closure) {
1475
            $condition = $condition($this);
1476
        }
1477
1478
        if ($condition) {
1479
            if ($query instanceof Closure) {
1480
                $query($this, $condition);
1481
            } elseif (is_array($query)) {
0 ignored issues
show
introduced by
The condition is_array($query) is always true.
Loading history...
1482
                $this->where($query);
1483
            }
1484
        } elseif ($otherwise) {
1485
            if ($otherwise instanceof Closure) {
1486
                $otherwise($this, $condition);
1487
            } elseif (is_array($otherwise)) {
0 ignored issues
show
introduced by
The condition is_array($otherwise) is always true.
Loading history...
1488
                $this->where($otherwise);
1489
            }
1490
        }
1491
1492
        return $this;
1493
    }
1494
1495
    /**
1496
     * 指定查询数量
1497
     * @access public
1498
     * @param  int $offset 起始位置
1499
     * @param  int $length 查询数量
1500
     * @return $this
1501
     */
1502
    public function limit(int $offset, int $length = null)
1503
    {
1504
        $this->options['limit'] = $offset . ($length ? ',' . $length : '');
1505
1506
        return $this;
1507
    }
1508
1509
    /**
1510
     * 指定分页
1511
     * @access public
1512
     * @param  int $page     页数
1513
     * @param  int $listRows 每页数量
1514
     * @return $this
1515
     */
1516
    public function page(int $page, int $listRows = null)
1517
    {
1518
        $this->options['page'] = [$page, $listRows];
1519
1520
        return $this;
1521
    }
1522
1523
    /**
1524
     * 分页查询
1525
     * @access public
1526
     * @param  int|array $listRows 每页数量 数组表示配置参数
1527
     * @param  int|bool  $simple   是否简洁模式或者总记录数
1528
     * @param  array     $config   配置参数
1529
     * @return \think\Paginator
1530
     * @throws DbException
1531
     */
1532
    public function paginate($listRows = null, $simple = false, $config = [])
1533
    {
1534
        if (is_int($simple)) {
1535
            $total  = $simple;
1536
            $simple = false;
1537
        }
1538
1539
        $paginate = array_merge($this->paginateConfig, $this->config->get('paginate'));
1540
1541
        if (is_array($listRows)) {
1542
            $config   = array_merge($paginate, $listRows);
1543
            $listRows = intval($config['list_rows']);
1544
        } else {
1545
            $config   = array_merge($paginate, $config);
1546
            $listRows = intval($listRows ?: $config['list_rows']);
1547
        }
1548
1549
        $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']);
1550
        $page  = isset($config['page']) ? (int) $config['page'] : call_user_func([
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...
1551
            $class,
1552
            'getCurrentPage',
1553
        ], $config['var_page']);
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...
1554
1555
        $page = $page < 1 ? 1 : $page;
1556
1557
        $config['path'] = $config['path'] ?? call_user_func([$class, 'getCurrentPath']);
1558
1559
        if (!isset($total) && !$simple) {
1560
            $options = $this->getOptions();
1561
1562
            unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']);
1563
1564
            $bind    = $this->bind;
1565
            $total   = $this->count();
1566
            $results = $this->options($options)->bind($bind)->page($page, $listRows)->select();
1567
        } elseif ($simple) {
1568
            $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
1569
            $total   = null;
1570
        } else {
1571
            $results = $this->page($page, $listRows)->select();
1572
        }
1573
1574
        $this->removeOption('limit');
1575
        $this->removeOption('page');
1576
1577
        return $class::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...
1578
    }
1579
1580
    /**
1581
     * 表达式方式指定当前操作的数据表
1582
     * @access public
1583
     * @param  mixed $table 表名
1584
     * @return $this
1585
     */
1586
    public function tableRaw(string $table)
1587
    {
1588
        $this->options['table'] = new Raw($table);
1589
1590
        return $this;
1591
    }
1592
1593
    /**
1594
     * 指定当前操作的数据表
1595
     * @access public
1596
     * @param  mixed $table 表名
1597
     * @return $this
1598
     */
1599
    public function table($table)
1600
    {
1601
        if (is_string($table)) {
1602
            if (strpos($table, ')')) {
1603
                // 子查询
1604
            } else {
1605
                $tables = explode(',', $table);
1606
                $table  = [];
1607
1608
                foreach ($tables as $item) {
1609
                    $item = trim($item);
1610
                    if (strpos($item, ' ')) {
1611
                        list($item, $alias) = explode(' ', $item);
1612
                        $this->alias([$item => $alias]);
1613
                        $table[$item] = $alias;
1614
                    } else {
1615
                        $table[] = $item;
1616
                    }
1617
                }
1618
            }
1619
        } elseif (is_array($table)) {
1620
            $tables = $table;
1621
            $table  = [];
1622
1623
            foreach ($tables as $key => $val) {
1624
                if (is_numeric($key)) {
1625
                    $table[] = $val;
1626
                } else {
1627
                    $this->alias([$key => $val]);
1628
                    $table[$key] = $val;
1629
                }
1630
            }
1631
        }
1632
1633
        $this->options['table'] = $table;
1634
1635
        return $this;
1636
    }
1637
1638
    /**
1639
     * USING支持 用于多表删除
1640
     * @access public
1641
     * @param  mixed $using USING
1642
     * @return $this
1643
     */
1644
    public function using($using)
1645
    {
1646
        $this->options['using'] = $using;
1647
        return $this;
1648
    }
1649
1650
    /**
1651
     * 存储过程调用
1652
     * @access public
1653
     * @param  bool $procedure 是否为存储过程查询
1654
     * @return $this
1655
     */
1656
    public function procedure($procedure = true)
1657
    {
1658
        $this->options['procedure'] = $procedure;
1659
        return $this;
1660
    }
1661
1662
    /**
1663
     * 是否允许返回空数据(或空模型)
1664
     * @access public
1665
     * @param  bool $allowEmpty 是否允许为空
1666
     * @return $this
1667
     */
1668
    public function allowEmpty(bool $allowEmpty = true)
1669
    {
1670
        $this->options['allow_empty'] = $allowEmpty;
1671
        return $this;
1672
    }
1673
1674
    /**
1675
     * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc'])
1676
     * @access public
1677
     * @param  string|array|Raw $field 排序字段
1678
     * @param  string           $order 排序
1679
     * @return $this
1680
     */
1681
    public function order($field, string $order = '')
1682
    {
1683
        if (empty($field)) {
1684
            return $this;
1685
        } elseif ($field instanceof Raw) {
1686
            $this->options['order'][] = $field;
1687
            return $this;
1688
        }
1689
1690
        if (is_string($field)) {
1691
            if (!empty($this->options['via'])) {
1692
                $field = $this->options['via'] . '.' . $field;
1693
            }
1694
            if (strpos($field, ',')) {
1695
                $field = array_map('trim', explode(',', $field));
1696
            } else {
1697
                $field = empty($order) ? $field : [$field => $order];
1698
            }
1699
        } elseif (!empty($this->options['via'])) {
1700
            foreach ($field as $key => $val) {
1701
                if (is_numeric($key)) {
1702
                    $field[$key] = $this->options['via'] . '.' . $val;
1703
                } else {
1704
                    $field[$this->options['via'] . '.' . $key] = $val;
1705
                    unset($field[$key]);
1706
                }
1707
            }
1708
        }
1709
1710
        if (!isset($this->options['order'])) {
1711
            $this->options['order'] = [];
1712
        }
1713
1714
        if (is_array($field)) {
1715
            $this->options['order'] = array_merge($this->options['order'], $field);
1716
        } else {
1717
            $this->options['order'][] = $field;
1718
        }
1719
1720
        return $this;
1721
    }
1722
1723
    /**
1724
     * 表达式方式指定Field排序
1725
     * @access public
1726
     * @param  string $field 排序字段
1727
     * @param  array  $bind  参数绑定
1728
     * @return $this
1729
     */
1730
    public function orderRaw(string $field, array $bind = [])
1731
    {
1732
        if (!empty($bind)) {
1733
            $this->bindParams($field, $bind);
1734
        }
1735
1736
        $this->options['order'][] = new Raw($field);
1737
1738
        return $this;
1739
    }
1740
1741
    /**
1742
     * 指定Field排序 orderField('id',[1,2,3],'desc')
1743
     * @access public
1744
     * @param  string $field 排序字段
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
1745
     * @param  array  $values 排序值
1746
     * @param  string $order 排序 desc/asc
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
1747
     * @return $this
1748
     */
1749
    public function orderField(string $field, array $values, string $order = '')
1750
    {
1751
        if (!empty($values)) {
1752
            $values['sort'] = $order;
1753
1754
            $this->options['order'][$field] = $values;
1755
        }
1756
1757
        return $this;
1758
    }
1759
1760
    /**
1761
     * 随机排序
1762
     * @access public
1763
     * @return $this
1764
     */
1765
    public function orderRand()
1766
    {
1767
        $this->options['order'][] = '[rand]';
1768
        return $this;
1769
    }
1770
1771
    /**
1772
     * 查询缓存
1773
     * @access public
1774
     * @param  mixed             $key    缓存key
1775
     * @param  integer|\DateTime $expire 缓存有效期
1776
     * @param  string            $tag    缓存标签
1777
     * @return $this
1778
     */
1779
    public function cache($key = true, $expire = null, $tag = null)
1780
    {
1781
        if (false === $key) {
1782
            return $this;
1783
        }
1784
1785
        if ($key instanceof \DateTimeInterface || (is_int($key) && is_null($expire))) {
1786
            $expire = $key;
1787
            $key    = true;
1788
        }
1789
1790
        $this->options['cache'] = [$key, $expire, $tag];
1791
1792
        return $this;
1793
    }
1794
1795
    /**
1796
     * 指定group查询
1797
     * @access public
1798
     * @param  string|array $group GROUP
1799
     * @return $this
1800
     */
1801
    public function group($group)
1802
    {
1803
        $this->options['group'] = $group;
1804
        return $this;
1805
    }
1806
1807
    /**
1808
     * 指定having查询
1809
     * @access public
1810
     * @param  string $having having
1811
     * @return $this
1812
     */
1813
    public function having(string $having)
1814
    {
1815
        $this->options['having'] = $having;
1816
        return $this;
1817
    }
1818
1819
    /**
1820
     * 指定查询lock
1821
     * @access public
1822
     * @param  bool|string $lock 是否lock
1823
     * @return $this
1824
     */
1825
    public function lock($lock = false)
1826
    {
1827
        $this->options['lock'] = $lock;
1828
1829
        if ($lock) {
1830
            $this->options['master'] = true;
1831
        }
1832
1833
        return $this;
1834
    }
1835
1836
    /**
1837
     * 指定distinct查询
1838
     * @access public
1839
     * @param  bool $distinct 是否唯一
1840
     * @return $this
1841
     */
1842
    public function distinct(bool $distinct = true)
1843
    {
1844
        $this->options['distinct'] = $distinct;
1845
        return $this;
1846
    }
1847
1848
    /**
1849
     * 指定数据表别名
1850
     * @access public
1851
     * @param  array|string $alias 数据表别名
1852
     * @return $this
1853
     */
1854
    public function alias($alias)
1855
    {
1856
        if (is_array($alias)) {
1857
            $this->options['alias'] = $alias;
1858
        } else {
1859
            $table = $this->getTable();
1860
1861
            $this->options['alias'][$table] = $alias;
1862
        }
1863
1864
        return $this;
1865
    }
1866
1867
    /**
1868
     * 指定强制索引
1869
     * @access public
1870
     * @param  string $force 索引名称
1871
     * @return $this
1872
     */
1873
    public function force(string $force)
1874
    {
1875
        $this->options['force'] = $force;
1876
        return $this;
1877
    }
1878
1879
    /**
1880
     * 查询注释
1881
     * @access public
1882
     * @param  string $comment 注释
1883
     * @return $this
1884
     */
1885
    public function comment(string $comment)
1886
    {
1887
        $this->options['comment'] = $comment;
1888
        return $this;
1889
    }
1890
1891
    /**
1892
     * 获取执行的SQL语句而不进行实际的查询
1893
     * @access public
1894
     * @param  bool $fetch 是否返回sql
1895
     * @return $this|Fetch
1896
     */
1897
    public function fetchSql(bool $fetch = true)
1898
    {
1899
        $this->options['fetch_sql'] = $fetch;
1900
1901
        if ($fetch) {
1902
            return new Fetch($this);
1903
        }
1904
1905
        return $this;
1906
    }
1907
1908
    /**
1909
     * 设置是否返回数据集对象
1910
     * @access public
1911
     * @param  bool|string $collection 是否返回数据集对象
1912
     * @return $this
1913
     */
1914
    public function fetchCollection($collection = true)
1915
    {
1916
        $this->options['collection'] = $collection;
1917
        return $this;
1918
    }
1919
1920
    /**
1921
     * 设置是否返回数组
1922
     * @access public
1923
     * @param  bool $asArray 是否返回数组
1924
     * @return $this
1925
     */
1926
    public function fetchArray(bool $asArray = true)
1927
    {
1928
        $this->options['array'] = $asArray;
1929
        return $this;
1930
    }
1931
1932
    /**
1933
     * 设置从主服务器读取数据
1934
     * @access public
1935
     * @param  bool $readMaster 是否从主服务器读取
1936
     * @return $this
1937
     */
1938
    public function master(bool $readMaster = true)
1939
    {
1940
        $this->options['master'] = $readMaster;
1941
        return $this;
1942
    }
1943
1944
    /**
1945
     * 设置后续从主库读取数据
1946
     * @access public
1947
     * @param  bool $all 是否所有表有效
1948
     * @return $this
1949
     */
1950
    public function readMaster(bool $all = false)
1951
    {
1952
        $table = $all ? '*' : $this->getTable();
1953
1954
        $this->db->readMaster($table);
1955
1956
        return $this;
1957
    }
1958
1959
    /**
1960
     * 设置是否严格检查字段名
1961
     * @access public
1962
     * @param  bool $strict 是否严格检查字段
1963
     * @return $this
1964
     */
1965
    public function strict(bool $strict = true)
1966
    {
1967
        $this->options['strict'] = $strict;
1968
        return $this;
1969
    }
1970
1971
    /**
1972
     * 设置查询数据不存在是否抛出异常
1973
     * @access public
1974
     * @param  bool $fail 数据不存在是否抛出异常
1975
     * @return $this
1976
     */
1977
    public function failException(bool $fail = true)
1978
    {
1979
        $this->options['fail'] = $fail;
1980
        return $this;
1981
    }
1982
1983
    /**
1984
     * 设置自增序列名
1985
     * @access public
1986
     * @param  string $sequence 自增序列名
1987
     * @return $this
1988
     */
1989
    public function sequence(string $sequence = null)
1990
    {
1991
        $this->options['sequence'] = $sequence;
1992
        return $this;
1993
    }
1994
1995
    /**
1996
     * 设置是否REPLACE
1997
     * @access public
1998
     * @param  bool $replace 是否使用REPLACE写入数据
1999
     * @return $this
2000
     */
2001
    public function replace(bool $replace = true)
2002
    {
2003
        $this->options['replace'] = $replace;
2004
        return $this;
2005
    }
2006
2007
    /**
2008
     * 设置当前查询所在的分区
2009
     * @access public
2010
     * @param  string|array $partition 分区名称
2011
     * @return $this
2012
     */
2013
    public function partition($partition)
2014
    {
2015
        $this->options['partition'] = $partition;
2016
        return $this;
2017
    }
2018
2019
    /**
2020
     * 设置DUPLICATE
2021
     * @access public
2022
     * @param  array|string|Raw $duplicate DUPLICATE信息
2023
     * @return $this
2024
     */
2025
    public function duplicate($duplicate)
2026
    {
2027
        $this->options['duplicate'] = $duplicate;
2028
        return $this;
2029
    }
2030
2031
    /**
2032
     * 设置查询的额外参数
2033
     * @access public
2034
     * @param  string $extra 额外信息
2035
     * @return $this
2036
     */
2037
    public function extra(string $extra)
2038
    {
2039
        $this->options['extra'] = $extra;
2040
        return $this;
2041
    }
2042
2043
    /**
2044
     * 设置需要隐藏的输出属性
2045
     * @access public
2046
     * @param  array $hidden 需要隐藏的字段名
2047
     * @return $this
2048
     */
2049
    public function hidden(array $hidden)
2050
    {
2051
        $this->options['hidden'] = $hidden;
2052
        return $this;
2053
    }
2054
2055
    /**
2056
     * 设置需要输出的属性
2057
     * @access public
2058
     * @param  array $visible 需要输出的属性
2059
     * @return $this
2060
     */
2061
    public function visible(array $visible)
2062
    {
2063
        $this->options['visible'] = $visible;
2064
        return $this;
2065
    }
2066
2067
    /**
2068
     * 设置需要追加输出的属性
2069
     * @access public
2070
     * @param  array $append 需要追加的属性
2071
     * @return $this
2072
     */
2073
    public function append(array $append)
2074
    {
2075
        $this->options['append'] = $append;
2076
        return $this;
2077
    }
2078
2079
    /**
2080
     * 设置JSON字段信息
2081
     * @access public
2082
     * @param  array $json JSON字段
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
2083
     * @param  bool  $assoc 是否取出数组
2084
     * @return $this
2085
     */
2086
    public function json(array $json = [], bool $assoc = false)
2087
    {
2088
        $this->options['json']       = $json;
2089
        $this->options['json_assoc'] = $assoc;
2090
        return $this;
2091
    }
2092
2093
    /**
0 ignored issues
show
Coding Style introduced by
Parameter ...$args should have a doc-comment as per coding-style.
Loading history...
2094
     * 添加查询范围
2095
     * @access public
2096
     * @param  array|string|Closure $scope 查询范围定义
2097
     * @param  array                $args  参数
0 ignored issues
show
Coding Style introduced by
Doc comment for parameter $args does not match actual variable name ...$args
Loading history...
2098
     * @return $this
2099
     */
2100
    public function scope($scope, ...$args)
2101
    {
2102
        // 查询范围的第一个参数始终是当前查询对象
2103
        array_unshift($args, $this);
2104
2105
        if ($scope instanceof Closure) {
2106
            call_user_func_array($scope, $args);
2107
            return $this;
2108
        }
2109
2110
        if (is_string($scope)) {
2111
            $scope = explode(',', $scope);
2112
        }
2113
2114
        if ($this->model) {
2115
            // 检查模型类的查询范围方法
2116
            foreach ($scope as $name) {
2117
                $method = 'scope' . trim($name);
2118
2119
                if (method_exists($this->model, $method)) {
2120
                    call_user_func_array([$this->model, $method], $args);
2121
                }
2122
            }
2123
        }
2124
2125
        return $this;
2126
    }
2127
2128
    /**
2129
     * 指定数据表主键
2130
     * @access public
2131
     * @param  string $pk 主键
2132
     * @return $this
2133
     */
2134
    public function pk(string $pk)
2135
    {
2136
        $this->pk = $pk;
2137
        return $this;
2138
    }
2139
2140
    /**
2141
     * 添加日期或者时间查询规则
2142
     * @access public
2143
     * @param  string       $name 时间表达式
2144
     * @param  string|array $rule 时间范围
2145
     * @return $this
2146
     */
2147
    public function timeRule(string $name, $rule)
2148
    {
2149
        $this->timeRule[$name] = $rule;
2150
        return $this;
2151
    }
2152
2153
    /**
2154
     * 查询日期或者时间
2155
     * @access public
2156
     * @param  string       $field 日期字段名
2157
     * @param  string       $op    比较运算符或者表达式
2158
     * @param  string|array $range 比较范围
2159
     * @param  string       $logic AND OR
2160
     * @return $this
2161
     */
2162
    public function whereTime(string $field, string $op, $range = null, string $logic = 'AND')
2163
    {
2164
        if (is_null($range) && isset($this->timeRule[$op])) {
2165
            $range = $this->timeRule[$op];
2166
            $op    = 'between';
2167
        }
2168
2169
        return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true);
2170
    }
2171
2172
    /**
2173
     * 查询某个时间间隔数据
2174
     * @access protected
2175
     * @param  string $field 日期字段名
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2176
     * @param  string $start 开始时间
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2177
     * @param  string $interval 时间间隔单位
2178
     * @param  string $logic AND OR
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2179
     * @return $this
2180
     */
2181
    protected function whereTimeInterval(string $field, string $start, string $interval = 'day', string $logic = 'AND')
2182
    {
2183
        $startTime = strtotime($start);
2184
        $endTime   = strtotime('+1 ' . $interval, $startTime);
2185
2186
        return $this->whereTime($field, 'between', [$startTime, $endTime], $logic);
2187
    }
2188
2189
    /**
2190
     * 查询月数据 whereMonth('time_field', '2018-1')
2191
     * @access public
2192
     * @param  string $field 日期字段名
2193
     * @param  string $month 月份信息
2194
     * @param  string $logic AND OR
2195
     * @return $this
2196
     */
2197
    public function whereMonth(string $field, string $month = 'this month', string $logic = 'AND')
2198
    {
2199
        if (in_array($month, ['this month', 'last month'])) {
2200
            $month = date('Y-m', strtotime($month));
2201
        }
2202
2203
        return $this->whereTimeInterval($field, $month, 'month', $logic);
2204
    }
2205
2206
    /**
2207
     * 查询年数据 whereYear('time_field', '2018')
2208
     * @access public
2209
     * @param  string $field 日期字段名
2210
     * @param  string $year  年份信息
2211
     * @param  string $logic AND OR
2212
     * @return $this
2213
     */
2214
    public function whereYear(string $field, string $year = 'this year', string $logic = 'AND')
2215
    {
2216
        if (in_array($year, ['this year', 'last year'])) {
2217
            $year = date('Y', strtotime($year));
2218
        }
2219
2220
        return $this->whereTimeInterval($field, $year . '-1-1', 'year', $logic);
2221
    }
2222
2223
    /**
2224
     * 查询日数据 whereDay('time_field', '2018-1-1')
2225
     * @access public
2226
     * @param  string $field 日期字段名
2227
     * @param  string $day   日期信息
2228
     * @param  string $logic AND OR
2229
     * @return $this
2230
     */
2231
    public function whereDay(string $field, string $day = 'today', string $logic = 'AND')
2232
    {
2233
        if (in_array($day, ['today', 'yesterday'])) {
2234
            $day = date('Y-m-d', strtotime($day));
2235
        }
2236
2237
        return $this->whereTimeInterval($field, $day, 'day', $logic);
2238
    }
2239
2240
    /**
2241
     * 查询日期或者时间范围 whereBetweenTime('time_field', '2018-1-1','2018-1-15')
2242
     * @access public
2243
     * @param  string     $field 日期字段名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2244
     * @param  string|int $startTime    开始时间
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
2245
     * @param  string|int $endTime 结束时间
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
2246
     * @param  string     $logic AND OR
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2247
     * @return $this
2248
     */
2249
    public function whereBetweenTime(string $field, $startTime, $endTime, string $logic = 'AND')
2250
    {
2251
        return $this->whereTime($field, 'between', [$startTime, $endTime], $logic);
2252
    }
2253
2254
    /**
2255
     * 查询日期或者时间范围 whereNotBetweenTime('time_field', '2018-1-1','2018-1-15')
2256
     * @access public
2257
     * @param  string     $field 日期字段名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2258
     * @param  string|int $startTime    开始时间
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
2259
     * @param  string|int $endTime 结束时间
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
2260
     * @return $this
2261
     */
2262
    public function whereNotBetweenTime(string $field, $startTime, $endTime)
2263
    {
2264
        return $this->whereTime($field, '<', $startTime)
2265
            ->whereTime($field, '>', $endTime);
2266
    }
2267
2268
    /**
2269
     * 查询当前时间在两个时间字段范围 whereBetweenTimeField('start_time', 'end_time')
2270
     * @access public
2271
     * @param  string $startField 开始时间字段
2272
     * @param  string $endField   结束时间字段
2273
     * @return $this
2274
     */
2275
    public function whereBetweenTimeField(string $startField, string $endField)
2276
    {
2277
        return $this->whereTime($startField, '<=', time())
2278
            ->whereTime($endField, '>=', time());
2279
    }
2280
2281
    /**
2282
     * 查询当前时间不在两个时间字段范围 whereNotBetweenTimeField('start_time', 'end_time')
2283
     * @access public
2284
     * @param  string $startField 开始时间字段
2285
     * @param  string $endField   结束时间字段
2286
     * @return $this
2287
     */
2288
    public function whereNotBetweenTimeField(string $startField, string $endField)
2289
    {
2290
        return $this->whereTime($startField, '>', time())
2291
            ->whereTime($endField, '<', time(), 'OR');
2292
    }
2293
2294
    /**
2295
     * 获取当前数据表的主键
2296
     * @access public
2297
     * @return string|array
2298
     */
2299
    public function getPk()
2300
    {
2301
        if (!empty($this->pk)) {
2302
            $pk = $this->pk;
2303
        } else {
2304
            $this->pk = $pk = $this->connection->getPk($this->getTable());
2305
        }
2306
2307
        return $pk;
2308
    }
2309
2310
    /**
2311
     * 批量参数绑定
2312
     * @access public
2313
     * @param  array $value 绑定变量值
2314
     * @return $this
2315
     */
2316
    public function bind(array $value)
2317
    {
2318
        $this->bind = array_merge($this->bind, $value);
2319
        return $this;
2320
    }
2321
2322
    /**
2323
     * 单个参数绑定
2324
     * @access public
2325
     * @param  mixed   $value 绑定变量值
2326
     * @param  integer $type  绑定类型
2327
     * @param  string  $name  绑定标识
2328
     * @return string
2329
     */
2330
    public function bindValue($value, int $type = null, string $name = null)
2331
    {
2332
        $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_';
2333
2334
        $this->bind[$name] = [$value, $type ?: PDO::PARAM_STR];
2335
        return $name;
2336
    }
2337
2338
    /**
2339
     * 检测参数是否已经绑定
2340
     * @access public
2341
     * @param  string $key 参数名
2342
     * @return bool
2343
     */
2344
    public function isBind($key)
2345
    {
2346
        return isset($this->bind[$key]);
2347
    }
2348
2349
    /**
2350
     * 参数绑定
2351
     * @access public
2352
     * @param  string $sql  绑定的sql表达式
2353
     * @param  array  $bind 参数绑定
2354
     * @return void
2355
     */
2356
    protected function bindParams(string &$sql, array $bind = []): void
2357
    {
2358
        foreach ($bind as $key => $value) {
2359
            if (is_array($value)) {
2360
                $name = $this->bindValue($value[0], $value[1], $value[2] ?? null);
2361
            } else {
2362
                $name = $this->bindValue($value);
2363
            }
2364
2365
            if (is_numeric($key)) {
2366
                $sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1);
2367
            } else {
2368
                $sql = str_replace(':' . $key, ':' . $name, $sql);
2369
            }
2370
        }
2371
    }
2372
2373
    /**
2374
     * 查询参数批量赋值
2375
     * @access protected
2376
     * @param  array $options 表达式参数
2377
     * @return $this
2378
     */
2379
    protected function options(array $options)
2380
    {
2381
        $this->options = $options;
2382
        return $this;
2383
    }
2384
2385
    /**
2386
     * 获取当前的查询参数
2387
     * @access public
2388
     * @param  string $name 参数名
2389
     * @return mixed
2390
     */
2391
    public function getOptions(string $name = '')
2392
    {
2393
        if ('' === $name) {
2394
            return $this->options;
2395
        }
2396
2397
        return $this->options[$name] ?? null;
2398
    }
2399
2400
    /**
2401
     * 设置当前的查询参数
2402
     * @access public
2403
     * @param  string $option 参数名
2404
     * @param  mixed  $value  参数值
2405
     * @return $this
2406
     */
2407
    public function setOption(string $option, $value)
2408
    {
2409
        $this->options[$option] = $value;
2410
        return $this;
2411
    }
2412
2413
    /**
2414
     * 设置关联查询
2415
     * @access public
2416
     * @param  array $relation 关联名称
2417
     * @return $this
2418
     */
2419
    public function relation(array $relation)
2420
    {
2421
        if (!empty($relation)) {
2422
            $this->options['relation'] = $relation;
2423
        }
2424
2425
        return $this;
2426
    }
2427
2428
    /**
2429
     * 设置关联查询JOIN预查询
2430
     * @access public
2431
     * @param  array $with 关联方法名称(数组)
2432
     * @return $this
2433
     */
2434
    public function with(array $with)
2435
    {
2436
        if (!empty($with)) {
2437
            $this->options['with'] = $with;
2438
        }
2439
2440
        return $this;
2441
    }
2442
2443
    /**
2444
     * 关联预载入 JOIN方式
2445
     * @access protected
2446
     * @param  array  $with 关联方法名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2447
     * @param  string $joinType JOIN方式
2448
     * @return $this
2449
     */
2450
    public function withJoin(array $with, string $joinType = '')
2451
    {
2452
        if (empty($with)) {
2453
            return $this;
2454
        }
2455
2456
        $first = true;
2457
2458
        /** @var Model $class */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
2459
        $class = $this->model;
2460
        foreach ($with as $key => $relation) {
2461
            $closure = null;
2462
            $field   = true;
2463
2464
            if ($relation instanceof Closure) {
2465
                // 支持闭包查询过滤关联条件
2466
                $closure  = $relation;
2467
                $relation = $key;
2468
            } elseif (is_array($relation)) {
2469
                $field    = $relation;
2470
                $relation = $key;
2471
            } elseif (is_string($relation) && strpos($relation, '.')) {
2472
                $relation = strstr($relation, '.', true);
2473
            }
2474
2475
            /** @var Relation $model */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
2476
            $relation = App::parseName($relation, 1, false);
2477
            $model    = $class->$relation();
2478
2479
            if ($model instanceof OneToOne) {
2480
                $model->eagerly($this, $relation, $field, $joinType, $closure, $first);
2481
                $first = false;
2482
            } else {
2483
                // 不支持其它关联
2484
                unset($with[$key]);
2485
            }
2486
        }
2487
2488
        $this->via();
2489
2490
        $this->options['with_join'] = $with;
2491
2492
        return $this;
2493
    }
2494
2495
    /**
2496
     * 设置数据字段获取器
2497
     * @access public
2498
     * @param  string   $name     字段名
2499
     * @param  callable $callback 闭包获取器
2500
     * @return $this
2501
     */
2502
    public function withAttr(string $name, callable $callback)
2503
    {
2504
        $this->options['with_attr'][$name] = $callback;
2505
2506
        return $this;
2507
    }
2508
2509
    /**
2510
     * 设置数据字段获取器
2511
     * @access public
2512
     * @param  array $attrs 字段获取器
2513
     * @return $this
2514
     */
2515
    public function withAttrs(array $attrs)
2516
    {
2517
        $this->options['with_attr'] = $attrs;
2518
2519
        return $this;
2520
    }
2521
2522
    /**
2523
     * 使用搜索器条件搜索字段
2524
     * @access public
2525
     * @param  array  $fields 搜索字段
2526
     * @param  array  $data   搜索数据
2527
     * @param  string $prefix 字段前缀标识
2528
     * @return $this
2529
     */
2530
    public function withSearch(array $fields, array $data = [], string $prefix = '')
2531
    {
2532
        foreach ($fields as $key => $field) {
2533
            if ($field instanceof Closure) {
2534
                $field($this, $data[$key] ?? null, $data, $prefix);
2535
            } elseif ($this->model) {
2536
                // 检测搜索器
2537
                $fieldName = is_numeric($key) ? $field : $key;
2538
                $method    = 'search' . App::parseName($fieldName, 1) . 'Attr';
2539
2540
                if (method_exists($this->model, $method)) {
2541
                    $this->model->$method($this, $data[$field] ?? null, $data, $prefix);
2542
                }
2543
            }
2544
        }
2545
2546
        return $this;
2547
    }
2548
2549
    /**
2550
     * 关联统计
2551
     * @access protected
2552
     * @param  array|string $relations 关联方法名
2553
     * @param  string       $aggregate 聚合查询方法
2554
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2555
     * @param  bool         $subQuery 是否使用子查询
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
2556
     * @return $this
2557
     */
2558
    protected function withAggregate($relations, string $aggregate = 'count', $field = '*', bool $subQuery = true)
2559
    {
2560
        if (is_string($relations)) {
2561
            $relations = explode(',', $relations);
2562
        }
2563
2564
        if (!$subQuery) {
2565
            $this->options['with_count'][] = [$relations, $aggregate, $field];
2566
        } else {
2567
            if (!isset($this->options['field'])) {
2568
                $this->field('*');
2569
            }
2570
2571
            foreach ($relations as $key => $relation) {
2572
                $closure = $aggregateField = null;
2573
2574
                if ($relation instanceof Closure) {
2575
                    $closure  = $relation;
2576
                    $relation = $key;
2577
                } elseif (!is_int($key)) {
2578
                    $aggregateField = $relation;
2579
                    $relation       = $key;
2580
                }
2581
2582
                $relation = App::parseName($relation, 1, false);
2583
2584
                $count = '(' . $this->model->$relation()->getRelationCountQuery($closure, $aggregate, $field, $aggregateField) . ')';
2585
2586
                if (empty($aggregateField)) {
2587
                    $aggregateField = App::parseName($relation) . '_' . $aggregate;
2588
                }
2589
2590
                $this->field([$count => $aggregateField]);
2591
            }
2592
        }
2593
2594
        return $this;
2595
    }
2596
2597
    /**
2598
     * 关联统计
2599
     * @access public
2600
     * @param  string|array $relation 关联方法名
2601
     * @param  bool         $subQuery 是否使用子查询
2602
     * @return $this
2603
     */
2604
    public function withCount($relation, bool $subQuery = true)
2605
    {
2606
        return $this->withAggregate($relation, 'count', '*', $subQuery);
2607
    }
2608
2609
    /**
2610
     * 关联统计Sum
2611
     * @access public
2612
     * @param  string|array $relation 关联方法名
2613
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2614
     * @param  bool         $subQuery 是否使用子查询
2615
     * @return $this
2616
     */
2617
    public function withSum($relation, string $field, bool $subQuery = true)
2618
    {
2619
        return $this->withAggregate($relation, 'sum', $field, $subQuery);
2620
    }
2621
2622
    /**
2623
     * 关联统计Max
2624
     * @access public
2625
     * @param  string|array $relation 关联方法名
2626
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2627
     * @param  bool         $subQuery 是否使用子查询
2628
     * @return $this
2629
     */
2630
    public function withMax($relation, string $field, bool $subQuery = true)
2631
    {
2632
        return $this->withAggregate($relation, 'max', $field, $subQuery);
2633
    }
2634
2635
    /**
2636
     * 关联统计Min
2637
     * @access public
2638
     * @param  string|array $relation 关联方法名
2639
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2640
     * @param  bool         $subQuery 是否使用子查询
2641
     * @return $this
2642
     */
2643
    public function withMin($relation, string $field, bool $subQuery = true)
2644
    {
2645
        return $this->withAggregate($relation, 'min', $field, $subQuery);
2646
    }
2647
2648
    /**
2649
     * 关联统计Avg
2650
     * @access public
2651
     * @param  string|array $relation 关联方法名
2652
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2653
     * @param  bool         $subQuery 是否使用子查询
2654
     * @return $this
2655
     */
2656
    public function withAvg($relation, string $field, bool $subQuery = true)
2657
    {
2658
        return $this->withAggregate($relation, 'avg', $field, $subQuery);
2659
    }
2660
2661
    /**
2662
     * 关联预加载中 获取关联指定字段值
2663
     * example:
2664
     * Model::with(['relation' => function($query){
2665
     *     $query->withField("id,name");
2666
     * }])
2667
     *
2668
     * @access public
2669
     * @param  string|array $field 指定获取的字段
2670
     * @return $this
2671
     */
2672
    public function withField($field)
2673
    {
2674
        $this->options['with_field'] = $field;
2675
2676
        return $this;
2677
    }
2678
2679
    /**
2680
     * 设置当前字段添加的表别名
2681
     * @access public
2682
     * @param  string $via 临时表别名
2683
     * @return $this
2684
     */
2685
    public function via(string $via = '')
2686
    {
2687
        $this->options['via'] = $via;
2688
2689
        return $this;
2690
    }
2691
2692
    /**
2693
     * 保存记录 自动判断insert或者update
2694
     * @access public
2695
     * @param  array $data        数据
2696
     * @param  bool  $forceInsert 是否强制insert
2697
     * @return integer
2698
     */
2699
    public function save(array $data = [], bool $forceInsert = false)
2700
    {
2701
        if ($forceInsert) {
2702
            return $this->insert($data);
2703
        }
2704
2705
        $this->options['data'] = array_merge($this->options['data'] ?? [], $data);
2706
2707
        if (!empty($this->options['where'])) {
2708
            $isUpdate = true;
2709
        } else {
2710
            $isUpdate = $this->parseUpdateData($this->options['data']);
2711
        }
2712
2713
        return $isUpdate ? $this->update() : $this->insert();
2714
    }
2715
2716
    /**
2717
     * 插入记录
2718
     * @access public
2719
     * @param  array   $data         数据
2720
     * @param  boolean $getLastInsID 返回自增主键
2721
     * @return integer
2722
     */
2723
    public function insert(array $data = [], bool $getLastInsID = false)
2724
    {
2725
        if (!empty($data)) {
2726
            $this->options['data'] = $data;
2727
        }
2728
2729
        return $this->connection->insert($this, $getLastInsID);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->connection...t($this, $getLastInsID) also could return the type string which is incompatible with the documented return type integer.
Loading history...
2730
    }
2731
2732
    /**
2733
     * 插入记录并获取自增ID
2734
     * @access public
2735
     * @param  array   $data     数据
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 5 found
Loading history...
2736
     * @return integer|string
2737
     */
2738
    public function insertGetId(array $data)
2739
    {
2740
        return $this->insert($data, true);
2741
    }
2742
2743
    /**
2744
     * 批量插入记录
2745
     * @access public
2746
     * @param  array   $dataSet 数据集
2747
     * @param  integer $limit   每次写入数据限制
2748
     * @return integer
2749
     */
2750
    public function insertAll(array $dataSet = [], int $limit = 0): int
2751
    {
2752
        if (empty($dataSet)) {
2753
            $dataSet = $this->options['data'] ?? [];
2754
        }
2755
2756
        if (empty($limit) && !empty($this->options['limit']) && is_numeric($this->options['limit'])) {
2757
            $limit = (int) $this->options['limit'];
2758
        }
2759
2760
        return $this->connection->insertAll($this, $dataSet, $limit);
2761
    }
2762
2763
    /**
2764
     * 通过Select方式插入记录
2765
     * @access public
2766
     * @param  array  $fields 要插入的数据表字段名
2767
     * @param  string $table  要插入的数据表名
2768
     * @return integer
2769
     * @throws PDOException
2770
     */
2771
    public function selectInsert(array $fields, string $table): int
2772
    {
2773
        return $this->connection->selectInsert($this, $fields, $table);
2774
    }
2775
2776
    /**
2777
     * 更新记录
2778
     * @access public
2779
     * @param  mixed $data 数据
2780
     * @return integer
2781
     * @throws Exception
2782
     * @throws PDOException
2783
     */
2784
    public function update(array $data = []): int
2785
    {
2786
        if (!empty($data)) {
2787
            $this->options['data'] = array_merge($this->options['data'] ?? [], $data);
2788
        }
2789
2790
        if (empty($this->options['where'])) {
2791
            $this->parseUpdateData($this->options['data']);
2792
        }
2793
2794
        if (empty($this->options['where']) && $this->model) {
2795
            $this->where($this->model->getWhere());
2796
        }
2797
2798
        if (empty($this->options['where'])) {
2799
            // 如果没有任何更新条件则不执行
2800
            throw new Exception('miss update condition');
2801
        }
2802
2803
        return $this->connection->update($this);
2804
    }
2805
2806
    /**
2807
     * 删除记录
2808
     * @access public
2809
     * @param  mixed $data 表达式 true 表示强制删除
2810
     * @return int
2811
     * @throws Exception
2812
     * @throws PDOException
2813
     */
2814
    public function delete($data = null): int
2815
    {
2816
        if (!is_null($data) && true !== $data) {
2817
            // AR模式分析主键条件
2818
            $this->parsePkWhere($data);
2819
        }
2820
2821
        if (empty($this->options['where']) && $this->model) {
2822
            $this->where($this->model->getWhere());
2823
        }
2824
2825
        if (true !== $data && empty($this->options['where'])) {
2826
            // 如果条件为空 不进行删除操作 除非设置 1=1
2827
            throw new Exception('delete without condition');
2828
        }
2829
2830
        if (!empty($this->options['soft_delete'])) {
2831
            // 软删除
2832
            list($field, $condition) = $this->options['soft_delete'];
2833
            if ($condition) {
2834
                unset($this->options['soft_delete']);
2835
                $this->options['data'] = [$field => $condition];
2836
2837
                return $this->connection->update($this);
2838
            }
2839
        }
2840
2841
        $this->options['data'] = $data;
2842
2843
        return $this->connection->delete($this);
2844
    }
2845
2846
    /**
2847
     * 执行查询但只返回PDOStatement对象
2848
     * @access public
2849
     * @return PDOStatement
2850
     */
2851
    public function getPdo(): PDOStatement
2852
    {
2853
        return $this->connection->pdo($this);
2854
    }
2855
2856
    /**
2857
     * 使用游标查找记录
2858
     * @access public
2859
     * @param  mixed $data 数据
2860
     * @return \Generator
2861
     */
2862
    public function cursor($data = null)
2863
    {
2864
        if (!is_null($data)) {
2865
            // 主键条件分析
2866
            $this->parsePkWhere($data);
2867
        }
2868
2869
        $this->options['data'] = $data;
2870
2871
        $connection = clone $this->connection;
2872
2873
        return $connection->cursor($this);
2874
    }
2875
2876
    /**
2877
     * 查找记录
2878
     * @access public
2879
     * @param  mixed $data 数据
2880
     * @return Collection|array|ModelCollection
2881
     * @throws DbException
2882
     * @throws ModelNotFoundException
2883
     * @throws DataNotFoundException
2884
     */
2885
    public function select($data = null)
2886
    {
2887
        if (!is_null($data)) {
2888
            // 主键条件分析
2889
            $this->parsePkWhere($data);
2890
        }
2891
2892
        $resultSet = $this->connection->select($this);
2893
2894
        // 返回结果处理
2895
        if (!empty($this->options['fail']) && count($resultSet) == 0) {
2896
            $this->throwNotFound();
2897
        }
2898
2899
        // 数据列表读取后的处理
2900
        if (!empty($this->model) && empty($this->options['array'])) {
2901
            // 生成模型对象
2902
            $resultSet = $this->resultSetToModelCollection($resultSet);
2903
        } else {
2904
            $this->resultSet($resultSet);
2905
        }
2906
2907
        return $resultSet;
2908
    }
2909
2910
    /**
2911
     * 查询数据转换为模型数据集对象
2912
     * @access protected
2913
     * @param  array $resultSet 数据集
2914
     * @return ModelCollection
2915
     */
2916
    protected function resultSetToModelCollection(array $resultSet): ModelCollection
2917
    {
2918
        if (!empty($this->options['collection']) && is_string($this->options['collection'])) {
2919
            $collection = $this->options['collection'];
2920
        }
2921
2922
        if (empty($resultSet)) {
2923
            return $this->model->toCollection([], $collection ?? null);
2924
        }
2925
2926
        // 检查动态获取器
2927
        if (!empty($this->options['with_attr'])) {
2928
            foreach ($this->options['with_attr'] as $name => $val) {
2929
                if (strpos($name, '.')) {
2930
                    list($relation, $field) = explode('.', $name);
2931
2932
                    $withRelationAttr[$relation][$field] = $val;
2933
                    unset($this->options['with_attr'][$name]);
2934
                }
2935
            }
2936
        }
2937
2938
        $withRelationAttr = $withRelationAttr ?? [];
2939
2940
        foreach ($resultSet as $key => &$result) {
2941
            // 数据转换为模型对象
2942
            $this->resultToModel($result, $this->options, true, $withRelationAttr);
2943
        }
2944
2945
        if (!empty($this->options['with'])) {
2946
            // 预载入
2947
            $result->eagerlyResultSet($resultSet, $this->options['with'], $withRelationAttr);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result seems to be defined by a foreach iteration on line 2940. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2948
        }
2949
2950
        if (!empty($this->options['with_join'])) {
2951
            // 预载入
2952
            $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, true);
2953
        }
2954
2955
        // 模型数据集转换
2956
        return $this->model->toCollection($resultSet, $collection ?? null);
2957
    }
2958
2959
    /**
2960
     * 处理数据集
2961
     * @access public
2962
     * @param  array $resultSet 数据集
2963
     * @return void
2964
     */
2965
    protected function resultSet(array &$resultSet): void
2966
    {
2967
        if (!empty($this->options['json'])) {
2968
            foreach ($resultSet as &$result) {
2969
                $this->jsonResult($result, $this->options['json'], true);
2970
            }
2971
        }
2972
2973
        if (!empty($this->options['with_attr'])) {
2974
            foreach ($resultSet as &$result) {
2975
                $this->getResultAttr($result, $this->options['with_attr']);
2976
            }
2977
        }
2978
2979
        if (!empty($this->options['visible']) || !empty($this->options['hidden'])) {
2980
            foreach ($resultSet as &$result) {
2981
                $this->filterResult($result);
2982
            }
2983
        }
2984
2985
        if (!empty($this->options['collection'])) {
2986
            // 返回Collection对象
2987
            $resultSet = new Collection($resultSet);
2988
        }
2989
    }
2990
2991
    /**
2992
     * 查找单条记录
2993
     * @access public
2994
     * @param  mixed $data 查询数据
2995
     * @return array|Model|null
2996
     * @throws DbException
2997
     * @throws ModelNotFoundException
2998
     * @throws DataNotFoundException
2999
     */
3000
    public function find($data = null)
3001
    {
3002
        if (!is_null($data)) {
3003
            // AR模式分析主键条件
3004
            $this->parsePkWhere($data);
3005
        }
3006
3007
        $result = $this->connection->find($this);
3008
3009
        // 数据处理
3010
        if (empty($result)) {
3011
            return $this->resultToEmpty();
3012
        }
3013
3014
        if (!empty($this->model) && empty($this->options['array'])) {
3015
            // 返回模型对象
3016
            $this->resultToModel($result, $this->options);
3017
        } else {
3018
            $this->result($result);
3019
        }
3020
3021
        return $result;
3022
    }
3023
3024
    /**
3025
     * 查找单条记录 不存在返回空数据(或者空模型)
3026
     * @access public
3027
     * @param  mixed $data 数据
3028
     * @return array|Model
3029
     */
3030
    public function findOrEmpty($data = null)
3031
    {
3032
        return $this->allowEmpty(true)->find($data);
3033
    }
3034
3035
    /**
3036
     * 处理空数据
3037
     * @access protected
3038
     * @return array|Model|null
3039
     * @throws DbException
3040
     * @throws ModelNotFoundException
3041
     * @throws DataNotFoundException
3042
     */
3043
    protected function resultToEmpty()
3044
    {
3045
        if (!empty($this->options['fail'])) {
3046
            $this->throwNotFound();
3047
        } elseif (!empty($this->options['allow_empty'])) {
3048
            return !empty($this->model) && empty($this->options['array']) ? $this->model->newInstance() : [];
3049
        } elseif (!empty($this->options['array'])) {
3050
            return [];
3051
        }
3052
    }
3053
3054
    /**
3055
     * 获取模型的更新条件
3056
     * @access protected
3057
     * @param  array $options 查询参数
3058
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
3059
    protected function getModelUpdateCondition(array $options)
3060
    {
3061
        return $options['where']['AND'] ?? null;
3062
    }
3063
3064
    /**
3065
     * 处理数据
3066
     * @access protected
3067
     * @param  array $result 查询数据
3068
     * @return void
3069
     */
3070
    protected function result(array &$result): void
3071
    {
3072
        if (!empty($this->options['json'])) {
3073
            $this->jsonResult($result, $this->options['json'], true);
3074
        }
3075
3076
        if (!empty($this->options['with_attr'])) {
3077
            $this->getResultAttr($result, $this->options['with_attr']);
3078
        }
3079
3080
        $this->filterResult($result);
3081
    }
3082
3083
    /**
3084
     * 处理数据的可见和隐藏
3085
     * @access protected
3086
     * @param  array $result 查询数据
3087
     * @return void
3088
     */
3089
    protected function filterResult(&$result): void
3090
    {
3091
        if (!empty($this->options['visible'])) {
3092
            foreach ($this->options['visible'] as $key) {
3093
                $array[] = $key;
3094
            }
3095
            $result = array_intersect_key($result, array_flip($array));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $array seems to be defined by a foreach iteration on line 3092. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
3096
        } elseif (!empty($this->options['hidden'])) {
3097
            foreach ($this->options['hidden'] as $key) {
3098
                $array[] = $key;
3099
            }
3100
            $result = array_diff_key($result, array_flip($array));
3101
        }
3102
    }
3103
3104
    /**
3105
     * 使用获取器处理数据
3106
     * @access protected
3107
     * @param  array $result   查询数据
3108
     * @param  array $withAttr 字段获取器
3109
     * @return void
3110
     */
3111
    protected function getResultAttr(array &$result, array $withAttr = []): void
3112
    {
3113
        foreach ($withAttr as $name => $closure) {
3114
            $name = App::parseName($name);
3115
3116
            if (strpos($name, '.')) {
3117
                // 支持JSON字段 获取器定义
3118
                list($key, $field) = explode('.', $name);
3119
3120
                if (isset($result[$key])) {
3121
                    $result[$key][$field] = $closure($result[$key][$field] ?? null, $result[$key]);
3122
                }
3123
            } else {
3124
                $result[$name] = $closure($result[$name] ?? null, $result);
3125
            }
3126
        }
3127
    }
3128
3129
    /**
3130
     * JSON字段数据转换
3131
     * @access protected
3132
     * @param  array $result           查询数据
3133
     * @param  array $json             JSON字段
3134
     * @param  bool  $assoc            是否转换为数组
3135
     * @param  array $withRelationAttr 关联获取器
3136
     * @return void
3137
     */
3138
    protected function jsonResult(array &$result, array $json = [], bool $assoc = false, array $withRelationAttr = []): void
3139
    {
3140
        foreach ($json as $name) {
3141
            if (!isset($result[$name])) {
3142
                continue;
3143
            }
3144
3145
            $result[$name] = json_decode($result[$name], $assoc);
3146
3147
            if (!isset($withRelationAttr[$name])) {
3148
                continue;
3149
            }
3150
3151
            foreach ($withRelationAttr[$name] as $key => $closure) {
3152
                $data = get_object_vars($result[$name]);
3153
3154
                $result[$name]->$key = $closure($result[$name]->$key ?? null, $data);
3155
            }
3156
        }
3157
    }
3158
3159
    /**
3160
     * 查询数据转换为模型对象
3161
     * @access protected
3162
     * @param  array $result           查询数据
3163
     * @param  array $options          查询参数
3164
     * @param  bool  $resultSet        是否为数据集查询
3165
     * @param  array $withRelationAttr 关联字段获取器
3166
     * @return void
3167
     */
3168
    protected function resultToModel(array &$result, array $options = [], bool $resultSet = false, array $withRelationAttr = []): void
3169
    {
3170
        // 动态获取器
3171
        if (!empty($options['with_attr']) && empty($withRelationAttr)) {
3172
            foreach ($options['with_attr'] as $name => $val) {
3173
                if (strpos($name, '.')) {
3174
                    list($relation, $field) = explode('.', $name);
3175
3176
                    $withRelationAttr[$relation][$field] = $val;
3177
                    unset($options['with_attr'][$name]);
3178
                }
3179
            }
3180
        }
3181
3182
        // JSON 数据处理
3183
        if (!empty($options['json'])) {
3184
            $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr);
3185
        }
3186
3187
        $result = $this->model->newInstance($result, $resultSet ? null : $this->getModelUpdateCondition($options));
3188
3189
        // 动态获取器
3190
        if (!empty($options['with_attr'])) {
3191
            $result->withAttribute($options['with_attr']);
3192
        }
3193
3194
        // 输出属性控制
3195
        if (!empty($options['visible'])) {
3196
            $result->visible($options['visible']);
3197
        } elseif (!empty($options['hidden'])) {
3198
            $result->hidden($options['hidden']);
3199
        }
3200
3201
        if (!empty($options['append'])) {
3202
            $result->append($options['append']);
3203
        }
3204
3205
        // 关联查询
3206
        if (!empty($options['relation'])) {
3207
            $result->relationQuery($options['relation']);
3208
        }
3209
3210
        // 预载入查询
3211
        if (!$resultSet && !empty($options['with'])) {
3212
            $result->eagerlyResult($result, $options['with'], $withRelationAttr);
3213
        }
3214
3215
        // JOIN预载入查询
3216
        if (!$resultSet && !empty($options['with_join'])) {
3217
            $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, true);
3218
        }
3219
3220
        // 关联统计
3221
        if (!empty($options['with_count'])) {
3222
            foreach ($options['with_count'] as $val) {
3223
                $result->relationCount($result, $val[0], $val[1], $val[2]);
3224
            }
3225
        }
3226
    }
3227
3228
    /**
3229
     * 查询失败 抛出异常
3230
     * @access protected
3231
     * @return void
3232
     * @throws ModelNotFoundException
3233
     * @throws DataNotFoundException
3234
     */
3235
    protected function throwNotFound(): void
3236
    {
3237
        if (!empty($this->model)) {
3238
            $class = get_class($this->model);
3239
            throw new ModelNotFoundException('model data Not Found:' . $class, $class, $this->options);
3240
        }
3241
3242
        $table = $this->getTable();
3243
        throw new DataNotFoundException('table data not Found:' . $table, $table, $this->options);
3244
    }
3245
3246
    /**
3247
     * 查找多条记录 如果不存在则抛出异常
3248
     * @access public
3249
     * @param  array|string|Query|Closure $data 数据
3250
     * @return array|PDOStatement|string|Model
3251
     * @throws DbException
3252
     * @throws ModelNotFoundException
3253
     * @throws DataNotFoundException
3254
     */
3255
    public function selectOrFail($data = null)
3256
    {
3257
        return $this->failException(true)->select($data);
3258
    }
3259
3260
    /**
3261
     * 查找单条记录 如果不存在则抛出异常
3262
     * @access public
3263
     * @param  array|string|Query|Closure $data 数据
3264
     * @return array|PDOStatement|string|Model
3265
     * @throws DbException
3266
     * @throws ModelNotFoundException
3267
     * @throws DataNotFoundException
3268
     */
3269
    public function findOrFail($data = null)
3270
    {
3271
        return $this->failException(true)->find($data);
3272
    }
3273
3274
    /**
3275
     * 分批数据返回处理
3276
     * @access public
3277
     * @param  integer      $count    每次处理的数据数量
3278
     * @param  callable     $callback 处理回调方法
3279
     * @param  string|array $column   分批处理的字段名
3280
     * @param  string       $order    字段排序
3281
     * @return bool
3282
     * @throws DbException
3283
     */
3284
    public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool
3285
    {
3286
        $options = $this->getOptions();
3287
        $column  = $column ?: $this->getPk();
3288
3289
        if (isset($options['order'])) {
3290
            unset($options['order']);
3291
        }
3292
3293
        $bind = $this->bind;
3294
3295
        if (is_array($column)) {
3296
            $times = 1;
3297
            $query = $this->options($options)->page($times, $count);
3298
        } else {
3299
            $query = $this->options($options)->limit($count);
3300
3301
            if (strpos($column, '.')) {
3302
                list($alias, $key) = explode('.', $column);
3303
            } else {
3304
                $key = $column;
3305
            }
3306
        }
3307
3308
        $resultSet = $query->order($column, $order)->select();
3309
3310
        while (count($resultSet) > 0) {
3311
            if ($resultSet instanceof Collection) {
3312
                $resultSet = $resultSet->all();
3313
            }
3314
3315
            if (false === call_user_func($callback, $resultSet)) {
3316
                return false;
3317
            }
3318
3319
            if (isset($times)) {
3320
                $times++;
3321
                $query = $this->options($options)->page($times, $count);
3322
            } else {
3323
                $end    = end($resultSet);
3324
                $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...
3325
3326
                $query = $this->options($options)
3327
                    ->limit($count)
3328
                    ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
3329
            }
3330
3331
            $resultSet = $query->bind($bind)->order($column, $order)->select();
3332
        }
3333
3334
        return true;
3335
    }
3336
3337
    /**
3338
     * 获取绑定的参数 并清空
3339
     * @access public
3340
     * @param  bool $clear 是否清空绑定数据
3341
     * @return array
3342
     */
3343
    public function getBind(bool $clear = true): array
3344
    {
3345
        $bind = $this->bind;
3346
        if ($clear) {
3347
            $this->bind = [];
3348
        }
3349
3350
        return $bind;
3351
    }
3352
3353
    /**
3354
     * 创建子查询SQL
3355
     * @access public
3356
     * @param  bool $sub 是否添加括号
3357
     * @return string
3358
     * @throws DbException
3359
     */
3360
    public function buildSql(bool $sub = true): string
3361
    {
3362
        return $sub ? '( ' . $this->fetchSql()->select() . ' )' : $this->fetchSql()->select();
3363
    }
3364
3365
    /**
3366
     * 视图查询处理
3367
     * @access protected
3368
     * @param  array $options 查询参数
3369
     * @return void
3370
     */
3371
    protected function parseView(array &$options): void
3372
    {
3373
        foreach (['AND', 'OR'] as $logic) {
3374
            if (isset($options['where'][$logic])) {
3375
                foreach ($options['where'][$logic] as $key => $val) {
3376
                    if (array_key_exists($key, $options['map'])) {
3377
                        array_shift($val);
3378
                        array_unshift($val, $options['map'][$key]);
3379
                        $options['where'][$logic][$options['map'][$key]] = $val;
3380
                        unset($options['where'][$logic][$key]);
3381
                    }
3382
                }
3383
            }
3384
        }
3385
3386
        if (isset($options['order'])) {
3387
            // 视图查询排序处理
3388
            foreach ($options['order'] as $key => $val) {
3389
                if (is_numeric($key) && is_string($val)) {
3390
                    if (strpos($val, ' ')) {
3391
                        list($field, $sort) = explode(' ', $val);
3392
                        if (array_key_exists($field, $options['map'])) {
3393
                            $options['order'][$options['map'][$field]] = $sort;
3394
                            unset($options['order'][$key]);
3395
                        }
3396
                    } elseif (array_key_exists($val, $options['map'])) {
3397
                        $options['order'][$options['map'][$val]] = 'asc';
3398
                        unset($options['order'][$key]);
3399
                    }
3400
                } elseif (array_key_exists($key, $options['map'])) {
3401
                    $options['order'][$options['map'][$key]] = $val;
3402
                    unset($options['order'][$key]);
3403
                }
3404
            }
3405
        }
3406
    }
3407
3408
    /**
3409
     * 分析数据是否存在更新条件
3410
     * @access public
3411
     * @param  array $data 数据
3412
     * @return bool
3413
     * @throws Exception
3414
     */
3415
    public function parseUpdateData(&$data): bool
3416
    {
3417
        $pk       = $this->getPk();
3418
        $isUpdate = false;
3419
        // 如果存在主键数据 则自动作为更新条件
3420
        if (is_string($pk) && isset($data[$pk])) {
3421
            $this->where($pk, '=', $data[$pk]);
3422
            $this->options['key'] = $data[$pk];
3423
            unset($data[$pk]);
3424
            $isUpdate = true;
3425
        } elseif (is_array($pk)) {
3426
            // 增加复合主键支持
3427
            foreach ($pk as $field) {
3428
                if (isset($data[$field])) {
3429
                    $this->where($field, '=', $data[$field]);
3430
                    $isUpdate = true;
3431
                } else {
3432
                    // 如果缺少复合主键数据则不执行
3433
                    throw new Exception('miss complex primary data');
3434
                }
3435
                unset($data[$field]);
3436
            }
3437
        }
3438
3439
        return $isUpdate;
3440
    }
3441
3442
    /**
3443
     * 把主键值转换为查询条件 支持复合主键
3444
     * @access public
3445
     * @param  array|string $data 主键数据
3446
     * @return void
3447
     * @throws Exception
3448
     */
3449
    public function parsePkWhere($data): void
3450
    {
3451
        $pk = $this->getPk();
3452
3453
        if (is_string($pk)) {
3454
            // 获取数据表
3455
            if (empty($this->options['table'])) {
3456
                $this->options['table'] = $this->getTable();
3457
            }
3458
3459
            $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table'];
3460
3461
            if (!empty($this->options['alias'][$table])) {
3462
                $alias = $this->options['alias'][$table];
3463
            }
3464
3465
            $key = isset($alias) ? $alias . '.' . $pk : $pk;
3466
            // 根据主键查询
3467
            if (is_array($data)) {
3468
                $this->where($key, 'in', $data);
3469
            } else {
3470
                $this->where($key, '=', $data);
3471
                $this->options['key'] = $data;
3472
            }
3473
        }
3474
    }
3475
3476
    /**
3477
     * 分析表达式(可用于查询或者写入操作)
3478
     * @access public
3479
     * @return array
3480
     */
3481
    public function parseOptions(): array
3482
    {
3483
        $options = $this->getOptions();
3484
3485
        // 获取数据表
3486
        if (empty($options['table'])) {
3487
            $options['table'] = $this->getTable();
3488
        }
3489
3490
        if (!isset($options['where'])) {
3491
            $options['where'] = [];
3492
        } elseif (isset($options['view'])) {
3493
            // 视图查询条件处理
3494
            $this->parseView($options);
3495
        }
3496
3497
        if (!isset($options['field'])) {
3498
            $options['field'] = '*';
3499
        }
3500
3501
        foreach (['data', 'order', 'join', 'union'] as $name) {
3502
            if (!isset($options[$name])) {
3503
                $options[$name] = [];
3504
            }
3505
        }
3506
3507
        if (!isset($options['strict'])) {
3508
            $options['strict'] = $this->connection->getConfig('fields_strict');
3509
        }
3510
3511
        foreach (['master', 'lock', 'fetch_sql', 'array', 'distinct', 'procedure'] as $name) {
3512
            if (!isset($options[$name])) {
3513
                $options[$name] = false;
3514
            }
3515
        }
3516
3517
        if (is_string($options['table']) && $this->db->isReadMaster($options['table'])) {
3518
            $options['master'] = true;
3519
        }
3520
3521
        foreach (['group', 'having', 'limit', 'force', 'comment', 'partition', 'duplicate', 'extra'] as $name) {
3522
            if (!isset($options[$name])) {
3523
                $options[$name] = '';
3524
            }
3525
        }
3526
3527
        if (isset($options['page'])) {
3528
            // 根据页数计算limit
3529
            list($page, $listRows) = $options['page'];
3530
            $page                  = $page > 0 ? $page : 1;
3531
            $listRows              = $listRows ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20);
3532
            $offset                = $listRows * ($page - 1);
3533
            $options['limit']      = $offset . ',' . $listRows;
3534
        }
3535
3536
        $this->options = $options;
3537
3538
        return $options;
3539
    }
3540
3541
    /**
3542
     * 触发事件
3543
     * @access public
3544
     * @param  string $event 事件名
3545
     * @return mixed
3546
     */
3547
    public function trigger(string $event)
3548
    {
3549
        return $this->event->trigger('db.' . $event, $this);
3550
    }
3551
3552
}
3553