Passed
Push — 5.2 ( 2654b8...acaf63 )
by liu
02:34
created

Query::findOrEmpty()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
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
     * 是否允许返回空数据(或空模型)
1572
     * @access public
1573
     * @param  bool $allowEmpty 是否允许为空
1574
     * @return $this
1575
     */
1576
    public function allowEmpty(bool $allowEmpty = true)
1577
    {
1578
        $this->options['allow_empty'] = $allowEmpty;
1579
        return $this;
1580
    }
1581
1582
    /**
1583
     * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc'])
1584
     * @access public
1585
     * @param  string|array|Raw $field 排序字段
1586
     * @param  string           $order 排序
1587
     * @return $this
1588
     */
1589
    public function order($field, string $order = '')
1590
    {
1591
        if (empty($field)) {
1592
            return $this;
1593
        } elseif ($field instanceof Raw) {
1594
            $this->options['order'][] = $field;
1595
            return $this;
1596
        }
1597
1598
        if (is_string($field)) {
1599
            if (!empty($this->options['via'])) {
1600
                $field = $this->options['via'] . '.' . $field;
1601
            }
1602
            if (strpos($field, ',')) {
1603
                $field = array_map('trim', explode(',', $field));
1604
            } else {
1605
                $field = empty($order) ? $field : [$field => $order];
1606
            }
1607
        } elseif (!empty($this->options['via'])) {
1608
            foreach ($field as $key => $val) {
1609
                if (is_numeric($key)) {
1610
                    $field[$key] = $this->options['via'] . '.' . $val;
1611
                } else {
1612
                    $field[$this->options['via'] . '.' . $key] = $val;
1613
                    unset($field[$key]);
1614
                }
1615
            }
1616
        }
1617
1618
        if (!isset($this->options['order'])) {
1619
            $this->options['order'] = [];
1620
        }
1621
1622
        if (is_array($field)) {
1623
            $this->options['order'] = array_merge($this->options['order'], $field);
1624
        } else {
1625
            $this->options['order'][] = $field;
1626
        }
1627
1628
        return $this;
1629
    }
1630
1631
    /**
1632
     * 表达式方式指定Field排序
1633
     * @access public
1634
     * @param  string $field 排序字段
1635
     * @param  array  $bind  参数绑定
1636
     * @return $this
1637
     */
1638
    public function orderRaw(string $field, array $bind = [])
1639
    {
1640
        if (!empty($bind)) {
1641
            $this->bindParams($field, $bind);
1642
        }
1643
1644
        $this->options['order'][] = new Raw($field);
1645
1646
        return $this;
1647
    }
1648
1649
    /**
1650
     * 指定Field排序 orderField('id',[1,2,3],'desc')
1651
     * @access public
1652
     * @param  string   $field 排序字段
1653
     * @param  array    $values 排序值
1654
     * @param  string   $order
1655
     * @return $this
1656
     */
1657
    public function orderField(string $field, array $values, string $order = '')
1658
    {
1659
        if (!empty($values)) {
1660
            $values['sort'] = $order;
1661
1662
            $this->options['order'][$field] = $values;
1663
        }
1664
1665
        return $this;
1666
    }
1667
1668
    /**
1669
     * 随机排序
1670
     * @access public
1671
     * @return $this
1672
     */
1673
    public function orderRand()
1674
    {
1675
        $this->options['order'][] = '[rand]';
1676
        return $this;
1677
    }
1678
1679
    /**
1680
     * 查询缓存
1681
     * @access public
1682
     * @param  mixed             $key    缓存key
1683
     * @param  integer|\DateTime $expire 缓存有效期
1684
     * @param  string            $tag    缓存标签
1685
     * @return $this
1686
     */
1687
    public function cache($key = true, $expire = null, $tag = null)
1688
    {
1689
        if (false === $key) {
1690
            return $this;
1691
        }
1692
1693
        if ($key instanceof \DateTimeInterface || (is_int($key) && is_null($expire))) {
1694
            $expire = $key;
1695
            $key    = true;
1696
        }
1697
1698
        $this->options['cache'] = [$key, $expire, $tag];
1699
1700
        return $this;
1701
    }
1702
1703
    /**
1704
     * 指定group查询
1705
     * @access public
1706
     * @param  string|array $group GROUP
1707
     * @return $this
1708
     */
1709
    public function group($group)
1710
    {
1711
        $this->options['group'] = $group;
1712
        return $this;
1713
    }
1714
1715
    /**
1716
     * 指定having查询
1717
     * @access public
1718
     * @param  string $having having
1719
     * @return $this
1720
     */
1721
    public function having(string $having)
1722
    {
1723
        $this->options['having'] = $having;
1724
        return $this;
1725
    }
1726
1727
    /**
1728
     * 指定查询lock
1729
     * @access public
1730
     * @param  bool|string $lock 是否lock
1731
     * @return $this
1732
     */
1733
    public function lock($lock = false)
1734
    {
1735
        $this->options['lock'] = $lock;
1736
1737
        if ($lock) {
1738
            $this->options['master'] = true;
1739
        }
1740
1741
        return $this;
1742
    }
1743
1744
    /**
1745
     * 指定distinct查询
1746
     * @access public
1747
     * @param  bool $distinct 是否唯一
1748
     * @return $this
1749
     */
1750
    public function distinct(bool $distinct = true)
1751
    {
1752
        $this->options['distinct'] = $distinct;
1753
        return $this;
1754
    }
1755
1756
    /**
1757
     * 指定数据表别名
1758
     * @access public
1759
     * @param  array|string $alias 数据表别名
1760
     * @return $this
1761
     */
1762
    public function alias($alias)
1763
    {
1764
        if (is_array($alias)) {
1765
            $this->options['alias'] = $alias;
1766
        } else {
1767
            $table = $this->getTable();
1768
1769
            $this->options['alias'][$table] = $alias;
1770
        }
1771
1772
        return $this;
1773
    }
1774
1775
    /**
1776
     * 指定强制索引
1777
     * @access public
1778
     * @param  string $force 索引名称
1779
     * @return $this
1780
     */
1781
    public function force(string $force)
1782
    {
1783
        $this->options['force'] = $force;
1784
        return $this;
1785
    }
1786
1787
    /**
1788
     * 查询注释
1789
     * @access public
1790
     * @param  string $comment 注释
1791
     * @return $this
1792
     */
1793
    public function comment(string $comment)
1794
    {
1795
        $this->options['comment'] = $comment;
1796
        return $this;
1797
    }
1798
1799
    /**
1800
     * 获取执行的SQL语句而不进行实际的查询
1801
     * @access public
1802
     * @param  bool $fetch 是否返回sql
1803
     * @return $this|Fetch
1804
     */
1805
    public function fetchSql(bool $fetch = true)
1806
    {
1807
        $this->options['fetch_sql'] = $fetch;
1808
1809
        if ($fetch) {
1810
            return new Fetch($this);
1811
        }
1812
1813
        return $this;
1814
    }
1815
1816
    /**
1817
     * 设置是否返回数据集对象
1818
     * @access public
1819
     * @param  bool|string  $collection  是否返回数据集对象
1820
     * @return $this
1821
     */
1822
    public function fetchCollection($collection = true)
1823
    {
1824
        $this->options['collection'] = $collection;
1825
        return $this;
1826
    }
