Passed
Push — 5.2 ( 61517e...2a99ba )
by liu
02:49
created

Query::withBind()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 5
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
     * 关联预载入 JOIN方式
2326
     * @access protected
2327
     * @param  array        $with 关联方法名
2328
     * @param  string       $joinType JOIN方式
2329
     * @return $this
2330
     */
2331
    public function withJoin(array $with, string $joinType = '')
2332
    {
2333
        if (empty($with)) {
2334
            return $this;
2335
        }
2336
2337
        $first = true;
2338
2339
        /** @var Model $class */
2340
        $class = $this->model;
2341
        foreach ($with as $key => $relation) {
2342
            $closure = null;
2343
            $field   = true;
2344
2345
            if ($relation instanceof Closure) {
2346
                // 支持闭包查询过滤关联条件
2347
                $closure  = $relation;
2348
                $relation = $key;
2349
            } elseif (is_array($relation)) {
2350
                $field    = $relation;
2351
                $relation = $key;
2352
            } elseif (is_string($relation) && strpos($relation, '.')) {
2353
                $relation = strstr($relation, '.', true);
2354
            }
2355
2356
            /** @var Relation $model */
2357
            $relation = App::parseName($relation, 1, false);
2358
            $model    = $class->$relation();
2359
2360
            if ($model instanceof OneToOne) {
2361
                $model->eagerly($this, $relation, $field, $joinType, $closure, $first);
2362
                $first = false;
2363
            } else {
2364
                // 不支持其它关联
2365
                unset($with[$key]);
2366
            }
2367
        }
2368
2369
        $this->via();
2370
2371
        $this->options['with_join'] = $with;
2372
2373
        return $this;
2374
    }
2375
2376
    /**
2377
     * 设置数据字段获取器
2378
     * @access public
2379
     * @param  string       $name       字段名
2380
     * @param  callable     $callback   闭包获取器
2381
     * @return $this
2382
     */
2383
    public function withAttr(string $name, callable $callback)
2384
    {
2385
        $this->options['with_attr'][$name] = $callback;
2386
2387
        return $this;
2388
    }
2389
2390
    /**
2391
     * 设置数据字段获取器
2392
     * @access public
2393
     * @param  array    $attrs       字段获取器
2394
     * @return $this
2395
     */
2396
    public function withAttrs(array $attrs)
2397
    {
2398
        $this->options['with_attr'] = $attrs;
2399
2400
        return $this;
2401
    }
2402
2403
    /**
2404
     * 使用搜索器条件搜索字段
2405
     * @access public
2406
     * @param  array    $fields     搜索字段
2407
     * @param  array    $data       搜索数据
2408
     * @param  string   $prefix     字段前缀标识
2409
     * @return $this
2410
     */
2411
    public function withSearch(array $fields, array $data = [], string $prefix = '')
2412
    {
2413
        foreach ($fields as $key => $field) {
2414
            if ($field instanceof Closure) {
2415
                $field($this, $data[$key] ?? null, $data, $prefix);
2416
            } elseif ($this->model) {
2417
                // 检测搜索器
2418
                $fieldName = is_numeric($key) ? $field : $key;
2419
                $method    = 'search' . App::parseName($fieldName, 1) . 'Attr';
2420
2421
                if (method_exists($this->model, $method)) {
2422
                    $this->model->$method($this, $data[$field] ?? null, $data, $prefix);
2423
                }
2424
            }
2425
        }
2426
2427
        return $this;
2428
    }
2429
2430
    /**
2431
     * 关联统计
2432
     * @access protected
2433
     * @param  array|string $relations 关联方法名
2434
     * @param  string       $aggregate 聚合查询方法
2435
     * @param  string       $field 字段
2436
     * @param  bool         $subQuery 是否使用子查询
2437
     * @return $this
2438
     */
2439
    protected function withAggregate($relations, string $aggregate = 'count', $field = '*', bool $subQuery = true)
