Passed
Push — 5.2 ( a32be7...2654b8 )
by liu
02:41
created

Query::distinct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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