1827
1828
    /**
1829
     * 设置是否返回数组
1830
     * @access public
1831
     * @param  bool  $asArray  是否返回数组
1832
     * @return $this
1833
     */
1834
    public function fetchArray(bool $asArray = true)
1835
    {
1836
        $this->options['array'] = $asArray;
1837
        return $this;
1838
    }
1839
1840
    /**
1841
     * 设置从主服务器读取数据
1842
     * @access public
1843
     * @param  bool $readMaster 是否从主服务器读取
1844
     * @return $this
1845
     */
1846
    public function master(bool $readMaster = true)
1847
    {
1848
        $this->options['master'] = $readMaster;
1849
        return $this;
1850
    }
1851
1852
    /**
1853
     * 设置是否严格检查字段名
1854
     * @access public
1855
     * @param  bool $strict 是否严格检查字段
1856
     * @return $this
1857
     */
1858
    public function strict(bool $strict = true)
1859
    {
1860
        $this->options['strict'] = $strict;
1861
        return $this;
1862
    }
1863
1864
    /**
1865
     * 设置查询数据不存在是否抛出异常
1866
     * @access public
1867
     * @param  bool $fail 数据不存在是否抛出异常
1868
     * @return $this
1869
     */
1870
    public function failException(bool $fail = true)
1871
    {
1872
        $this->options['fail'] = $fail;
1873
        return $this;
1874
    }
1875
1876
    /**
1877
     * 设置自增序列名
1878
     * @access public
1879
     * @param  string $sequence 自增序列名
1880
     * @return $this
1881
     */
1882
    public function sequence(string $sequence = null)
1883
    {
1884
        $this->options['sequence'] = $sequence;
1885
        return $this;
1886
    }
1887
1888
    /**
1889
     * 设置当前查询所在的分区
1890
     * @access public
1891
     * @param  string|array  $partition  分区名称
1892
     * @return $this
1893
     */
1894
    public function partition($partition)
1895
    {
1896
        $this->options['partition'] = $partition;
1897
        return $this;
1898
    }
1899
1900
    /**
1901
     * 设置DUPLICATE
1902
     * @access public
1903
     * @param  array|string|Raw  $duplicate
1904
     * @return $this
1905
     */
1906
    public function duplicate($duplicate)
1907
    {
1908
        $this->options['duplicate'] = $duplicate;
1909
        return $this;
1910
    }
1911
1912
    /**
1913
     * 设置查询的额外参数
1914
     * @access public
1915
     * @param  string  $extra
1916
     * @return $this
1917
     */
1918
    public function extra(string $extra)
1919
    {
1920
        $this->options['extra'] = $extra;
1921
        return $this;
1922
    }
1923
1924
    /**
1925
     * 设置需要隐藏的输出属性
1926
     * @access public
1927
     * @param  array $hidden 需要隐藏的字段名
1928
     * @return $this
1929
     */
1930
    public function hidden(array $hidden)
1931
    {
1932
        $this->options['hidden'] = $hidden;
1933
        return $this;
1934
    }
1935
1936
    /**
1937
     * 设置需要输出的属性
1938
     * @access public
1939
     * @param  array $visible 需要输出的属性
1940
     * @return $this
1941
     */
1942
    public function visible(array $visible)
1943
    {
1944
        $this->options['visible'] = $visible;
1945
        return $this;
1946
    }
1947
1948
    /**
1949
     * 设置需要追加输出的属性
1950
     * @access public
1951
     * @param  array $append 需要追加的属性
1952
     * @return $this
1953
     */
1954
    public function append(array $append)
1955
    {
1956
        $this->options['append'] = $append;
1957
        return $this;
1958
    }
1959
1960
    /**
1961
     * 设置JSON字段信息
1962
     * @access public
1963
     * @param  array $json JSON字段
1964
     * @param  bool  $assoc 是否取出数组
1965
     * @return $this
1966
     */
1967
    public function json(array $json = [], bool $assoc = false)
1968
    {
1969
        $this->options['json']       = $json;
1970
        $this->options['json_assoc'] = $assoc;
1971
        return $this;
1972
    }
1973
1974
    /**
1975
     * 添加查询范围
1976
     * @access public
1977
     * @param  array|string|Closure   $scope 查询范围定义
1978
     * @param  array                   $args  参数
1979
     * @return $this
1980
     */
1981
    public function scope($scope, ...$args)
1982
    {
1983
        // 查询范围的第一个参数始终是当前查询对象
1984
        array_unshift($args, $this);
1985
1986
        if ($scope instanceof Closure) {
1987
            call_user_func_array($scope, $args);
1988
            return $this;
1989
        }
1990
1991
        if (is_string($scope)) {
1992
            $scope = explode(',', $scope);
1993
        }
1994
1995
        if ($this->model) {
1996
            // 检查模型类的查询范围方法
1997
            foreach ($scope as $name) {
1998
                $method = 'scope' . trim($name);
1999
2000
                if (method_exists($this->model, $method)) {
2001
                    call_user_func_array([$this->model, $method], $args);
2002
                }
2003
            }
2004
        }
2005
2006
        return $this;
2007
    }
2008
2009
    /**
2010
     * 指定数据表主键
2011
     * @access public
2012
     * @param  string $pk 主键
2013
     * @return $this
2014
     */
2015
    public function pk(string $pk)
2016
    {
2017
        $this->pk = $pk;
2018
        return $this;
2019
    }
2020
2021
    /**
2022
     * 添加日期或者时间查询规则
2023
     * @access public
2024
     * @param  string       $name  时间表达式
2025
     * @param  string|array $rule  时间范围
2026
     * @return $this
2027
     */
2028
    public function timeRule(string $name, $rule)
2029
    {
2030
        $this->timeRule[$name] = $rule;
2031
        return $this;
2032
    }
2033
2034
    /**
2035
     * 查询日期或者时间
2036
     * @access public
2037
     * @param  string       $field 日期字段名
2038
     * @param  string       $op    比较运算符或者表达式
2039
     * @param  string|array $range 比较范围
2040
     * @param  string       $logic AND OR
2041
     * @return $this
2042
     */
2043
    public function whereTime(string $field, string $op, $range = null, string $logic = 'AND')
2044
    {
2045
        if (is_null($range) && isset($this->timeRule[$op])) {
2046
            $range = $this->timeRule[$op];
2047
            $op    = 'between';
2048
        }
2049
2050
        return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true);
2051
    }
2052
2053
    /**
2054
     * 查询某个时间间隔数据
2055
     * @access protected
2056
     * @param  string    $field 日期字段名
2057
     * @param  string    $start 开始时间
2058
     * @param  string    $interval 时间间隔单位
2059
     * @param  string    $logic AND OR
2060
     * @return $this
2061
     */
2062
    protected function whereTimeInterval(string $field, string $start, string $interval = 'day', string $logic = 'AND')
2063
    {
2064
        $startTime = strtotime($start);
2065
        $endTime   = strtotime('+1 ' . $interval, $startTime);
2066
2067
        return $this->whereTime($field, 'between', [$startTime, $endTime], $logic);
2068
    }
2069
2070
    /**
2071
     * 查询月数据 whereMonth('time_field', '2018-1')
2072
     * @access public
2073
     * @param  string    $field 日期字段名
2074
     * @param  string    $month 月份信息
2075
     * @param  string    $logic AND OR
2076
     * @return $this
2077
     */