2440
    {
2441
        if (is_string($relations)) {
2442
            $relations = explode(',', $relations);
2443
        }
2444
2445
        if (!$subQuery) {
2446
            $this->options['with_count'][] = [$relations, $aggregate, $field];
2447
        } else {
2448
            if (!isset($this->options['field'])) {
2449
                $this->field('*');
2450
            }
2451
2452
            foreach ($relations as $key => $relation) {
2453
                $closure = $aggregateField = null;
2454
2455
                if ($relation instanceof Closure) {
2456
                    $closure  = $relation;
2457
                    $relation = $key;
2458
                } elseif (!is_int($key)) {
2459
                    $aggregateField = $relation;
2460
                    $relation       = $key;
2461
                }
2462
2463
                $relation = App::parseName($relation, 1, false);
2464
2465
                $count = '(' . $this->model->$relation()->getRelationCountQuery($closure, $aggregate, $field, $aggregateField) . ')';
2466
2467
                if (empty($aggregateField)) {
2468
                    $aggregateField = App::parseName($relation) . '_' . $aggregate;
2469
                }
2470
2471
                $this->field([$count => $aggregateField]);
2472
            }
2473
        }
2474
2475
        return $this;
2476
    }
2477
2478
    /**
2479
     * 关联统计
2480
     * @access public
2481
     * @param  string|array $relation 关联方法名
2482
     * @param  bool         $subQuery 是否使用子查询
2483
     * @return $this
2484
     */
2485
    public function withCount($relation, bool $subQuery = true)
2486
    {
2487
        return $this->withAggregate($relation, 'count', '*', $subQuery);
2488
    }
2489
2490
    /**
2491
     * 关联统计Sum
2492
     * @access public
2493
     * @param  string|array $relation 关联方法名
2494
     * @param  string       $field 字段
2495
     * @param  bool         $subQuery 是否使用子查询
2496
     * @return $this
2497
     */
2498
    public function withSum($relation, string $field, bool $subQuery = true)
2499
    {
2500
        return $this->withAggregate($relation, 'sum', $field, $subQuery);
2501
    }
2502
2503
    /**
2504
     * 关联统计Max
2505
     * @access public
2506
     * @param  string|array $relation 关联方法名
2507
     * @param  string       $field 字段
2508
     * @param  bool         $subQuery 是否使用子查询
2509
     * @return $this
2510
     */
2511
    public function withMax($relation, string $field, bool $subQuery = true)
2512
    {
2513
        return $this->withAggregate($relation, 'max', $field, $subQuery);
2514
    }
2515
2516
    /**
2517
     * 关联统计Min
2518
     * @access public
2519
     * @param  string|array $relation 关联方法名
2520
     * @param  string       $field 字段
2521
     * @param  bool         $subQuery 是否使用子查询
2522
     * @return $this
2523
     */
2524
    public function withMin($relation, string $field, bool $subQuery = true)
2525
    {
2526
        return $this->withAggregate($relation, 'min', $field, $subQuery);
2527
    }
2528
2529
    /**
2530
     * 关联统计Avg
2531
     * @access public
2532
     * @param  string|array $relation 关联方法名
2533
     * @param  string       $field 字段
2534
     * @param  bool         $subQuery 是否使用子查询
2535
     * @return $this
2536
     */
2537
    public function withAvg($relation, string $field, bool $subQuery = true)
2538
    {
2539
        return $this->withAggregate($relation, 'avg', $field, $subQuery);
2540
    }
2541
2542
    /**
2543
     * 关联预加载中 获取关联指定字段值
2544
     * example:
2545
     * Model::with(['relation' => function($query){
2546
     *     $query->withField("id,name");
2547
     * }])
2548
     *
2549
     * @access public
2550
     * @param  string | array $field 指定获取的字段
2551
     * @return $this
2552
     */
2553
    public function withField($field)
2554
    {
2555
        $this->options['with_field'] = $field;
2556
2557
        return $this;
2558
    }
2559
2560
    /**
2561
     * 设置当前字段添加的表别名
2562
     * @access public
2563
     * @param  string $via
2564
     * @return $this
2565
     */
2566
    public function via(string $via = '')
2567
    {
2568
        $this->options['via'] = $via;
2569
2570
        return $this;
2571
    }
2572
2573
    /**
2574
     * 插入记录
2575
     * @access public
2576
     * @param  array   $data         数据
2577
     * @param  boolean $replace      是否replace
2578
     * @param  boolean $getLastInsID 返回自增主键
2579
     * @param  string  $sequence     自增序列名
2580
     * @return integer
2581
     */
2582
    public function insert(array $data = [], bool $replace = false, bool $getLastInsID = false, string $sequence = null)
2583
    {
2584
        $this->options['data'] = array_merge($this->options['data'] ?? [], $data);
2585
2586
        return $this->connection->insert($this, $replace, $getLastInsID, $sequence);
2587
    }