2078
    public function whereMonth(string $field, string $month = 'this month', string $logic = 'AND')
2079
    {
2080
        if (in_array($month, ['this month', 'last month'])) {
2081
            $month = date('Y-m', strtotime($month));
2082
        }
2083
2084
        return $this->whereTimeInterval($field, $month, 'month', $logic);
2085
    }
2086
2087
    /**
2088
     * 查询年数据 whereYear('time_field', '2018')
2089
     * @access public
2090
     * @param  string    $field 日期字段名
2091
     * @param  string    $year  年份信息
2092
     * @param  string    $logic AND OR
2093
     * @return $this
2094
     */
2095
    public function whereYear(string $field, string $year = 'this year', string $logic = 'AND')
2096
    {
2097
        if (in_array($year, ['this year', 'last year'])) {
2098
            $year = date('Y', strtotime($year));
2099
        }
2100
2101
        return $this->whereTimeInterval($field, $year . '-1-1', 'year', $logic);
2102
    }
2103
2104
    /**
2105
     * 查询日数据 whereDay('time_field', '2018-1-1')
2106
     * @access public
2107
     * @param  string    $field 日期字段名
2108
     * @param  string    $day   日期信息
2109
     * @param  string    $logic AND OR
2110
     * @return $this
2111
     */
2112
    public function whereDay(string $field, string $day = 'today', string $logic = 'AND')
2113
    {
2114
        if (in_array($day, ['today', 'yesterday'])) {
2115
            $day = date('Y-m-d', strtotime($day));
2116
        }
2117
2118
        return $this->whereTimeInterval($field, $day, 'day', $logic);
2119
    }
2120
2121
    /**
2122
     * 查询日期或者时间范围 whereBetweenTime('time_field', '2018-1-1','2018-1-15')
2123
     * @access public
2124
     * @param  string           $field 日期字段名
2125
     * @param  string|int       $startTime    开始时间
2126
     * @param  string|int       $endTime 结束时间
2127
     * @param  string           $logic AND OR
2128
     * @return $this
2129
     */
2130
    public function whereBetweenTime(string $field, $startTime, $endTime, string $logic = 'AND')
2131
    {
2132
        return $this->whereTime($field, 'between', [$startTime, $endTime], $logic);
2133
    }
2134
2135
    /**
2136
     * 查询日期或者时间范围 whereNotBetweenTime('time_field', '2018-1-1','2018-1-15')
2137
     * @access public
2138
     * @param  string           $field 日期字段名
2139
     * @param  string|int       $startTime    开始时间
2140
     * @param  string|int       $endTime 结束时间
2141
     * @return $this
2142
     */
2143
    public function whereNotBetweenTime(string $field, $startTime, $endTime)
2144
    {
2145
        return $this->whereTime($field, '<', $startTime)
2146
            ->whereTime($field, '>', $endTime);
2147
    }
2148
2149
    /**
2150
     * 查询当前时间在两个时间字段范围 whereBetweenTimeField('start_time', 'end_time')
2151
     * @access public
2152
     * @param  string    $startField    开始时间字段
2153
     * @param  string    $endField      结束时间字段
2154
     * @return $this
2155
     */
2156
    public function whereBetweenTimeField(string $startField, string $endField)
2157
    {
2158
        return $this->whereTime($startField, '<=', time())
2159
            ->whereTime($endField, '>=', time());
2160
    }
2161
2162
    /**
2163
     * 查询当前时间不在两个时间字段范围 whereNotBetweenTimeField('start_time', 'end_time')
2164
     * @access public
2165
     * @param  string    $startField    开始时间字段
2166
     * @param  string    $endField      结束时间字段
2167
     * @return $this
2168
     */
2169
    public function whereNotBetweenTimeField(string $startField, string $endField)
2170
    {
2171
        return $this->whereTime($startField, '>', time())
2172
            ->whereTime($endField, '<', time(), 'OR');
2173
    }
2174
2175
    /**
2176
     * 获取当前数据表的主键
2177
     * @access public
2178
     * @return string|array
2179
     */
2180
    public function getPk()
2181
    {
2182
        if (!empty($this->pk)) {
2183
            $pk = $this->pk;
2184
        } else {
2185
            $this->pk = $pk = $this->connection->getPk($this->getTable());
2186
        }
2187
2188
        return $pk;
2189
    }
2190
2191
    /**
2192
     * 批量参数绑定
2193
     * @access public
2194
     * @param  array   $value 绑定变量值
2195
     * @return $this
2196
     */
2197
    public function bind(array $value)
2198
    {
2199
        $this->bind = array_merge($this->bind, $value);
2200
        return $this;
2201
    }
2202
2203
    /**
2204
     * 单个参数绑定
2205
     * @access public
2206
     * @param  mixed   $value 绑定变量值
2207
     * @param  integer $type  绑定类型
2208
     * @param  string  $name  绑定标识
2209
     * @return string
2210
     */
2211
    public function bindValue($value, int $type = null, string $name = null)
2212
    {
2213
        $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_';
2214
2215
        $this->bind[$name] = [$value, $type ?: PDO::PARAM_STR];
2216
        return $name;
2217
    }
2218
2219
    /**
2220
     * 检测参数是否已经绑定
2221
     * @access public
2222
     * @param  string $key 参数名
2223
     * @return bool
2224
     */
2225
    public function isBind($key)
2226
    {
2227
        return isset($this->bind[$key]);
2228
    }
2229
2230
    /**
2231
     * 参数绑定
2232
     * @access public
2233
     * @param  string $sql    绑定的sql表达式
2234
     * @param  array  $bind   参数绑定
2235
     * @return void
2236
     */
2237
    protected function bindParams(string &$sql, array $bind = []): void
2238
    {
2239
        foreach ($bind as $key => $value) {
2240
            if (is_array($value)) {
2241
                $name = $this->bindValue($value[0], $value[1], $value[2] ?? null);
2242
            } else {
2243
                $name = $this->bindValue($value);
2244
            }
2245
2246
            if (is_numeric($key)) {
2247
                $sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1);
2248
            } else {
2249
                $sql = str_replace(':' . $key, ':' . $name, $sql);
2250
            }
2251
        }
2252
    }
2253
2254
    /**
2255
     * 查询参数批量赋值
2256
     * @access protected
2257
     * @param  array $options 表达式参数
2258
     * @return $this
2259
     */
2260
    protected function options(array $options)
2261
    {
2262
        $this->options = $options;
2263
        return $this;
2264
    }
2265
2266
    /**
2267
     * 获取当前的查询参数
2268
     * @access public
2269
     * @param  string $name 参数名
2270
     * @return mixed
2271
     */
2272
    public function getOptions(string $name = '')
2273
    {
2274
        if ('' === $name) {
2275
            return $this->options;
2276
        }
2277
2278
        return $this->options[$name] ?? null;
2279
    }
2280
2281
    /**
2282
     * 设置当前的查询参数
2283
     * @access public
2284
     * @param  string $option 参数名
2285
     * @param  mixed  $value  参数值
2286
     * @return $this
2287
     */
2288
    public function setOption(string $option, $value)
2289
    {
2290
        $this->options[$option] = $value;
2291
        return $this;
2292
    }
2293
2294
    /**
2295
     * 设置关联查询
2296
     * @access public
2297
     * @param  array $relation 关联名称
2298
     * @return $this
2299
     */
2300
    public function relation(array $relation)
2301
    {
2302
        if (!empty($relation)) {
2303
            $this->options['relation'] = $relation;
2304
        }
2305
2306
        return $this;
2307
    }
2308
2309
    /**
2310
     * 设置关联查询JOIN预查询
2311
     * @access public
2312
     * @param  array $with 关联方法名称(数组)
2313
     * @return $this
2314
     */
2315
    public function with(array $with)
2316
    {
2317
        if (!empty($with)) {
2318
            $this->options['with'] = $with;
2319
        }
2320
2321
        return $this;
2322
    }
2323
2324
    /**
2325
     * 设置一对一关联查询的属性绑定(必须和with搭配使用)
2326
     * @access public
2327
     * @param  string $relation 关联名称
2328
     * @param  array  $bind     关联属性名称
2329
     * @return $this
2330
     */
2331
    public function withBind(string $relation, array $bind = [])
2332
    {
2333
        $this->options['with_bind'][App::parseName($relation)] = $bind;
2334
2335
        return $this;
2336
    }
2337
2338
    /**
2339
     * 关联预载入 JOIN方式
2340
     * @access protected
2341
     * @param  array        $with 关联方法名
2342
     * @param  string       $joinType JOIN方式
2343
     * @return $this
2344
     */
2345
    public function withJoin(array $with, string $joinType = '')
2346
    {
2347
        if (empty($with)) {
2348
            return $this;
2349
        }
2350
2351
        $first = true;
2352
2353
        /** @var Model $class */
2354
        $class = $this->model;
2355
        foreach ($with as $key => $relation) {
2356
            $closure = null;
2357
            $field   = true;
2358
2359
            if ($relation instanceof Closure) {
2360
                // 支持闭包查询过滤关联条件
2361
                $closure  = $relation;
2362
                $relation = $key;
2363
            } elseif (is_array($relation)) {
2364
                $field    = $relation;
2365
                $relation = $key;
2366
            } elseif (is_string($relation) && strpos($relation, '.')) {
2367
                $relation = strstr($relation, '.', true);
2368
            }
2369
2370
            /** @var Relation $model */
2371
            $relation = App::parseName($relation, 1, false);
2372
            $model    = $class->$relation();
2373
2374
            if ($model instanceof OneToOne) {
2375
                $model->eagerly($this, $relation, $field, $joinType, $closure, $first);
2376
                $first = false;
2377
            } else {
2378
                // 不支持其它关联
2379
                unset($with[$key]);
2380
            }
2381
        }
2382
2383
        $this->via();
2384
2385
        $this->options['with_join'] = $with;
2386
2387
        return $this;
2388
    }
2389
2390
    /**
2391
     * 设置数据字段获取器
2392
     * @access public
2393
     * @param  string       $name       字段名
2394
     * @param  callable     $callback   闭包获取器
2395
     * @return $this
2396
     */
2397
    public function withAttr(string $name, callable $callback)
2398
    {
2399
        $this->options['with_attr'][$name] = $callback;
2400
2401
        return $this;
2402
    }
2403
2404
    /**
2405
     * 设置数据字段获取器
2406
     * @access public
2407
     * @param  array    $attrs       字段获取器
2408
     * @return $this
2409
     */
2410
    public function withAttrs(array $attrs)
2411
    {
2412
        $this->options['with_attr'] = $attrs;
2413
2414
        return $this;
2415
    }
2416
2417
    /**
2418
     * 使用搜索器条件搜索字段
2419
     * @access public
2420
     * @param  array    $fields     搜索字段
2421
     * @param  array    $data       搜索数据
2422
     * @param  string   $prefix     字段前缀标识
2423
     * @return $this
2424
     */
2425
    public function withSearch(array $fields, array $data = [], string $prefix = '')
2426
    {
2427
        foreach ($fields as $key => $field) {
2428
            if ($field instanceof Closure) {
2429
                $field($this, $data[$key] ?? null, $data, $prefix);
2430
            } elseif ($this->model) {
2431
                // 检测搜索器
2432
                $fieldName = is_numeric($key) ? $field : $key;
2433
                $method    = 'search' . App::parseName($fieldName, 1) . 'Attr';
2434
2435
                if (method_exists($this->model, $method)) {
2436
                    $this->model->$method($this, $data[$field] ?? null, $data, $prefix);
2437
                }
2438
            }
2439
        }
2440
2441
        return $this;
2442
    }
2443
2444
    /**
2445
     * 关联统计
2446
     * @access protected
2447
     * @param  array|string $relations 关联方法名
2448
     * @param  string       $aggregate 聚合查询方法
2449
     * @param  string       $field 字段
2450
     * @param  bool         $subQuery 是否使用子查询
2451
     * @return $this
2452
     */
2453
    protected function withAggregate($relations, string $aggregate = 'count', $field = '*', bool $subQuery = true)
2454
    {
2455
        if (is_string($relations)) {
2456
            $relations = explode(',', $relations);
2457
        }
2458
2459
        if (!$subQuery) {
2460
            $this->options['with_count'][] = [$relations, $aggregate, $field];
2461
        } else {
2462
            if (!isset($this->options['field'])) {
2463
                $this->field('*');
2464
            }
2465
2466
            foreach ($relations as $key => $relation) {
2467
                $closure = $aggregateField = null;
2468
2469
                if ($relation instanceof Closure) {
2470
                    $closure  = $relation;
2471
                    $relation = $key;
2472
                } elseif (!is_int($key)) {
2473
                    $aggregateField = $relation;
2474
                    $relation       = $key;
2475
                }
2476
2477
                $relation = App::parseName($relation, 1, false);
2478
2479
                $count = '(' . $this->model->$relation()->getRelationCountQuery($closure, $aggregate, $field, $aggregateField) . ')';
2480
2481
                if (empty($aggregateField)) {
2482
                    $aggregateField = App::parseName($relation) . '_' . $aggregate;
2483
                }
2484
2485
                $this->field([$count => $aggregateField]);
2486
            }
2487
        }
2488
2489
        return $this;
2490
    }
2491
2492
    /**
2493
     * 关联统计
2494
     * @access public
2495
     * @param  string|array $relation 关联方法名
2496
     * @param  bool         $subQuery 是否使用子查询
2497
     * @return $this
2498
     */
2499
    public function withCount($relation, bool $subQuery = true)
2500
    {
2501
        return $this->withAggregate($relation, 'count', '*', $subQuery);
2502
    }
2503
2504
    /**
2505
     * 关联统计Sum
2506
     * @access public
2507
     * @param  string|array $relation 关联方法名
2508
     * @param  string       $field 字段
2509
     * @param  bool         $subQuery 是否使用子查询
2510
     * @return $this
2511
     */
2512
    public function withSum($relation, string $field, bool $subQuery = true)
2513
    {
2514
        return $this->withAggregate($relation, 'sum', $field, $subQuery);
2515
    }
2516
2517
    /**
2518
     * 关联统计Max
2519
     * @access public
2520
     * @param  string|array $relation 关联方法名
2521
     * @param  string       $field 字段
2522
     * @param  bool         $subQuery 是否使用子查询
2523
     * @return $this
2524
     */
2525
    public function withMax($relation, string $field, bool $subQuery = true)