2588
2589
    /**
2590
     * 插入记录并获取自增ID
2591
     * @access public
2592
     * @param  array   $data     数据
2593
     * @param  boolean $replace  是否replace
2594
     * @param  string  $sequence 自增序列名
2595
     * @return integer|string
2596
     */
2597
    public function insertGetId(array $data, bool $replace = false, string $sequence = null)
2598
    {
2599
        return $this->insert($data, $replace, true, $sequence);
2600
    }
2601
2602
    /**
2603
     * 批量插入记录
2604
     * @access public
2605
     * @param  array     $dataSet 数据集
2606
     * @param  boolean   $replace 是否replace
2607
     * @param  integer   $limit   每次写入数据限制
2608
     * @return integer
2609
     */
2610
    public function insertAll(array $dataSet = [], bool $replace = false, int $limit = 0): int
2611
    {
2612
        if (empty($dataSet)) {
2613
            $dataSet = $this->options['data'] ?? [];
2614
        }
2615
2616
        if (empty($limit) && !empty($this->options['limit']) && is_numeric($this->options['limit'])) {
2617
            $limit = (int) $this->options['limit'];
2618
        }
2619
2620
        return $this->connection->insertAll($this, $dataSet, $replace, $limit);
2621
    }
2622
2623
    /**
2624
     * 通过Select方式插入记录
2625
     * @access public
2626
     * @param  array    $fields 要插入的数据表字段名
2627
     * @param  string   $table  要插入的数据表名
2628
     * @return integer
2629
     * @throws PDOException
2630
     */
2631
    public function selectInsert(array $fields, string $table): int
2632
    {
2633
        return $this->connection->selectInsert($this, $fields, $table);
2634
    }
2635
2636
    /**
2637
     * 更新记录
2638
     * @access public
2639
     * @param  mixed $data 数据
2640
     * @return integer
2641
     * @throws Exception
2642
     * @throws PDOException
2643
     */
2644
    public function update(array $data = []): int
2645
    {
2646
        $data = array_merge($this->options['data'] ?? [], $data);
2647
2648
        if (empty($this->options['where'])) {
2649
            $this->parseUpdateData($data);
2650
        }
2651
2652
        if (empty($this->options['where']) && $this->model) {
2653
            $this->where($this->model->getWhere());
2654
        }
2655
2656
        if (empty($this->options['where'])) {
2657
            // 如果没有任何更新条件则不执行
2658
            throw new Exception('miss update condition');
2659
        }
2660
2661
        $this->options['data'] = $data;
2662
2663
        return $this->connection->update($this);
2664
    }
2665
2666
    /**
2667
     * 删除记录
2668
     * @access public
2669
     * @param  mixed $data 表达式 true 表示强制删除
2670
     * @return int
2671
     * @throws Exception
2672
     * @throws PDOException
2673
     */
2674
    public function delete($data = null): int
2675
    {
2676
        if (!is_null($data) && true !== $data) {
2677
            // AR模式分析主键条件
2678
            $this->parsePkWhere($data);
2679
        }
2680
2681
        if (empty($this->options['where']) && $this->model) {
2682
            $this->where($this->model->getWhere());
2683
        }
2684
2685
        if (true !== $data && empty($this->options['where'])) {
2686
            // 如果条件为空 不进行删除操作 除非设置 1=1
2687
            throw new Exception('delete without condition');
2688
        }
2689
2690
        if (!empty($this->options['soft_delete'])) {
2691
            // 软删除
2692
            list($field, $condition) = $this->options['soft_delete'];
2693
            if ($condition) {
2694
                unset($this->options['soft_delete']);
2695
                $this->options['data'] = [$field => $condition];
2696
2697
                return $this->connection->update($this);
2698
            }
2699
        }
2700
2701
        $this->options['data'] = $data;
2702
2703
        return $this->connection->delete($this);
2704
    }
2705
2706
    /**
2707
     * 执行查询但只返回PDOStatement对象
2708
     * @access public
2709
     * @return PDOStatement
2710
     */
2711
    public function getPdo(): PDOStatement
2712
    {
2713
        return $this->connection->pdo($this);
2714
    }
2715
2716
    /**
2717
     * 使用游标查找记录
2718
     * @access public
2719
     * @param  mixed $data
2720
     * @return \Generator
2721
     */
2722
    public function cursor($data = null)
2723
    {
2724
        if (!is_null($data)) {
2725
            // 主键条件分析
2726
            $this->parsePkWhere($data);
2727
        }
2728
2729
        $this->options['data'] = $data;
2730
2731
        $connection = clone $this->connection;
2732
2733
        return $connection->cursor($this);
2734
    }
2735
2736
    /**
2737
     * 查找记录
2738
     * @access public
2739
     * @param  mixed $data
2740
     * @return Collection|array|ModelCollection
2741
     * @throws DbException
2742
     * @throws ModelNotFoundException
2743
     * @throws DataNotFoundException
2744
     */
2745
    public function select($data = null)
2746
    {
2747
        if (!is_null($data)) {
2748
            // 主键条件分析
2749
            $this->parsePkWhere($data);
2750
        }
2751
2752
        $resultSet = $this->connection->select($this);
2753
2754
        // 返回结果处理
2755
        if (!empty($this->options['fail']) && count($resultSet) == 0) {
2756
            $this->throwNotFound();
2757
        }
2758
2759
        // 数据列表读取后的处理
2760
        if (!empty($this->model) && empty($this->options['array'])) {
2761
            // 生成模型对象
2762
            $resultSet = $this->resultSetToModelCollection($resultSet);
2763
        } else {
2764
            $this->resultSet($resultSet);
2765
        }
2766
2767
        return $resultSet;
2768
    }
2769
2770
    /**
2771
     * 查询数据转换为模型数据集对象
2772
     * @access protected
2773
     * @param  array  $resultSet         数据集
2774
     * @return ModelCollection
2775
     */
2776
    protected function resultSetToModelCollection(array $resultSet): ModelCollection
2777
    {
2778
        if (!empty($this->options['collection']) && is_string($this->options['collection'])) {
2779
            $collection = $this->options['collection'];
2780
        }
2781
2782
        if (empty($resultSet)) {
2783
            return $this->model->toCollection([], $collection ?? null);
2784
        }
2785
2786
        // 检查动态获取器
2787
        if (!empty($this->options['with_attr'])) {
2788
            foreach ($this->options['with_attr'] as $name => $val) {
2789
                if (strpos($name, '.')) {
2790
                    list($relation, $field) = explode('.', $name);
2791
2792
                    $withRelationAttr[$relation][$field] = $val;
2793
                    unset($this->options['with_attr'][$name]);
2794
                }
2795
            }
2796
        }
2797
2798
        $withRelationAttr = $withRelationAttr ?? [];
2799
2800
        foreach ($resultSet as $key => &$result) {
2801
            // 数据转换为模型对象
2802
            $this->resultToModel($result, $this->options, true, $withRelationAttr);
2803
        }
2804
2805
        if (!empty($this->options['with'])) {
2806
            // 预载入
2807
            $result->eagerlyResultSet($resultSet, $this->options['with'], $withRelationAttr);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result seems to be defined by a foreach iteration on line 2800. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2808
        }
2809
2810
        if (!empty($this->options['with_join'])) {
2811
            // 预载入
2812
            $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, true);
2813
        }
2814
2815
        // 模型数据集转换
2816
        return $this->model->toCollection($resultSet, $collection ?? null);
2817
    }
2818
2819
    /**
2820
     * 处理数据集
2821
     * @access public
2822
     * @param  array $resultSet
2823
     * @return void
2824
     */
2825
    protected function resultSet(array &$resultSet): void
2826
    {
2827
        if (!empty($this->options['json'])) {
2828
            foreach ($resultSet as &$result) {
2829
                $this->jsonResult($result, $this->options['json'], true);
2830
            }
2831
        }
2832
2833
        if (!empty($this->options['with_attr'])) {
2834
            foreach ($resultSet as &$result) {
2835
                $this->getResultAttr($result, $this->options['with_attr']);
2836
            }
2837
        }
2838
2839
        if (!empty($this->options['visible']) || !empty($this->options['hidden'])) {
2840
            foreach ($resultSet as &$result) {
2841
                $this->filterResult($result);
2842
            }
2843
        }
2844
2845
        if (!empty($this->options['collection'])) {
2846
            // 返回Collection对象
2847
            $resultSet = new Collection($resultSet);
2848
        }
2849
    }
2850
2851
    /**
2852
     * 查找单条记录
2853
     * @access public
2854
     * @param  mixed $data
2855
     * @return array|Model|null
2856
     * @throws DbException
2857
     * @throws ModelNotFoundException
2858
     * @throws DataNotFoundException
2859
     */
2860
    public function find($data = null)