2526
    {
2527
        return $this->withAggregate($relation, 'max', $field, $subQuery);
2528
    }
2529
2530
    /**
2531
     * 关联统计Min
2532
     * @access public
2533
     * @param  string|array $relation 关联方法名
2534
     * @param  string       $field 字段
2535
     * @param  bool         $subQuery 是否使用子查询
2536
     * @return $this
2537
     */
2538
    public function withMin($relation, string $field, bool $subQuery = true)
2539
    {
2540
        return $this->withAggregate($relation, 'min', $field, $subQuery);
2541
    }
2542
2543
    /**
2544
     * 关联统计Avg
2545
     * @access public
2546
     * @param  string|array $relation 关联方法名
2547
     * @param  string       $field 字段
2548
     * @param  bool         $subQuery 是否使用子查询
2549
     * @return $this
2550
     */
2551
    public function withAvg($relation, string $field, bool $subQuery = true)
2552
    {
2553
        return $this->withAggregate($relation, 'avg', $field, $subQuery);
2554
    }
2555
2556
    /**
2557
     * 关联预加载中 获取关联指定字段值
2558
     * example:
2559
     * Model::with(['relation' => function($query){
2560
     *     $query->withField("id,name");
2561
     * }])
2562
     *
2563
     * @access public
2564
     * @param  string | array $field 指定获取的字段
2565
     * @return $this
2566
     */
2567
    public function withField($field)
2568
    {
2569
        $this->options['with_field'] = $field;
2570
2571
        return $this;
2572
    }
2573
2574
    /**
2575
     * 设置当前字段添加的表别名
2576
     * @access public
2577
     * @param  string $via
2578
     * @return $this
2579
     */
2580
    public function via(string $via = '')
2581
    {
2582
        $this->options['via'] = $via;
2583
2584
        return $this;
2585
    }
2586
2587
    /**
2588
     * 插入记录
2589
     * @access public
2590
     * @param  array   $data         数据
2591
     * @param  boolean $replace      是否replace
2592
     * @param  boolean $getLastInsID 返回自增主键
2593
     * @param  string  $sequence     自增序列名
2594
     * @return integer
2595
     */
2596
    public function insert(array $data = [], bool $replace = false, bool $getLastInsID = false, string $sequence = null)
2597
    {
2598
        $this->options['data'] = array_merge($this->options['data'] ?? [], $data);
2599
2600
        return $this->connection->insert($this, $replace, $getLastInsID, $sequence);
2601
    }
2602
2603
    /**
2604
     * 插入记录并获取自增ID
2605
     * @access public
2606
     * @param  array   $data     数据
2607
     * @param  boolean $replace  是否replace
2608
     * @param  string  $sequence 自增序列名
2609
     * @return integer|string
2610
     */
2611
    public function insertGetId(array $data, bool $replace = false, string $sequence = null)
2612
    {
2613
        return $this->insert($data, $replace, true, $sequence);
2614
    }
2615
2616
    /**
2617
     * 批量插入记录
2618
     * @access public
2619
     * @param  array     $dataSet 数据集
2620
     * @param  boolean   $replace 是否replace
2621
     * @param  integer   $limit   每次写入数据限制
2622
     * @return integer
2623
     */
2624
    public function insertAll(array $dataSet = [], bool $replace = false, int $limit = 0): int
2625
    {
2626
        if (empty($dataSet)) {
2627
            $dataSet = $this->options['data'] ?? [];
2628
        }
2629
2630
        if (empty($limit) && !empty($this->options['limit']) && is_numeric($this->options['limit'])) {
2631
            $limit = (int) $this->options['limit'];
2632
        }
2633
2634
        return $this->connection->insertAll($this, $dataSet, $replace, $limit);
2635
    }
2636
2637
    /**
2638
     * 通过Select方式插入记录
2639
     * @access public
2640
     * @param  array    $fields 要插入的数据表字段名
2641
     * @param  string   $table  要插入的数据表名
2642
     * @return integer
2643
     * @throws PDOException
2644
     */
2645
    public function selectInsert(array $fields, string $table): int
2646
    {
2647
        return $this->connection->selectInsert($this, $fields, $table);
2648
    }
2649
2650
    /**
2651
     * 更新记录
2652
     * @access public
2653
     * @param  mixed $data 数据
2654
     * @return integer
2655
     * @throws Exception
2656
     * @throws PDOException
2657
     */
2658
    public function update(array $data = []): int
2659
    {
2660
        $data = array_merge($this->options['data'] ?? [], $data);
2661
2662
        if (empty($this->options['where'])) {
2663
            $this->parseUpdateData($data);
2664
        }
2665
2666
        if (empty($this->options['where']) && $this->model) {
2667
            $this->where($this->model->getWhere());
2668
        }
2669
2670
        if (empty($this->options['where'])) {
2671
            // 如果没有任何更新条件则不执行
2672
            throw new Exception('miss update condition');
2673
        }
2674
2675
        $this->options['data'] = $data;
2676
2677
        return $this->connection->update($this);
2678
    }
2679
2680
    /**
2681
     * 删除记录
2682
     * @access public
2683
     * @param  mixed $data 表达式 true 表示强制删除
2684
     * @return int
2685
     * @throws Exception
2686
     * @throws PDOException
2687
     */
2688
    public function delete($data = null): int
2689
    {
2690
        if (!is_null($data) && true !== $data) {
2691
            // AR模式分析主键条件
2692
            $this->parsePkWhere($data);
2693
        }
2694
2695
        if (empty($this->options['where']) && $this->model) {
2696
            $this->where($this->model->getWhere());
2697
        }
2698
2699
        if (true !== $data && empty($this->options['where'])) {
2700
            // 如果条件为空 不进行删除操作 除非设置 1=1
2701
            throw new Exception('delete without condition');
2702
        }
2703
2704
        if (!empty($this->options['soft_delete'])) {
2705
            // 软删除
2706
            list($field, $condition) = $this->options['soft_delete'];
2707
            if ($condition) {
2708
                unset($this->options['soft_delete']);
2709
                $this->options['data'] = [$field => $condition];
2710
2711
                return $this->connection->update($this);
2712
            }
2713
        }
2714
2715
        $this->options['data'] = $data;
2716
2717
        return $this->connection->delete($this);
2718
    }
2719
2720
    /**
2721
     * 执行查询但只返回PDOStatement对象
2722
     * @access public
2723
     * @return PDOStatement
2724
     */
2725
    public function getPdo(): PDOStatement
2726
    {
2727
        return $this->connection->pdo($this);
2728
    }
2729
2730
    /**
2731
     * 使用游标查找记录
2732
     * @access public
2733
     * @param  mixed $data
2734
     * @return \Generator
2735
     */
2736
    public function cursor($data = null)
2737
    {
2738
        if (!is_null($data)) {
2739
            // 主键条件分析
2740
            $this->parsePkWhere($data);
2741
        }
2742
2743
        $this->options['data'] = $data;
2744
2745
        $connection = clone $this->connection;
2746
2747
        return $connection->cursor($this);
2748
    }
2749
2750
    /**
2751
     * 查找记录
2752
     * @access public
2753
     * @param  mixed $data
2754
     * @return Collection|array|ModelCollection
2755
     * @throws DbException
2756
     * @throws ModelNotFoundException
2757
     * @throws DataNotFoundException
2758
     */