2861
    {
2862
        if (!is_null($data)) {
2863
            // AR模式分析主键条件
2864
            $this->parsePkWhere($data);
2865
        }
2866
2867
        $result = $this->connection->find($this);
2868
2869
        // 数据处理
2870
        if (empty($result)) {
2871
            return $this->resultToEmpty();
2872
        }
2873
2874
        if (!empty($this->model) && empty($this->options['array'])) {
2875
            // 返回模型对象
2876
            $this->resultToModel($result, $this->options);
2877
        } else {
2878
            $this->result($result);
2879
        }
2880
2881
        return $result;
2882
    }
2883
2884
    /**
2885
     * 查找单条记录 不存在返回空数据(或者空模型)
2886
     * @access public
2887
     * @param  mixed $data
2888
     * @return array|Model
2889
     */
2890
    public function findOrEmpty($data = null)
2891
    {
2892
        return $this->allowEmpty(true)->find($data);
2893
    }
2894
2895
    /**
2896
     * 处理空数据
2897
     * @access protected
2898
     * @return array|Model|null
2899
     * @throws DbException
2900
     * @throws ModelNotFoundException
2901
     * @throws DataNotFoundException
2902
     */
2903
    protected function resultToEmpty()
2904
    {
2905
        if (!empty($this->options['fail'])) {
2906
            $this->throwNotFound();
2907
        } elseif (!empty($this->options['allow_empty'])) {
2908
            return !empty($this->model) && empty($this->options['array']) ? $this->model->newInstance([], true) : [];
2909
        }
2910
    }
2911
2912
    /**
2913
     * 获取模型的更新条件
2914
     * @access protected
2915
     * @param  array $options 查询参数
2916
     */
2917
    protected function getModelUpdateCondition(array $options)
2918
    {
2919
        return $options['where']['AND'] ?? null;
2920
    }
2921
2922
    /**
2923
     * 处理数据
2924
     * @access protected
2925
     * @param  array $result     查询数据
2926
     * @return void
2927
     */
2928
    protected function result(array &$result): void
2929
    {
2930
        if (!empty($this->options['json'])) {
2931
            $this->jsonResult($result, $this->options['json'], true);
2932
        }
2933
2934
        if (!empty($this->options['with_attr'])) {
2935
            $this->getResultAttr($result, $this->options['with_attr']);
2936
        }
2937
2938
        $this->filterResult($result);
2939
    }
2940
2941
    /**
2942
     * 处理数据的可见和隐藏
2943
     * @access protected
2944
     * @param  array $result     查询数据
2945
     * @return void
2946
     */
2947
    protected function filterResult(&$result): void
2948
    {
2949
        if (!empty($this->options['visible'])) {
2950
            foreach ($this->options['visible'] as $key) {
2951
                $array[] = $key;
2952
            }
2953
            $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 2950. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2954
        } elseif (!empty($this->options['hidden'])) {
2955
            foreach ($this->options['hidden'] as $key) {
2956
                $array[] = $key;
2957
            }
2958
            $result = array_diff_key($result, array_flip($array));
2959
        }
2960
    }
2961
2962
    /**
2963
     * 使用获取器处理数据
2964
     * @access protected
2965
     * @param  array $result     查询数据
2966
     * @param  array $withAttr   字段获取器
2967
     * @return void
2968
     */
2969
    protected function getResultAttr(array &$result, array $withAttr = []): void
2970
    {
2971
        foreach ($withAttr as $name => $closure) {
2972
            $name = App::parseName($name);
2973
2974
            if (strpos($name, '.')) {
2975
                // 支持JSON字段 获取器定义
2976
                list($key, $field) = explode('.', $name);
2977
2978
                if (isset($result[$key])) {
2979
                    $result[$key][$field] = $closure($result[$key][$field] ?? null, $result[$key]);
2980
                }
2981
            } else {
2982
                $result[$name] = $closure($result[$name] ?? null, $result);
2983
            }
2984
        }
2985
    }
2986
2987
    /**
2988
     * JSON字段数据转换
2989
     * @access protected
2990
     * @param  array $result            查询数据
2991
     * @param  array $json              JSON字段
2992
     * @param  bool  $assoc             是否转换为数组
2993
     * @param  array $withRelationAttr  关联获取器
2994
     * @return void
2995
     */
2996
    protected function jsonResult(array &$result, array $json = [], bool $assoc = false, array $withRelationAttr = []): void