2759
    public function select($data = null)
2760
    {
2761
        if (!is_null($data)) {
2762
            // 主键条件分析
2763
            $this->parsePkWhere($data);
2764
        }
2765
2766
        $resultSet = $this->connection->select($this);
2767
2768
        // 返回结果处理
2769
        if (!empty($this->options['fail']) && count($resultSet) == 0) {
2770
            $this->throwNotFound();
2771
        }
2772
2773
        // 数据列表读取后的处理
2774
        if (!empty($this->model) && empty($this->options['array'])) {
2775
            // 生成模型对象
2776
            $resultSet = $this->resultSetToModelCollection($resultSet);
2777
        } else {
2778
            $this->resultSet($resultSet);
2779
        }
2780
2781
        return $resultSet;
2782
    }
2783
2784
    /**
2785
     * 查询数据转换为模型数据集对象
2786
     * @access protected
2787
     * @param  array  $resultSet         数据集
2788
     * @return ModelCollection
2789
     */
2790
    protected function resultSetToModelCollection(array $resultSet): ModelCollection
2791
    {
2792
        if (!empty($this->options['collection']) && is_string($this->options['collection'])) {
2793
            $collection = $this->options['collection'];
2794
        }
2795
2796
        if (empty($resultSet)) {
2797
            return $this->model->toCollection([], $collection ?? null);
2798
        }
2799
2800
        // 检查动态获取器
2801
        if (!empty($this->options['with_attr'])) {
2802
            foreach ($this->options['with_attr'] as $name => $val) {
2803
                if (strpos($name, '.')) {
2804
                    list($relation, $field) = explode('.', $name);
2805
2806
                    $withRelationAttr[$relation][$field] = $val;
2807
                    unset($this->options['with_attr'][$name]);
2808
                }
2809
            }
2810
        }
2811
2812
        $withRelationAttr = $withRelationAttr ?? [];
2813
2814
        foreach ($resultSet as $key => &$result) {
2815
            // 数据转换为模型对象
2816
            $this->resultToModel($result, $this->options, true, $withRelationAttr);
2817
        }
2818
2819
        if (!empty($this->options['with'])) {
2820
            // 预载入
2821
            $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 2814. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2822
        }
2823
2824
        if (!empty($this->options['with_join'])) {
2825
            // 预载入
2826
            $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, $this->options['with_bind'] ?? [], true);
2827
        }
2828
2829
        // 模型数据集转换
2830
        return $this->model->toCollection($resultSet, $collection ?? null);
2831
    }
2832
2833
    /**
2834
     * 处理数据集
2835
     * @access public
2836
     * @param  array $resultSet
2837
     * @return void
2838
     */
2839
    protected function resultSet(array &$resultSet): void
2840
    {
2841
        if (!empty($this->options['json'])) {
2842
            foreach ($resultSet as &$result) {
2843
                $this->jsonResult($result, $this->options['json'], true);
2844
            }
2845
        }
2846
2847
        if (!empty($this->options['with_attr'])) {
2848
            foreach ($resultSet as &$result) {
2849
                $this->getResultAttr($result, $this->options['with_attr']);
2850
            }
2851
        }
2852
2853
        if (!empty($this->options['visible']) || !empty($this->options['hidden'])) {
2854
            foreach ($resultSet as &$result) {
2855
                $this->filterResult($result);
2856
            }
2857
        }
2858
2859
        if (!empty($this->options['collection'])) {
2860
            // 返回Collection对象
2861
            $resultSet = new Collection($resultSet);
2862
        }
2863
    }
2864
2865
    /**
2866
     * 查找单条记录
2867
     * @access public
2868
     * @param  mixed $data
2869
     * @return array|Model|null
2870
     * @throws DbException
2871
     * @throws ModelNotFoundException
2872
     * @throws DataNotFoundException
2873
     */
2874
    public function find($data = null)
2875
    {
2876
        if (!is_null($data)) {
2877
            // AR模式分析主键条件
2878
            $this->parsePkWhere($data);
2879
        }
2880
2881
        $result = $this->connection->find($this);
2882
2883
        // 数据处理
2884
        if (empty($result)) {
2885
            return $this->resultToEmpty();
2886
        }
2887
2888
        if (!empty($this->model) && empty($this->options['array'])) {
2889
            // 返回模型对象
2890
            $this->resultToModel($result, $this->options);
2891
        } else {
2892
            $this->result($result);
2893
        }
2894
2895
        return $result;
2896
    }
2897
2898
    /**
2899
     * 查找单条记录 不存在返回空数据(或者空模型)
2900
     * @access public
2901
     * @param  mixed $data
2902
     * @return array|Model
2903
     */
2904
    public function findOrEmpty($data = null)
2905
    {
2906
        return $this->allowEmpty(true)->find($data);
2907
    }
2908
2909
    /**
2910
     * 处理空数据
2911
     * @access protected
2912
     * @return array|Model|null
2913
     * @throws DbException
2914
     * @throws ModelNotFoundException
2915
     * @throws DataNotFoundException
2916
     */
2917
    protected function resultToEmpty()
2918
    {
2919
        if (!empty($this->options['fail'])) {
2920
            $this->throwNotFound();
2921
        } elseif (!empty($this->options['allow_empty'])) {
2922
            return !empty($this->model) && empty($this->options['array']) ? $this->model->newInstance([], true) : [];
2923
        }
2924
    }
2925
2926
    /**
2927
     * 获取模型的更新条件
2928
     * @access protected
2929
     * @param  array $options 查询参数
2930
     */
2931
    protected function getModelUpdateCondition(array $options)
2932
    {
2933
        return $options['where']['AND'] ?? null;
2934
    }
2935
2936
    /**
2937
     * 处理数据
2938
     * @access protected
2939
     * @param  array $result     查询数据
2940
     * @return void
2941
     */
2942
    protected function result(array &$result): void
2943
    {
2944
        if (!empty($this->options['json'])) {
2945
            $this->jsonResult($result, $this->options['json'], true);
2946
        }
2947
2948
        if (!empty($this->options['with_attr'])) {
2949
            $this->getResultAttr($result, $this->options['with_attr']);
2950
        }
2951
2952
        $this->filterResult($result);
2953
    }
2954
2955
    /**
2956
     * 处理数据的可见和隐藏
2957
     * @access protected
2958
     * @param  array $result     查询数据
2959
     * @return void
2960
     */
2961
    protected function filterResult(&$result): void
2962
    {
2963
        if (!empty($this->options['visible'])) {
2964
            foreach ($this->options['visible'] as $key) {
2965
                $array[] = $key;
2966
            }
2967
            $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 2964. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2968
        } elseif (!empty($this->options['hidden'])) {
2969
            foreach ($this->options['hidden'] as $key) {
2970
                $array[] = $key;
2971
            }
2972
            $result = array_diff_key($result, array_flip($array));
2973
        }
2974
    }
2975
2976
    /**
2977
     * 使用获取器处理数据
2978
     * @access protected
2979
     * @param  array $result     查询数据
2980
     * @param  array $withAttr   字段获取器
2981
     * @return void
2982
     */
2983
    protected function getResultAttr(array &$result, array $withAttr = []): void