2997
    {
2998
        foreach ($json as $name) {
2999
            if (!isset($result[$name])) {
3000
                continue;
3001
            }
3002
3003
            $result[$name] = json_decode($result[$name], $assoc);
3004
3005
            if (!isset($withRelationAttr[$name])) {
3006
                continue;
3007
            }
3008
3009
            foreach ($withRelationAttr[$name] as $key => $closure) {
3010
                $data = get_object_vars($result[$name]);
3011
3012
                $result[$name]->$key = $closure($result[$name]->$key ?? null, $data);
3013
            }
3014
        }
3015
    }
3016
3017
    /**
3018
     * 查询数据转换为模型对象
3019
     * @access protected
3020
     * @param  array $result            查询数据
3021
     * @param  array $options           查询参数
3022
     * @param  bool  $resultSet         是否为数据集查询
3023
     * @param  array $withRelationAttr  关联字段获取器
3024
     * @return void
3025
     */
3026
    protected function resultToModel(array &$result, array $options = [], bool $resultSet = false, array $withRelationAttr = []): void
3027
    {
3028
        // 动态获取器
3029
        if (!empty($options['with_attr']) && empty($withRelationAttr)) {
3030
            foreach ($options['with_attr'] as $name => $val) {
3031
                if (strpos($name, '.')) {
3032
                    list($relation, $field) = explode('.', $name);
3033
3034
                    $withRelationAttr[$relation][$field] = $val;
3035
                    unset($options['with_attr'][$name]);
3036
                }
3037
            }
3038
        }
3039
3040
        // JSON 数据处理
3041
        if (!empty($options['json'])) {
3042
            $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr);
3043
        }
3044
3045
        $result = $this->model->newInstance($result, true, $resultSet ? null : $this->getModelUpdateCondition($options));
3046
3047
        // 动态获取器
3048
        if (!empty($options['with_attr'])) {
3049
            $result->withAttribute($options['with_attr']);
3050
        }
3051
3052
        // 输出属性控制
3053
        if (!empty($options['visible'])) {
3054
            $result->visible($options['visible']);
3055
        } elseif (!empty($options['hidden'])) {
3056
            $result->hidden($options['hidden']);
3057
        }
3058
3059
        if (!empty($options['append'])) {
3060
            $result->append($options['append']);
3061
        }
3062
3063
        // 关联查询
3064
        if (!empty($options['relation'])) {
3065
            $result->relationQuery($options['relation']);
3066
        }
3067
3068
        // 预载入查询
3069
        if (!$resultSet && !empty($options['with'])) {
3070
            $result->eagerlyResult($result, $options['with'], $withRelationAttr);
3071
        }
3072
3073
        // JOIN预载入查询
3074
        if (!$resultSet && !empty($options['with_join'])) {
3075
            $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, true);
3076
        }
3077
3078
        // 关联统计
3079
        if (!empty($options['with_count'])) {
3080
            foreach ($options['with_count'] as $val) {
3081
                $result->relationCount($result, $val[0], $val[1], $val[2]);
3082
            }
3083
        }
3084
    }
3085
3086
    /**
3087
     * 查询失败 抛出异常
3088
     * @access protected
3089
     * @return void
3090
     * @throws ModelNotFoundException
3091
     * @throws DataNotFoundException
3092
     */
3093
    protected function throwNotFound(): void
3094
    {
3095
        if (!empty($this->model)) {
3096
            $class = get_class($this->model);
3097
            throw new ModelNotFoundException('model data Not Found:' . $class, $class, $this->options);
3098
        }
3099
3100
        $table = $this->getTable();
3101
        throw new DataNotFoundException('table data not Found:' . $table, $table, $this->options);
3102
    }
3103
3104
    /**
3105
     * 查找多条记录 如果不存在则抛出异常
3106
     * @access public
3107
     * @param  array|string|Query|Closure $data
3108
     * @return array|PDOStatement|string|Model
3109
     * @throws DbException
3110
     * @throws ModelNotFoundException
3111
     * @throws DataNotFoundException
3112
     */
3113
    public function selectOrFail($data = null)
3114
    {
3115
        return $this->failException(true)->select($data);
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 findOrFail($data = null)
3128
    {
3129
        return $this->failException(true)->find($data);
3130
    }
3131
3132
    /**
3133
     * 分批数据返回处理
3134
     * @access public
3135
     * @param  integer      $count    每次处理的数据数量
3136
     * @param  callable     $callback 处理回调方法
3137
     * @param  string|array $column   分批处理的字段名
3138
     * @param  string       $order    字段排序
3139
     * @return bool
3140
     * @throws DbException
3141
     */
3142
    public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool
3143
    {
3144
        $options = $this->getOptions();
3145
        $column  = $column ?: $this->getPk();
3146
3147
        if (isset($options['order'])) {
3148
            if (Container::pull('app')->isDebug()) {
3149
                throw new DbException('chunk not support call order');
3150
            }
3151
            unset($options['order']);
3152
        }
3153
3154
        $bind = $this->bind;
3155
3156
        if (is_array($column)) {
3157
            $times = 1;
3158
            $query = $this->options($options)->page($times, $count);
3159
        } else {
3160
            $query = $this->options($options)->limit($count);
3161
3162
            if (strpos($column, '.')) {
3163
                list($alias, $key) = explode('.', $column);
3164
            } else {
3165
                $key = $column;
3166
            }
3167
        }
3168
3169
        $resultSet = $query->order($column, $order)->select();
3170
3171
        while (count($resultSet) > 0) {
3172
            if ($resultSet instanceof Collection) {
3173
                $resultSet = $resultSet->all();
3174
            }
3175
3176
            if (false === call_user_func($callback, $resultSet)) {
3177
                return false;
3178
            }
3179
3180
            if (isset($times)) {
3181
                $times++;
3182
                $query = $this->options($options)->page($times, $count);
3183
            } else {
3184
                $end    = end($resultSet);
3185
                $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...
3186
3187
                $query = $this->options($options)
3188
                    ->limit($count)
3189
                    ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
3190
            }
3191
3192
            $resultSet = $query->bind($bind)->order($column, $order)->select();
3193
        }
3194
3195
        return true;
3196
    }
3197
3198
    /**
3199
     * 获取绑定的参数 并清空
3200
     * @access public
3201
     * @param  bool $clear
3202
     * @return array
3203
     */
3204
    public function getBind(bool $clear = true): array
3205
    {
3206
        $bind = $this->bind;
3207
        if ($clear) {
3208
            $this->bind = [];
3209
        }
3210
3211
        return $bind;
3212
    }
3213
3214
    /**
3215
     * 创建子查询SQL
3216
     * @access public
3217
     * @param  bool $sub
3218
     * @return string
3219
     * @throws DbException
3220
     */
3221
    public function buildSql(bool $sub = true): string
3222
    {
3223
        return $sub ? '( ' . $this->fetchSql()->select() . ' )' : $this->fetchSql()->select();
3224
    }
3225
3226
    /**
3227
     * 视图查询处理
3228
     * @access protected
3229
     * @param  array   $options    查询参数
3230
     * @return void
3231
     */
3232
    protected function parseView(array &$options): void
3233
    {
3234
        foreach (['AND', 'OR'] as $logic) {
3235
            if (isset($options['where'][$logic])) {
3236
                foreach ($options['where'][$logic] as $key => $val) {
3237
                    if (array_key_exists($key, $options['map'])) {
3238
                        array_shift($val);
3239
                        array_unshift($val, $options['map'][$key]);
3240
                        $options['where'][$logic][$options['map'][$key]] = $val;
3241
                        unset($options['where'][$logic][$key]);
3242
                    }
3243
                }
3244
            }
3245
        }
3246
3247
        if (isset($options['order'])) {
3248
            // 视图查询排序处理
3249
            foreach ($options['order'] as $key => $val) {
3250
                if (is_numeric($key) && is_string($val)) {
3251
                    if (strpos($val, ' ')) {
3252
                        list($field, $sort) = explode(' ', $val);
3253
                        if (array_key_exists($field, $options['map'])) {
3254
                            $options['order'][$options['map'][$field]] = $sort;
3255
                            unset($options['order'][$key]);
3256
                        }
3257
                    } elseif (array_key_exists($val, $options['map'])) {
3258
                        $options['order'][$options['map'][$val]] = 'asc';
3259
                        unset($options['order'][$key]);
3260
                    }
3261
                } elseif (array_key_exists($key, $options['map'])) {
3262
                    $options['order'][$options['map'][$key]] = $val;
3263
                    unset($options['order'][$key]);
3264
                }
3265
            }
3266
        }
3267
    }
3268
3269
    protected function parseUpdateData(&$data)
3270
    {
3271
        $pk = $this->getPk();
3272
        // 如果存在主键数据 则自动作为更新条件
3273
        if (is_string($pk) && isset($data[$pk])) {
3274
            $this->where($pk, '=', $data[$pk]);
3275
            $this->options['key'] = $data[$pk];
3276
            unset($data[$pk]);
3277
        } elseif (is_array($pk)) {
3278
            // 增加复合主键支持
3279
            foreach ($pk as $field) {
3280
                if (isset($data[$field])) {
3281
                    $this->where($field, '=', $data[$field]);
3282
                } else {
3283
                    // 如果缺少复合主键数据则不执行
3284
                    throw new Exception('miss complex primary data');
3285
                }
3286
                unset($data[$field]);
3287
            }
3288
        }
3289
    }
3290
3291
    /**
3292
     * 把主键值转换为查询条件 支持复合主键
3293
     * @access public
3294
     * @param  array|string $data    主键数据
3295
     * @return void
3296
     * @throws Exception
3297
     */
3298
    public function parsePkWhere($data): void
3299
    {
3300
        $pk = $this->getPk();
3301
3302
        if (is_string($pk)) {
3303
            // 获取数据表
3304
            if (empty($this->options['table'])) {
3305
                $this->options['table'] = $this->getTable();
3306
            }
3307
3308
            $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table'];
3309
3310
            if (!empty($this->options['alias'][$table])) {
3311
                $alias = $this->options['alias'][$table];
3312
            }
3313
3314
            $key = isset($alias) ? $alias . '.' . $pk : $pk;
3315
            // 根据主键查询
3316
            if (is_array($data)) {
3317
                $this->where($key, 'in', $data);
3318
            } else {
3319
                $this->where($key, '=', $data);
3320
                $this->options['key'] = $data;
3321
            }
3322
        }
3323
    }
3324
3325
    /**
3326
     * 分析表达式(可用于查询或者写入操作)
3327
     * @access public
3328
     * @return array
3329
     */
3330
    public function parseOptions(): array
3331
    {
3332
        $options = $this->getOptions();
3333
3334
        // 获取数据表
3335
        if (empty($options['table'])) {
3336
            $options['table'] = $this->getTable();
3337
        }
3338
3339
        if (!isset($options['where'])) {
3340
            $options['where'] = [];
3341
        } elseif (isset($options['view'])) {
3342
            // 视图查询条件处理
3343
            $this->parseView($options);
3344
        }
3345
3346
        if (!isset($options['field'])) {
3347
            $options['field'] = '*';
3348
        }
3349
3350
        foreach (['data', 'order', 'join', 'union'] as $name) {
3351
            if (!isset($options[$name])) {
3352
                $options[$name] = [];
3353
            }
3354
        }
3355
3356
        if (!isset($options['strict'])) {
3357
            $options['strict'] = $this->connection->getConfig('fields_strict');
3358
        }
3359
3360
        foreach (['master', 'lock', 'fetch_sql', 'array', 'distinct', 'procedure'] as $name) {
3361
            if (!isset($options[$name])) {
3362
                $options[$name] = false;
3363
            }
3364
        }
3365
3366
        foreach (['group', 'having', 'limit', 'force', 'comment', 'partition', 'duplicate', 'extra'] as $name) {
3367
            if (!isset($options[$name])) {
3368
                $options[$name] = '';
3369
            }
3370
        }
3371
3372
        if (isset($options['page'])) {
3373
            // 根据页数计算limit
3374
            list($page, $listRows) = $options['page'];
3375
            $page                  = $page > 0 ? $page : 1;
3376
            $listRows              = $listRows ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20);
3377
            $offset                = $listRows * ($page - 1);
3378
            $options['limit']      = $offset . ',' . $listRows;
3379
        }
3380
3381
        $this->options = $options;
3382
3383
        return $options;
3384
    }
3385
3386
    /**
3387
     * 注册回调方法
3388
     * @access public
3389
     * @param  string   $event    事件名
3390
     * @param  callable $callback 回调方法
3391
     * @return void
3392
     */
3393
    public static function event(string $event, callable $callback): void
3394
    {
3395
        self::$event[$event] = $callback;
3396
    }
3397
3398
    /**
3399
     * 触发事件
3400
     * @access public
3401
     * @param  string $event   事件名
3402
     * @return mixed
3403
     */
3404
    public function trigger(string $event)
3405
    {
3406
        $result = false;
3407
3408
        if (isset(self::$event[$event])) {
3409
            $result = Container::getInstance()->invoke(self::$event[$event], [$this]);
3410
        }
3411
3412
        return $result;
3413
    }
3414
3415
}
3416