2984
    {
2985
        foreach ($withAttr as $name => $closure) {
2986
            $name = App::parseName($name);
2987
2988
            if (strpos($name, '.')) {
2989
                // 支持JSON字段 获取器定义
2990
                list($key, $field) = explode('.', $name);
2991
2992
                if (isset($result[$key])) {
2993
                    $result[$key][$field] = $closure($result[$key][$field] ?? null, $result[$key]);
2994
                }
2995
            } else {
2996
                $result[$name] = $closure($result[$name] ?? null, $result);
2997
            }
2998
        }
2999
    }
3000
3001
    /**
3002
     * JSON字段数据转换
3003
     * @access protected
3004
     * @param  array $result            查询数据
3005
     * @param  array $json              JSON字段
3006
     * @param  bool  $assoc             是否转换为数组
3007
     * @param  array $withRelationAttr  关联获取器
3008
     * @return void
3009
     */
3010
    protected function jsonResult(array &$result, array $json = [], bool $assoc = false, array $withRelationAttr = []): void
3011
    {
3012
        foreach ($json as $name) {
3013
            if (!isset($result[$name])) {
3014
                continue;
3015
            }
3016
3017
            $result[$name] = json_decode($result[$name], $assoc);
3018
3019
            if (!isset($withRelationAttr[$name])) {
3020
                continue;
3021
            }
3022
3023
            foreach ($withRelationAttr[$name] as $key => $closure) {
3024
                $data = get_object_vars($result[$name]);
3025
3026
                $result[$name]->$key = $closure($result[$name]->$key ?? null, $data);
3027
            }
3028
        }
3029
    }
3030
3031
    /**
3032
     * 查询数据转换为模型对象
3033
     * @access protected
3034
     * @param  array $result            查询数据
3035
     * @param  array $options           查询参数
3036
     * @param  bool  $resultSet         是否为数据集查询
3037
     * @param  array $withRelationAttr  关联字段获取器
3038
     * @return void
3039
     */
3040
    protected function resultToModel(array &$result, array $options = [], bool $resultSet = false, array $withRelationAttr = []): void
3041
    {
3042
        // 动态获取器
3043
        if (!empty($options['with_attr']) && empty($withRelationAttr)) {
3044
            foreach ($options['with_attr'] as $name => $val) {
3045
                if (strpos($name, '.')) {
3046
                    list($relation, $field) = explode('.', $name);
3047
3048
                    $withRelationAttr[$relation][$field] = $val;
3049
                    unset($options['with_attr'][$name]);
3050
                }
3051
            }
3052
        }
3053
3054
        // JSON 数据处理
3055
        if (!empty($options['json'])) {
3056
            $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr);
3057
        }
3058
3059
        $result = $this->model->newInstance($result, true, $resultSet ? null : $this->getModelUpdateCondition($options));
3060
3061
        // 动态获取器
3062
        if (!empty($options['with_attr'])) {
3063
            $result->withAttribute($options['with_attr']);
3064
        }
3065
3066
        // 输出属性控制
3067
        if (!empty($options['visible'])) {
3068
            $result->visible($options['visible']);
3069
        } elseif (!empty($options['hidden'])) {
3070
            $result->hidden($options['hidden']);
3071
        }
3072
3073
        if (!empty($options['append'])) {
3074
            $result->append($options['append']);
3075
        }
3076
3077
        // 关联查询
3078
        if (!empty($options['relation'])) {
3079
            $result->relationQuery($options['relation']);
3080
        }
3081
3082
        // 预载入查询
3083
        if (!$resultSet && !empty($options['with'])) {
3084
            $result->eagerlyResult($result, $options['with'], $withRelationAttr, $this->options['with_bind'] ?? []);
3085
        }
3086
3087
        // JOIN预载入查询
3088
        if (!$resultSet && !empty($options['with_join'])) {
3089
            $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, $this->options['with_bind'] ?? [], true);
3090
        }
3091
3092
        // 关联统计
3093
        if (!empty($options['with_count'])) {
3094
            foreach ($options['with_count'] as $val) {
3095
                $result->relationCount($result, $val[0], $val[1], $val[2]);
3096
            }
3097
        }
3098
    }
3099
3100
    /**
3101
     * 查询失败 抛出异常
3102
     * @access protected
3103
     * @return void
3104
     * @throws ModelNotFoundException
3105
     * @throws DataNotFoundException
3106
     */
3107
    protected function throwNotFound(): void
3108
    {
3109
        if (!empty($this->model)) {
3110
            $class = get_class($this->model);
3111
            throw new ModelNotFoundException('model data Not Found:' . $class, $class, $this->options);
3112
        }
3113
3114
        $table = $this->getTable();
3115
        throw new DataNotFoundException('table data not Found:' . $table, $table, $this->options);
3116
    }
3117
3118
    /**
3119
     * 查找多条记录 如果不存在则抛出异常
3120
     * @access public
3121
     * @param  array|string|Query|Closure $data
3122
     * @return array|PDOStatement|string|Model
3123
     * @throws DbException
3124
     * @throws ModelNotFoundException
3125
     * @throws DataNotFoundException
3126
     */
3127
    public function selectOrFail($data = null)
3128
    {
3129
        return $this->failException(true)->select($data);
3130
    }
3131
3132
    /**
3133
     * 查找单条记录 如果不存在则抛出异常
3134
     * @access public
3135
     * @param  array|string|Query|Closure $data
3136
     * @return array|PDOStatement|string|Model
3137
     * @throws DbException
3138
     * @throws ModelNotFoundException
3139
     * @throws DataNotFoundException
3140
     */
3141
    public function findOrFail($data = null)
3142
    {
3143
        return $this->failException(true)->find($data);
3144
    }
3145
3146
    /**
3147
     * 分批数据返回处理
3148
     * @access public
3149
     * @param  integer      $count    每次处理的数据数量
3150
     * @param  callable     $callback 处理回调方法
3151
     * @param  string|array $column   分批处理的字段名
3152
     * @param  string       $order    字段排序
3153
     * @return bool
3154
     * @throws DbException
3155
     */
3156
    public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool
3157
    {
3158
        $options = $this->getOptions();
3159
        $column  = $column ?: $this->getPk();
3160
3161
        if (isset($options['order'])) {
3162
            if (Container::pull('app')->isDebug()) {
3163
                throw new DbException('chunk not support call order');
3164
            }
3165
            unset($options['order']);
3166
        }
3167
3168
        $bind = $this->bind;
3169
3170
        if (is_array($column)) {
3171
            $times = 1;
3172
            $query = $this->options($options)->page($times, $count);
3173
        } else {
3174
            $query = $this->options($options)->limit($count);
3175
3176
            if (strpos($column, '.')) {
3177
                list($alias, $key) = explode('.', $column);
3178
            } else {
3179
                $key = $column;
3180
            }
3181
        }
3182
3183
        $resultSet = $query->order($column, $order)->select();
3184
3185
        while (count($resultSet) > 0) {
3186
            if ($resultSet instanceof Collection) {
3187
                $resultSet = $resultSet->all();
3188
            }
3189
3190
            if (false === call_user_func($callback, $resultSet)) {
3191
                return false;
3192
            }
3193
3194
            if (isset($times)) {
3195
                $times++;
3196
                $query = $this->options($options)->page($times, $count);
3197
            } else {
3198
                $end    = end($resultSet);
3199
                $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...
3200
3201
                $query = $this->options($options)
3202
                    ->limit($count)
3203
                    ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
3204
            }
3205
3206
            $resultSet = $query->bind($bind)->order($column, $order)->select();
3207
        }
3208
3209
        return true;
3210
    }
3211
3212
    /**
3213
     * 获取绑定的参数 并清空
3214
     * @access public
3215
     * @param  bool $clear
3216
     * @return array
3217
     */
3218
    public function getBind(bool $clear = true): array
3219
    {
3220
        $bind = $this->bind;
3221
        if ($clear) {
3222
            $this->bind = [];
3223
        }
3224
3225
        return $bind;
3226
    }
3227
3228
    /**
3229
     * 创建子查询SQL
3230
     * @access public
3231
     * @param  bool $sub
3232
     * @return string
3233
     * @throws DbException
3234
     */
3235
    public function buildSql(bool $sub = true): string
3236
    {
3237
        return $sub ? '( ' . $this->fetchSql()->select() . ' )' : $this->fetchSql()->select();
3238
    }
3239
3240
    /**
3241
     * 视图查询处理
3242
     * @access protected
3243
     * @param  array   $options    查询参数
3244
     * @return void
3245
     */
3246
    protected function parseView(array &$options): void
3247
    {
3248
        foreach (['AND', 'OR'] as $logic) {
3249
            if (isset($options['where'][$logic])) {
3250
                foreach ($options['where'][$logic] as $key => $val) {
3251
                    if (array_key_exists($key, $options['map'])) {
3252
                        array_shift($val);
3253
                        array_unshift($val, $options['map'][$key]);
3254
                        $options['where'][$logic][$options['map'][$key]] = $val;
3255
                        unset($options['where'][$logic][$key]);
3256
                    }
3257
                }
3258
            }
3259
        }
3260
3261
        if (isset($options['order'])) {
3262
            // 视图查询排序处理
3263
            foreach ($options['order'] as $key => $val) {
3264
                if (is_numeric($key) && is_string($val)) {
3265
                    if (strpos($val, ' ')) {
3266
                        list($field, $sort) = explode(' ', $val);
3267
                        if (array_key_exists($field, $options['map'])) {
3268
                            $options['order'][$options['map'][$field]] = $sort;
3269
                            unset($options['order'][$key]);
3270
                        }
3271
                    } elseif (array_key_exists($val, $options['map'])) {
3272
                        $options['order'][$options['map'][$val]] = 'asc';
3273
                        unset($options['order'][$key]);
3274
                    }
3275
                } elseif (array_key_exists($key, $options['map'])) {
3276
                    $options['order'][$options['map'][$key]] = $val;
3277
                    unset($options['order'][$key]);
3278
                }
3279
            }
3280
        }
3281
    }
3282
3283
    protected function parseUpdateData(&$data)
3284
    {
3285
        $pk = $this->getPk();
3286
        // 如果存在主键数据 则自动作为更新条件
3287
        if (is_string($pk) && isset($data[$pk])) {
3288
            $this->where($pk, '=', $data[$pk]);
3289
            $this->options['key'] = $data[$pk];
3290
            unset($data[$pk]);
3291
        } elseif (is_array($pk)) {
3292
            // 增加复合主键支持
3293
            foreach ($pk as $field) {
3294
                if (isset($data[$field])) {
3295
                    $this->where($field, '=', $data[$field]);
3296
                } else {
3297
                    // 如果缺少复合主键数据则不执行
3298
                    throw new Exception('miss complex primary data');
3299
                }
3300
                unset($data[$field]);
3301
            }
3302
        }
3303
    }
3304
3305
    /**
3306
     * 把主键值转换为查询条件 支持复合主键
3307
     * @access public
3308
     * @param  array|string $data    主键数据
3309
     * @return void
3310
     * @throws Exception
3311
     */
3312
    public function parsePkWhere($data): void
3313
    {
3314
        $pk = $this->getPk();
3315
3316
        if (is_string($pk)) {
3317
            // 获取数据表
3318
            if (empty($this->options['table'])) {
3319
                $this->options['table'] = $this->getTable();
3320
            }
3321
3322
            $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table'];
3323
3324
            if (!empty($this->options['alias'][$table])) {
3325
                $alias = $this->options['alias'][$table];
3326
            }
3327
3328
            $key = isset($alias) ? $alias . '.' . $pk : $pk;
3329
            // 根据主键查询
3330
            if (is_array($data)) {
3331
                $this->where($key, 'in', $data);
3332
            } else {
3333
                $this->where($key, '=', $data);
3334
                $this->options['key'] = $data;
3335
            }
3336
        }
3337
    }
3338
3339
    /**
3340
     * 分析表达式(可用于查询或者写入操作)
3341
     * @access public
3342
     * @return array
3343
     */
3344
    public function parseOptions(): array
3345
    {
3346
        $options = $this->getOptions();
3347
3348
        // 获取数据表
3349
        if (empty($options['table'])) {
3350
            $options['table'] = $this->getTable();
3351
        }
3352
3353
        if (!isset($options['where'])) {
3354
            $options['where'] = [];
3355
        } elseif (isset($options['view'])) {
3356
            // 视图查询条件处理
3357
            $this->parseView($options);
3358
        }
3359
3360
        if (!isset($options['field'])) {
3361
            $options['field'] = '*';
3362
        }
3363
3364
        foreach (['data', 'order', 'join', 'union'] as $name) {
3365
            if (!isset($options[$name])) {
3366
                $options[$name] = [];
3367
            }
3368
        }
3369
3370
        if (!isset($options['strict'])) {
3371
            $options['strict'] = $this->connection->getConfig('fields_strict');
3372
        }
3373
3374
        foreach (['master', 'lock', 'fetch_sql', 'array', 'distinct', 'procedure'] as $name) {
3375
            if (!isset($options[$name])) {
3376
                $options[$name] = false;
3377
            }
3378
        }
3379
3380
        foreach (['group', 'having', 'limit', 'force', 'comment', 'partition', 'duplicate', 'extra'] as $name) {
3381
            if (!isset($options[$name])) {
3382
                $options[$name] = '';
3383
            }
3384
        }
3385
3386
        if (isset($options['page'])) {
3387
            // 根据页数计算limit
3388
            list($page, $listRows) = $options['page'];
3389
            $page                  = $page > 0 ? $page : 1;
3390
            $listRows              = $listRows ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20);
3391
            $offset                = $listRows * ($page - 1);
3392
            $options['limit']      = $offset . ',' . $listRows;
3393
        }
3394
3395
        $this->options = $options;
3396
3397
        return $options;
3398
    }
3399
3400
    /**
3401
     * 注册回调方法
3402
     * @access public
3403
     * @param  string   $event    事件名
3404
     * @param  callable $callback 回调方法
3405
     * @return void
3406
     */
3407
    public static function event(string $event, callable $callback): void
3408
    {
3409
        self::$event[$event] = $callback;
3410
    }
3411
3412
    /**
3413
     * 触发事件
3414
     * @access public
3415
     * @param  string $event   事件名
3416
     * @return mixed
3417
     */
3418
    public function trigger(string $event)
3419
    {
3420
        $result = false;
3421
3422
        if (isset(self::$event[$event])) {
3423
            $result = Container::getInstance()->invoke(self::$event[$event], [$this]);
3424
        }
3425
3426
        return $result;
3427
    }
3428
3429
}
3430