Passed
Push — 5.2 ( da0c84...54339d )
by liu
02:41
created

Query::getJoinTable()   B

Complexity

Conditions 11
Paths 16

Size

Total Lines 37
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 21
nc 16
nop 2
dl 0
loc 37
rs 7.3166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
// +----------------------------------------------------------------------
3
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
6
// +----------------------------------------------------------------------
7
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
// +----------------------------------------------------------------------
9
// | Author: liu21st <[email protected]>
10
// +----------------------------------------------------------------------
11
declare (strict_types = 1);
12
13
namespace think\db;
14
15
use 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
     * @var array
113
     */
114
    protected $timeExp = ['d' => 'today', 'w' => 'week', 'm' => 'month', 'y' => 'year'];
115
116
    /**
117
     * 架构函数
118
     * @access public
119
     */
120
    public function __construct(Connection $connection)
121
    {
122
        $this->connection = $connection;
123
124
        $this->prefix = $this->connection->getConfig('prefix');
125
    }
126
127
    /**
128
     * 创建一个新的查询对象
129
     * @access public
130
     * @return Query
131
     */
132
    public function newQuery()
133
    {
134
        return new static($this->connection);
135
    }
136
137
    /**
138
     * 利用__call方法实现一些特殊的Model方法
139
     * @access public
140
     * @param  string $method 方法名称
141
     * @param  array  $args   调用参数
142
     * @return mixed
143
     * @throws DbException
144
     * @throws Exception
145
     */
146
    public function __call(string $method, array $args)
147
    {
148
        if (strtolower(substr($method, 0, 5)) == 'getby') {
149
            // 根据某个字段获取记录
150
            $field = App::parseName(substr($method, 5));
151
            return $this->where($field, '=', $args[0])->find();
152
        } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
153
            // 根据某个字段获取记录的某个值
154
            $name = App::parseName(substr($method, 10));
155
            return $this->where($name, '=', $args[0])->value($args[1]);
156
        } elseif (strtolower(substr($method, 0, 7)) == 'whereor') {
157
            $name = App::parseName(substr($method, 7));
158
            array_unshift($args, $name);
159
            return call_user_func_array([$this, 'whereOr'], $args);
160
        } elseif (strtolower(substr($method, 0, 5)) == 'where') {
161
            $name = App::parseName(substr($method, 5));
162
            array_unshift($args, $name);
163
            return call_user_func_array([$this, 'where'], $args);
164
        } elseif ($this->model && method_exists($this->model, 'scope' . $method)) {
165
            // 动态调用命名范围
166
            $method = 'scope' . $method;
167
            array_unshift($args, $this);
168
169
            call_user_func_array([$this->model, $method], $args);
170
            return $this;
171
        } else {
172
            throw new Exception('method not exist:' . static::class . '->' . $method);
173
        }
174
    }
175
176
    /**
177
     * 获取当前的数据库Connection对象
178
     * @access public
179
     * @return Connection
180
     */
181
    public function getConnection(): Connection
182
    {
183
        return $this->connection;
184
    }
185
186
    /**
187
     * 设置当前的数据库Connection对象
188
     * @access public
189
     * @param  Connection      $connection
190
     * @return $this
191
     */
192
    public function setConnection(Connection $connection)
193
    {
194
        $this->connection = $connection;
195
196
        return $this;
197
    }
198
199
    /**
200
     * 指定模型
201
     * @access public
202
     * @param  Model $model 模型对象实例
203
     * @return $this
204
     */
205
    public function model(Model $model)
206
    {
207
        $this->model = $model;
208
        return $this;
209
    }
210
211
    /**
212
     * 获取当前的模型对象
213
     * @access public
214
     * @return Model|null
215
     */
216
    public function getModel()
217
    {
218
        return $this->model ? $this->model->setQuery($this) : null;
219
    }
220
221
    /**
222
     * 指定当前数据表名(不含前缀)
223
     * @access public
224
     * @param  string $name
225
     * @return $this
226
     */
227
    public function name(string $name)
228
    {
229
        $this->name = $name;
230
        return $this;
231
    }
232
233
    /**
234
     * 获取当前的数据表名称
235
     * @access public
236
     * @return string
237
     */
238
    public function getName(): string
239
    {
240
        return $this->name ?: $this->model->getName();
241
    }
242
243
    /**
244
     * 获取数据库的配置参数
245
     * @access public
246
     * @param  string $name 参数名称
247
     * @return mixed
248
     */
249
    public function getConfig(string $name = '')
250
    {
251
        return $this->connection->getConfig($name);
252
    }
253
254
    /**
255
     * 得到当前或者指定名称的数据表
256
     * @access public
257
     * @param  string $name
258
     * @return string
259
     */
260
    public function getTable(string $name = ''): string
261
    {
262
        if (empty($name) && isset($this->options['table'])) {
263
            return $this->options['table'];
264
        }
265
266
        $name = $name ?: $this->name;
267
268
        return $this->prefix . App::parseName($name);
269
    }
270
271
    /**
272
     * 获取数据表字段信息
273
     * @access public
274
     * @param  string $tableName 数据表名
275
     * @return array
276
     */
277
    public function getTableFields($tableName = ''): array
278
    {
279
        if ('' == $tableName) {
280
            $tableName = $this->options['table'] ?? $this->getTable();
281
        }
282
283
        return $this->connection->getTableFields($tableName);
284
    }
285
286
    /**
287
     * 获取数据表字段类型
288
     * @access protected
289
     * @param  string $tableName 数据表名
290
     * @param  string $field    字段名
291
     * @return array|string
292
     */
293
    protected function getTableFieldsType($tableName = '', string $field = null)
294
    {
295
        if ('' == $tableName) {
296
            $tableName = $this->options['table'] ?? $this->getTable();
297
        }
298
299
        return $this->connection->getFieldsType($tableName, $field);
300
    }
301
302
    /**
303
     * 设置字段类型信息
304
     * @access public
305
     * @param  array $type 字段类型信息
306
     * @return $this
307
     */
308
    public function setFieldType(array $type)
309
    {
310
        $this->options['field_type'] = $type;
311
        return $this;
312
    }
313
314
    /**
315
     * 获取字段类型信息
316
     * @access public
317
     * @return array
318
     */
319
    public function getFieldsType()
320
    {
321
        return !empty($this->options['field_type']) ? $this->options['field_type'] : $this->getTableFieldsType();
0 ignored issues
show
Bug Best Practice introduced by
The expression return ! empty($this->op...s->getTableFieldsType() also could return the type string which is incompatible with the documented return type array.
Loading history...
322
    }
323
324
    /**
325
     * 获取字段类型信息
326
     * @access public
327
     * @param  string $field 字段名
328
     * @return string|null
329
     */
330
    public function getFieldType(string $field)
331
    {
332
        $fieldType = $this->getFieldsType();
333
334
        return $fieldType[$field] ?? null;
335
    }
336
337
    /**
338
     * 获取字段类型信息
339
     * @access public
340
     * @return array
341
     */
342
    public function getFieldsBindType()
343
    {
344
        $fieldType = $this->getFieldsType();
345
346
        return array_map([$this->connection, 'getFieldBindType'], $fieldType);
347
    }
348
349
    /**
350
     * 获取字段类型信息
351
     * @access public
352
     * @param  string $field 字段名
353
     * @return string|null
354
     */
355
    public function getFieldBindType(string $field)
356
    {
357
        $fieldType = $this->getFieldType($field);
358
359
        return $this->connection->getFieldBindType($fieldType ?: '');
360
    }
361
362
    /**
363
     * 执行查询 返回数据集
364
     * @access public
365
     * @param  string      $sql    sql指令
366
     * @param  array       $bind   参数绑定
367
     * @return array
368
     * @throws BindParamException
369
     * @throws PDOException
370
     */
371
    public function query(string $sql, array $bind = []): array
372
    {
373
        return $this->connection->query($this, $sql, $bind, true);
374
    }
375
376
    /**
377
     * 执行语句
378
     * @access public
379
     * @param  string $sql  sql指令
380
     * @param  array  $bind 参数绑定
381
     * @return int
382
     * @throws BindParamException
383
     * @throws PDOException
384
     */
385
    public function execute(string $sql, array $bind = []): int
386
    {
387
        return $this->connection->execute($this, $sql, $bind);
388
    }
389
390
    /**
391
     * 监听SQL执行
392
     * @access public
393
     * @param  callable $callback 回调方法
394
     * @return void
395
     */
396
    public function listen(callable $callback): void
397
    {
398
        $this->connection->listen($callback);
399
    }
400
401
    /**
402
     * 获取最近插入的ID
403
     * @access public
404
     * @param  string $sequence 自增序列名
405
     * @return string
406
     */
407
    public function getLastInsID(string $sequence = null): string
408
    {
409
        return $this->connection->getLastInsID($sequence);
410
    }
411
412
    /**
413
     * 获取返回或者影响的记录数
414
     * @access public
415
     * @return integer
416
     */
417
    public function getNumRows(): int
418
    {
419
        return $this->connection->getNumRows();
420
    }
421
422
    /**
423
     * 获取最近一次查询的sql语句
424
     * @access public
425
     * @return string
426
     */
427
    public function getLastSql(): string
428
    {
429
        return $this->connection->getLastSql();
430
    }
431
432
    /**
433
     * 执行数据库事务
434
     * @access public
435
     * @param  callable $callback 数据操作方法回调
436
     * @return mixed
437
     */
438
    public function transaction(callable $callback)
439
    {
440
        return $this->connection->transaction($callback);
441
    }
442
443
    /**
444
     * 启动事务
445
     * @access public
446
     * @return void
447
     */
448
    public function startTrans(): void
449
    {
450
        $this->connection->startTrans();
451
    }
452
453
    /**
454
     * 用于非自动提交状态下面的查询提交
455
     * @access public
456
     * @return void
457
     * @throws PDOException
458
     */
459
    public function commit(): void
460
    {
461
        $this->connection->commit();
462
    }
463
464
    /**
465
     * 事务回滚
466
     * @access public
467
     * @return void
468
     * @throws PDOException
469
     */
470
    public function rollback(): void
471
    {
472
        $this->connection->rollback();
473
    }
474
475
    /**
476
     * 批处理执行SQL语句
477
     * 批处理的指令都认为是execute操作
478
     * @access public
479
     * @param  array $sql SQL批处理指令
480
     * @return bool
481
     */
482
    public function batchQuery(array $sql = []): bool
483
    {
484
        return $this->connection->batchQuery($this, $sql);
485
    }
486
487
    /**
488
     * 得到某个字段的值
489
     * @access public
490
     * @param  string $field   字段名
491
     * @param  mixed  $default 默认值
492
     * @return mixed
493
     */
494
    public function value(string $field, $default = null)
495
    {
496
        return $this->connection->value($this, $field, $default);
497
    }
498
499
    /**
500
     * 得到某个列的数组
501
     * @access public
502
     * @param  string $field 字段名 多个字段用逗号分隔
503
     * @param  string $key   索引
504
     * @return array
505
     */
506
    public function column(string $field, string $key = ''): array
507
    {
508
        return $this->connection->column($this, $field, $key);
509
    }
510
511
    /**
512
     * 聚合查询
513
     * @access protected
514
     * @param  string               $aggregate    聚合方法
515
     * @param  string|Raw           $field        字段名
516
     * @param  bool                 $force        强制转为数字类型
517
     * @return mixed
518
     */
519
    protected function aggregate(string $aggregate, $field, bool $force = false)
520
    {
521
        return $this->connection->aggregate($this, $aggregate, $field, $force);
522
    }
523
524
    /**
525
     * COUNT查询
526
     * @access public
527
     * @param  string|Raw $field 字段名
528
     * @return int
529
     */
530
    public function count(string $field = '*'): int
531
    {
532
        if (!empty($this->options['group'])) {
533
            // 支持GROUP
534
            $options = $this->getOptions();
535
            $subSql  = $this->options($options)->field('count(' . $field . ') AS think_count')->bind($this->bind)->buildSql();
536
537
            $query = $this->newQuery()->table([$subSql => '_group_count_']);
538
539
            $count = $query->aggregate('COUNT', '*');
540
        } else {
541
            $count = $this->aggregate('COUNT', $field);
542
        }
543
544
        return (int) $count;
545
    }
546
547
    /**
548
     * SUM查询
549
     * @access public
550
     * @param  string|Raw $field 字段名
551
     * @return float
552
     */
553
    public function sum($field): float
554
    {
555
        return $this->aggregate('SUM', $field, true);
556
    }
557
558
    /**
559
     * MIN查询
560
     * @access public
561
     * @param  string|Raw    $field    字段名
562
     * @param  bool          $force    强制转为数字类型
563
     * @return mixed
564
     */
565
    public function min($field, bool $force = true)
566
    {
567
        return $this->aggregate('MIN', $field, $force);
568
    }
569
570
    /**
571
     * MAX查询
572
     * @access public
573
     * @param  string|Raw    $field    字段名
574
     * @param  bool          $force    强制转为数字类型
575
     * @return mixed
576
     */
577
    public function max($field, bool $force = true)
578
    {
579
        return $this->aggregate('MAX', $field, $force);
580
    }
581
582
    /**
583
     * AVG查询
584
     * @access public
585
     * @param  string|Raw $field 字段名
586
     * @return float
587
     */
588
    public function avg($field): float
589
    {
590
        return $this->aggregate('AVG', $field, true);
591
    }
592
593
    /**
594
     * 查询SQL组装 join
595
     * @access public
596
     * @param  mixed  $join      关联的表名
597
     * @param  mixed  $condition 条件
598
     * @param  string $type      JOIN类型
599
     * @param  array  $bind      参数绑定
600
     * @return $this
601
     */
602
    public function join($join, string $condition = null, string $type = 'INNER', array $bind = [])
603
    {
604
        $table = $this->getJoinTable($join);
605
606
        if (!empty($bind) && $condition) {
607
            $this->bindParams($condition, $bind);
608
        }
609
610
        $this->options['join'][] = [$table, strtoupper($type), $condition];
611
612
        return $this;
613
    }
614
615
    /**
616
     * LEFT JOIN
617
     * @access public
618
     * @param  mixed  $join      关联的表名
619
     * @param  mixed  $condition 条件
620
     * @param  array  $bind      参数绑定
621
     * @return $this
622
     */
623
    public function leftJoin($join, string $condition = null, array $bind = [])
624
    {
625
        return $this->join($join, $condition, 'LEFT', $bind);
626
    }
627
628
    /**
629
     * RIGHT JOIN
630
     * @access public
631
     * @param  mixed  $join      关联的表名
632
     * @param  mixed  $condition 条件
633
     * @param  array  $bind      参数绑定
634
     * @return $this
635
     */
636
    public function rightJoin($join, string $condition = null, array $bind = [])
637
    {
638
        return $this->join($join, $condition, 'RIGHT', $bind);
639
    }
640
641
    /**
642
     * FULL JOIN
643
     * @access public
644
     * @param  mixed  $join      关联的表名
645
     * @param  mixed  $condition 条件
646
     * @param  array  $bind      参数绑定
647
     * @return $this
648
     */
649
    public function fullJoin($join, string $condition = null, array $bind = [])
650
    {
651
        return $this->join($join, $condition, 'FULL');
652
    }
653
654
    /**
655
     * 获取Join表名及别名 支持
656
     * ['prefix_table或者子查询'=>'alias'] 'table alias'
657
     * @access protected
658
     * @param  array|string|Raw $join
659
     * @param  string           $alias
660
     * @return string|array
661
     */
662
    protected function getJoinTable($join, &$alias = null)
663
    {
664
        if (is_array($join)) {
665
            $table = $join;
666
            $alias = array_shift($join);
667
            return $table;
668
        } elseif ($join instanceof Raw) {
669
            return $join;
670
        }
671
672
        $join = trim($join);
673
674
        if (false !== strpos($join, '(')) {
675
            // 使用子查询
676
            $table = $join;
677
        } else {
678
            // 使用别名
679
            if (strpos($join, ' ')) {
680
                // 使用别名
681
                list($table, $alias) = explode(' ', $join);
682
            } else {
683
                $table = $join;
684
                if (false === strpos($join, '.')) {
685
                    $alias = $join;
686
                }
687
            }
688
689
            if ($this->prefix && false === strpos($table, '.') && 0 !== strpos($table, $this->prefix)) {
690
                $table = $this->getTable($table);
691
            }
692
        }
693
694
        if (!empty($alias) && $table != $alias) {
695
            $table = [$table => $alias];
696
        }
697
698
        return $table;
699
    }
700
701
    /**
702
     * 查询SQL组装 union
703
     * @access public
704
     * @param  mixed   $union
705
     * @param  boolean $all
706
     * @return $this
707
     */
708
    public function union($union, bool $all = false)
709
    {
710
        $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION';
711
712
        if (is_array($union)) {
713
            $this->options['union'] = array_merge($this->options['union'], $union);
714
        } else {
715
            $this->options['union'][] = $union;
716
        }
717
718
        return $this;
719
    }
720
721
    /**
722
     * 查询SQL组装 union all
723
     * @access public
724
     * @param  mixed   $union
725
     * @return $this
726
     */
727
    public function unionAll($union)
728
    {
729
        return $this->union($union, true);
730
    }
731
732
    /**
733
     * 指定查询字段 支持字段排除和指定数据表
734
     * @access public
735
     * @param  mixed   $field
736
     * @param  boolean $except    是否排除
737
     * @param  string  $tableName 数据表名
738
     * @param  string  $prefix    字段前缀
739
     * @param  string  $alias     别名前缀
740
     * @return $this
741
     */
742
    public function field($field, bool $except = false, string $tableName = '', string $prefix = '', string $alias = '')
743
    {
744
        if (empty($field)) {
745
            return $this;
746
        } elseif ($field instanceof Raw) {
747
            $this->options['field'][] = $field;
748
            return $this;
749
        }
750
751
        if (is_string($field)) {
752
            if (preg_match('/[\<\'\"\(]/', $field)) {
753
                return $this->fieldRaw($field);
754
            }
755
756
            $field = array_map('trim', explode(',', $field));
757
        }
758
759
        if (true === $field) {
760
            // 获取全部字段
761
            $fields = $this->getTableFields($tableName);
762
            $field  = $fields ?: ['*'];
0 ignored issues
show
introduced by
$fields is an empty array, thus is always false.
Loading history...
763
        } elseif ($except) {
764
            // 字段排除
765
            $fields = $this->getTableFields($tableName);
766
            $field  = $fields ? array_diff($fields, $field) : $field;
0 ignored issues
show
introduced by
$fields is an empty array, thus is always false.
Loading history...
767
        }
768
769
        if ($tableName) {
770
            // 添加统一的前缀
771
            $prefix = $prefix ?: $tableName;
772
            foreach ($field as $key => &$val) {
773
                if (is_numeric($key) && $alias) {
774
                    $field[$prefix . '.' . $val] = $alias . $val;
775
                    unset($field[$key]);
776
                } elseif (is_numeric($key)) {
777
                    $val = $prefix . '.' . $val;
778
                }
779
            }
780
        }
781
782
        if (isset($this->options['field'])) {
783
            $field = array_merge((array) $this->options['field'], $field);
784
        }
785
786
        $this->options['field'] = array_unique($field);
787
788
        return $this;
789
    }
790
791
    /**
792
     * 表达式方式指定查询字段
793
     * @access public
794
     * @param  string $field    字段名
795
     * @return $this
796
     */
797
    public function fieldRaw(string $field)
798
    {
799
        $this->options['field'][] = new Raw($field);
800
801
        return $this;
802
    }
803
804
    /**
805
     * 设置数据
806
     * @access public
807
     * @param  array $data 数据
808
     * @return $this
809
     */
810
    public function data(array $data)
811
    {
812
        $this->options['data'] = $data;
813
814
        return $this;
815
    }
816
817
    /**
818
     * 字段值增长
819
     * @access public
820
     * @param  string       $field 字段名
821
     * @param  integer      $step  增长值
822
     * @param  integer      $lazyTime 延时时间(s)
823
     * @return $this
824
     */
825
    public function inc(string $field, int $step = 1, int $lazyTime = 0, string $op = 'INC')
826
    {
827
        if ($lazyTime > 0) {
828
            // 延迟写入
829
            $condition = $this->options['where'] ?? [];
830
831
            $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition));
832
            $step = $this->connection->lazyWrite($op, $guid, $step, $lazyTime);
833
834
            if (false === $step) {
835
                return $this;
836
            }
837
838
            $op = 'INC';
839
        }
840
841
        $this->options['data'][$field] = [$op, $step];
842
843
        return $this;
844
    }
845
846
    /**
847
     * 字段值减少
848
     * @access public
849
     * @param  string       $field 字段名
850
     * @param  integer      $step  增长值
851
     * @param  integer      $lazyTime 延时时间(s)
852
     * @return $this
853
     */
854
    public function dec(string $field, int $step = 1, int $lazyTime = 0)
855
    {
856
        return $this->inc($field, $step, $lazyTime, 'DEC');
857
    }
858
859
    /**
860
     * 使用表达式设置数据
861
     * @access public
862
     * @param  string $field 字段名
863
     * @param  string $value 字段值
864
     * @return $this
865
     */
866
    public function exp(string $field, string $value)
867
    {
868
        $this->options['data'][$field] = new Raw($value);
869
        return $this;
870
    }
871
872
    /**
873
     * 指定JOIN查询字段
874
     * @access public
875
     * @param  string|array $table 数据表
876
     * @param  string|array $field 查询字段
877
     * @param  string       $on    JOIN条件
878
     * @param  string       $type  JOIN类型
879
     * @return $this
880
     */
881
    public function view($join, $field = true, $on = null, string $type = 'INNER', array $bind = [])
882
    {
883
        $this->options['view'] = true;
884
885
        $fields = [];
886
        $table  = $this->getJoinTable($join, $alias);
887
888
        if (true === $field) {
889
            $fields = $alias . '.*';
890
        } else {
891
            if (is_string($field)) {
892
                $field = explode(',', $field);
893
            }
894
895
            foreach ($field as $key => $val) {
896
                if (is_numeric($key)) {
897
                    $fields[] = $alias . '.' . $val;
898
899
                    $this->options['map'][$val] = $alias . '.' . $val;
900
                } else {
901
                    if (preg_match('/[,=\.\'\"\(\s]/', $key)) {
902
                        $name = $key;
903
                    } else {
904
                        $name = $alias . '.' . $key;
905
                    }
906
907
                    $fields[] = $name . ' AS ' . $val;
908
909
                    $this->options['map'][$val] = $name;
910
                }
911
            }
912
        }
913
914
        $this->field($fields);
915
916
        if ($on) {
917
            $this->join($table, $on, $type, $bind);
918
        } else {
919
            $this->table($table);
920
        }
921
922
        return $this;
923
    }
924
925
    /**
926
     * 指定AND查询条件
927
     * @access public
928
     * @param  mixed $field     查询字段
929
     * @param  mixed $op        查询表达式
930
     * @param  mixed $condition 查询条件
931
     * @return $this
932
     */
933
    public function where($field, $op = null, $condition = null)
934
    {
935
        if ($field instanceof $this) {
936
            $this->options['where'] = $field->getOptions('where');
937
            return $this;
938
        }
939
940
        $param = func_get_args();
941
        array_shift($param);
942
        return $this->parseWhereExp('AND', $field, $op, $condition, $param);
943
    }
944
945
    /**
946
     * 指定OR查询条件
947
     * @access public
948
     * @param  mixed $field     查询字段
949
     * @param  mixed $op        查询表达式
950
     * @param  mixed $condition 查询条件
951
     * @return $this
952
     */
953
    public function whereOr($field, $op = null, $condition = null)
954
    {
955
        $param = func_get_args();
956
        array_shift($param);
957
        return $this->parseWhereExp('OR', $field, $op, $condition, $param);
958
    }
959
960
    /**
961
     * 指定XOR查询条件
962
     * @access public
963
     * @param  mixed $field     查询字段
964
     * @param  mixed $op        查询表达式
965
     * @param  mixed $condition 查询条件
966
     * @return $this
967
     */
968
    public function whereXor($field, $op = null, $condition = null)
969
    {
970
        $param = func_get_args();
971
        array_shift($param);
972
        return $this->parseWhereExp('XOR', $field, $op, $condition, $param);
973
    }
974
975
    /**
976
     * 指定Null查询条件
977
     * @access public
978
     * @param  mixed  $field 查询字段
979
     * @param  string $logic 查询逻辑 and or xor
980
     * @return $this
981
     */
982
    public function whereNull(string $field, string $logic = 'AND')
983
    {
984
        return $this->parseWhereExp($logic, $field, 'NULL', null, [], true);
985
    }
986
987
    /**
988
     * 指定NotNull查询条件
989
     * @access public
990
     * @param  mixed  $field 查询字段
991
     * @param  string $logic 查询逻辑 and or xor
992
     * @return $this
993
     */
994
    public function whereNotNull(string $field, string $logic = 'AND')
995
    {
996
        return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true);
997
    }
998
999
    /**
1000
     * 指定Exists查询条件
1001
     * @access public
1002
     * @param  mixed  $condition 查询条件
1003
     * @param  string $logic     查询逻辑 and or xor
1004
     * @return $this
1005
     */
1006
    public function whereExists($condition, string $logic = 'AND')
1007
    {
1008
        if (is_string($condition)) {
1009
            $condition = new Raw($condition);
1010
        }
1011
1012
        $this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition];
1013
        return $this;
1014
    }
1015
1016
    /**
1017
     * 指定NotExists查询条件
1018
     * @access public
1019
     * @param  mixed  $condition 查询条件
1020
     * @param  string $logic     查询逻辑 and or xor
1021
     * @return $this
1022
     */
1023
    public function whereNotExists($condition, string $logic = 'AND')
1024
    {
1025
        if (is_string($condition)) {
1026
            $condition = new Raw($condition);
1027
        }
1028
1029
        $this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition];
1030
        return $this;
1031
    }
1032
1033
    /**
1034
     * 指定In查询条件
1035
     * @access public
1036
     * @param  mixed  $field     查询字段
1037
     * @param  mixed  $condition 查询条件
1038
     * @param  string $logic     查询逻辑 and or xor
1039
     * @return $this
1040
     */
1041
    public function whereIn(string $field, $condition, string $logic = 'AND')
1042
    {
1043
        return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true);
1044
    }
1045
1046
    /**
1047
     * 指定NotIn查询条件
1048
     * @access public
1049
     * @param  mixed  $field     查询字段
1050
     * @param  mixed  $condition 查询条件
1051
     * @param  string $logic     查询逻辑 and or xor
1052
     * @return $this
1053
     */
1054
    public function whereNotIn(string $field, $condition, string $logic = 'AND')
1055
    {
1056
        return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true);
1057
    }
1058
1059
    /**
1060
     * 指定Like查询条件
1061
     * @access public
1062
     * @param  mixed  $field     查询字段
1063
     * @param  mixed  $condition 查询条件
1064
     * @param  string $logic     查询逻辑 and or xor
1065
     * @return $this
1066
     */
1067
    public function whereLike(string $field, $condition, string $logic = 'AND')
1068
    {
1069
        return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true);
1070
    }
1071
1072
    /**
1073
     * 指定NotLike查询条件
1074
     * @access public
1075
     * @param  mixed  $field     查询字段
1076
     * @param  mixed  $condition 查询条件
1077
     * @param  string $logic     查询逻辑 and or xor
1078
     * @return $this
1079
     */
1080
    public function whereNotLike(string $field, $condition, string $logic = 'AND')
1081
    {
1082
        return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true);
1083
    }
1084
1085
    /**
1086
     * 指定Between查询条件
1087
     * @access public
1088
     * @param  mixed  $field     查询字段
1089
     * @param  mixed  $condition 查询条件
1090
     * @param  string $logic     查询逻辑 and or xor
1091
     * @return $this
1092
     */
1093
    public function whereBetween(string $field, $condition, string $logic = 'AND')
1094
    {
1095
        return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true);
1096
    }
1097
1098
    /**
1099
     * 指定NotBetween查询条件
1100
     * @access public
1101
     * @param  mixed  $field     查询字段
1102
     * @param  mixed  $condition 查询条件
1103
     * @param  string $logic     查询逻辑 and or xor
1104
     * @return $this
1105
     */
1106
    public function whereNotBetween(string $field, $condition, string $logic = 'AND')
1107
    {
1108
        return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true);
1109
    }
1110
1111
    /**
1112
     * 比较两个字段
1113
     * @access public
1114
     * @param  string    $field1     查询字段
1115
     * @param  string    $operator   比较操作符
1116
     * @param  string    $field2     比较字段
1117
     * @param  string    $logic      查询逻辑 and or xor
1118
     * @return $this
1119
     */
1120
    public function whereColumn(string $field1, string $operator, string $field2 = null, string $logic = 'AND')
1121
    {
1122
        if (is_null($field2)) {
1123
            $field2   = $operator;
1124
            $operator = '=';
1125
        }
1126
1127
        return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true);
1128
    }
1129
1130
    /**
1131
     * 设置软删除字段及条件
1132
     * @access public
1133
     * @param  string       $field     查询字段
1134
     * @param  mixed        $condition 查询条件
1135
     * @return $this
1136
     */
1137
    public function useSoftDelete(string $field, $condition = null)
1138
    {
1139
        if ($field) {
1140
            $this->options['soft_delete'] = [$field, $condition];
1141
        }
1142
1143
        return $this;
1144
    }
1145
1146
    /**
1147
     * 指定Exp查询条件
1148
     * @access public
1149
     * @param  mixed  $field     查询字段
1150
     * @param  string $where     查询条件
1151
     * @param  array  $bind      参数绑定
1152
     * @param  string $logic     查询逻辑 and or xor
1153
     * @return $this
1154
     */
1155
    public function whereExp(string $field, string $where, array $bind = [], string $logic = 'AND')
1156
    {
1157
        if (!empty($bind)) {
1158
            $this->bindParams($where, $bind);
1159
        }
1160
1161
        $this->options['where'][$logic][] = [$field, 'EXP', new Raw($where)];
1162
1163
        return $this;
1164
    }
1165
1166
    /**
1167
     * 指定字段Raw查询
1168
     * @access public
1169
     * @param  string $field     查询字段表达式
1170
     * @param  mixed  $op        查询表达式
1171
     * @param  string $condition 查询条件
1172
     * @param  string $logic     查询逻辑 and or xor
1173
     * @return $this
1174
     */
1175
    public function whereFieldRaw(string $field, $op, $condition = null, string $logic = 'AND')
1176
    {
1177
        if (is_null($condition)) {
1178
            $condition = $op;
1179
            $op        = '=';
1180
        }
1181
1182
        $this->options['where'][$logic][] = [new Raw($field), $op, $condition];
1183
        return $this;
1184
    }
1185
1186
    /**
1187
     * 指定表达式查询条件
1188
     * @access public
1189
     * @param  string $where  查询条件
1190
     * @param  array  $bind   参数绑定
1191
     * @param  string $logic  查询逻辑 and or xor
1192
     * @return $this
1193
     */
1194
    public function whereRaw(string $where, array $bind = [], string $logic = 'AND')
1195
    {
1196
        if (!empty($bind)) {
1197
            $this->bindParams($where, $bind);
1198
        }
1199
1200
        $this->options['where'][$logic][] = new Raw($where);
1201
1202
        return $this;
1203
    }
1204
1205
    /**
1206
     * 指定表达式查询条件 OR
1207
     * @access public
1208
     * @param  string $where  查询条件
1209
     * @param  array  $bind   参数绑定
1210
     * @return $this
1211
     */
1212
    public function whereOrRaw(string $where, array $bind = [])
1213
    {
1214
        return $this->whereRaw($where, $bind, 'OR');
1215
    }
1216
1217
    /**
1218
     * 分析查询表达式
1219
     * @access protected
1220
     * @param  string   $logic     查询逻辑 and or xor
1221
     * @param  mixed    $field     查询字段
1222
     * @param  mixed    $op        查询表达式
1223
     * @param  mixed    $condition 查询条件
1224
     * @param  array    $param     查询参数
1225
     * @param  bool     $strict    严格模式
1226
     * @return $this
1227
     */
1228
    protected function parseWhereExp(string $logic, $field, $op, $condition, array $param = [], bool $strict = false)
1229
    {
1230
        $logic = strtoupper($logic);
1231
1232
        if (is_string($field) && !empty($this->options['via']) && false === strpos($field, '.')) {
1233
            $field = $this->options['via'] . '.' . $field;
1234
        }
1235
1236
        if ($field instanceof Raw) {
1237
            return $this->whereRaw($field, is_array($op) ? $op : []);
1238
        } elseif ($strict) {
1239
            // 使用严格模式查询
1240
            if ('=' == $op) {
1241
                $where = $this->whereEq($field, $condition);
1242
            } else {
1243
                $where = [$field, $op, $condition, $logic];
1244
            }
1245
        } elseif (is_array($field)) {
1246
            // 解析数组批量查询
1247
            return $this->parseArrayWhereItems($field, $logic);
1248
        } elseif ($field instanceof Closure) {
1249
            $where = $field;
1250
        } elseif (is_string($field)) {
1251
            if (preg_match('/[,=\<\'\"\(\s]/', $field)) {
1252
                return $this->whereRaw($field, is_array($op) ? $op : []);
1253
            } elseif (is_string($op) && strtolower($op) == 'exp') {
1254
                $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : [];
1255
                return $this->whereExp($field, $condition, $bind, $logic);
1256
            }
1257
1258
            $where = $this->parseWhereItem($logic, $field, $op, $condition, $param);
1259
        }
1260
1261
        if (!empty($where)) {
1262
            $this->options['where'][$logic][] = $where;
1263
        }
1264
1265
        return $this;
1266
    }
1267
1268
    /**
1269
     * 分析查询表达式
1270
     * @access protected
1271
     * @param  string   $logic     查询逻辑 and or xor
1272
     * @param  mixed    $field     查询字段
1273
     * @param  mixed    $op        查询表达式
1274
     * @param  mixed    $condition 查询条件
1275
     * @param  array    $param     查询参数
1276
     * @return array
1277
     */
1278
    protected function parseWhereItem(string $logic, $field, $op, $condition, array $param = []): array
1279
    {
1280
        if (is_array($op)) {
1281
            // 同一字段多条件查询
1282
            array_unshift($param, $field);
1283
            $where = $param;
1284
        } elseif (!is_string($op)) {
1285
            $where = $this->whereEq($field, $op);
1286
        } elseif ($field && is_null($condition)) {
1287
            if (in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) {
1288
                // null查询
1289
                $where = [$field, $op, ''];
1290
            } elseif ('=' == $op) {
1291
                $where = [$field, 'NULL', ''];
1292
            } elseif ('<>' == $op) {
1293
                $where = [$field, 'NOTNULL', ''];
1294
            } else {
1295
                // 字段相等查询
1296
                $where = $this->whereEq($field, $op);
1297
            }
1298
        } elseif (in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) {
1299
            $where = [$field, $op, is_string($condition) ? new Raw($condition) : $condition];
1300
        } else {
1301
            $where = $field ? [$field, $op, $condition, $param[2] ?? null] : [];
1302
        }
1303
1304
        return $where;
1305
    }
1306
1307
    protected function whereEq($field, $value)
1308
    {
1309
        $where = [$field, '=', $value];
1310
        if ($this->getPk() == $field) {
1311
            $this->options['key'] = $value;
1312
        }
1313
1314
        return $where;
1315
    }
1316
1317
    /**
1318
     * 数组批量查询
1319
     * @access protected
1320
     * @param  array    $field     批量查询
1321
     * @param  string   $logic     查询逻辑 and or xor
1322
     * @return $this
1323
     */
1324
    protected function parseArrayWhereItems(array $field, string $logic)
1325
    {
1326
        if (key($field) !== 0) {
1327
            $where = [];
1328
            foreach ($field as $key => $val) {
1329
                if ($val instanceof Raw) {
1330
                    $where[] = [$key, 'exp', $val];
1331
                } else {
1332
                    $where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, is_array($val) ? 'IN' : '=', $val];
1333
                }
1334
            }
1335
        } else {
1336
            // 数组批量查询
1337
            $where = $field;
1338
        }
1339
1340
        if (!empty($where)) {
1341
            $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? array_merge($this->options['where'][$logic], $where) : $where;
1342
        }
1343
1344
        return $this;
1345
    }
1346
1347
    /**
1348
     * 去除某个查询条件
1349
     * @access public
1350
     * @param  string $field 查询字段
1351
     * @param  string $logic 查询逻辑 and or xor
1352
     * @return $this
1353
     */
1354
    public function removeWhereField(string $field, string $logic = 'AND')
1355
    {
1356
        $logic = strtoupper($logic);
1357
1358
        if (isset($this->options['where'][$logic])) {
1359
            foreach ($this->options['where'][$logic] as $key => $val) {
1360
                if (is_array($val) && $val[0] == $field) {
1361
                    unset($this->options['where'][$logic][$key]);
1362
                }
1363
            }
1364
        }
1365
1366
        return $this;
1367
    }
1368
1369
    /**
1370
     * 去除查询参数
1371
     * @access public
1372
     * @param  string $option 参数名 留空去除所有参数
1373
     * @return $this
1374
     */
1375
    public function removeOption(string $option = '')
1376
    {
1377
        if ('' === $option) {
1378
            $this->options = [];
1379
            $this->bind    = [];
1380
        } elseif (isset($this->options[$option])) {
1381
            unset($this->options[$option]);
1382
        }
1383
1384
        return $this;
1385
    }
1386
1387
    /**
1388
     * 条件查询
1389
     * @access public
1390
     * @param  mixed             $condition  满足条件(支持闭包)
1391
     * @param  Closure|array    $query      满足条件后执行的查询表达式(闭包或数组)
1392
     * @param  Closure|array    $otherwise  不满足条件后执行
1393
     * @return $this
1394
     */
1395
    public function when($condition, $query, $otherwise = null)
1396
    {
1397
        if ($condition instanceof Closure) {
1398
            $condition = $condition($this);
1399
        }
1400
1401
        if ($condition) {
1402
            if ($query instanceof Closure) {
1403
                $query($this, $condition);
1404
            } elseif (is_array($query)) {
0 ignored issues
show
introduced by
The condition is_array($query) is always true.
Loading history...
1405
                $this->where($query);
1406
            }
1407
        } elseif ($otherwise) {
1408
            if ($otherwise instanceof Closure) {
1409
                $otherwise($this, $condition);
1410
            } elseif (is_array($otherwise)) {
0 ignored issues
show
introduced by
The condition is_array($otherwise) is always true.
Loading history...
1411
                $this->where($otherwise);
1412
            }
1413
        }
1414
1415
        return $this;
1416
    }
1417
1418
    /**
1419
     * 指定查询数量
1420
     * @access public
1421
     * @param  int $offset 起始位置
1422
     * @param  int $length 查询数量
1423
     * @return $this
1424
     */
1425
    public function limit(int $offset, int $length = null)
1426
    {
1427
        $this->options['limit'] = $offset . ($length ? ',' . $length : '');
1428
1429
        return $this;
1430
    }
1431
1432
    /**
1433
     * 指定分页
1434
     * @access public
1435
     * @param  int $page     页数
1436
     * @param  int $listRows 每页数量
1437
     * @return $this
1438
     */
1439
    public function page(int $page, int $listRows = null)
1440
    {
1441
        $this->options['page'] = [$page, $listRows];
1442
1443
        return $this;
1444
    }
1445
1446
    /**
1447
     * 分页查询
1448
     * @access public
1449
     * @param  int|array $listRows 每页数量 数组表示配置参数
1450
     * @param  int|bool  $simple   是否简洁模式或者总记录数
1451
     * @param  array     $config   配置参数
1452
     * @return \think\Paginator
1453
     * @throws DbException
1454
     */
1455
    public function paginate($listRows = null, $simple = false, $config = [])
1456
    {
1457
        if (is_int($simple)) {
1458
            $total  = $simple;
1459
            $simple = false;
1460
        }
1461
1462
        $paginate = array_merge($this->paginateConfig, Container::pull('config')->get('paginate'));
1463
1464
        if (is_array($listRows)) {
1465
            $config   = array_merge($paginate, $listRows);
1466
            $listRows = intval($config['list_rows']);
1467
        } else {
1468
            $config   = array_merge($paginate, $config);
1469
            $listRows = intval($listRows ?: $config['list_rows']);
1470
        }
1471
1472
        /** @var Paginator $class */
1473
        $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']);
1474
        $page  = isset($config['page']) ? (int) $config['page'] : call_user_func([
1475
            $class,
1476
            'getCurrentPage',
1477
        ], $config['var_page']);
1478
1479
        $page = $page < 1 ? 1 : $page;
1480
1481
        $config['path'] = $config['path'] ?? call_user_func([$class, 'getCurrentPath']);
1482
1483
        if (!isset($total) && !$simple) {
1484
            $options = $this->getOptions();
1485
1486
            unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']);
1487
1488
            $bind    = $this->bind;
1489
            $total   = $this->count();
1490
            $results = $this->options($options)->bind($bind)->page($page, $listRows)->select();
1491
        } elseif ($simple) {
1492
            $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
1493
            $total   = null;
1494
        } else {
1495
            $results = $this->page($page, $listRows)->select();
1496
        }
1497
1498
        $this->removeOption('limit');
1499
        $this->removeOption('page');
1500
1501
        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...
1502
    }
1503
1504
    /**
1505
     * 表达式方式指定当前操作的数据表
1506
     * @access public
1507
     * @param  mixed $table 表名
1508
     * @return $this
1509
     */
1510
    public function tableRaw(string $table)
1511
    {
1512
        $this->options['table'] = new Raw($table);
1513
1514
        return $this;
1515
    }
1516
1517
    /**
1518
     * 指定当前操作的数据表
1519
     * @access public
1520
     * @param  mixed $table 表名
1521
     * @return $this
1522
     */
1523
    public function table($table)
1524
    {
1525
        if (is_string($table)) {
1526
            if (strpos($table, ')')) {
1527
                // 子查询
1528
            } else {
1529
                $tables = explode(',', $table);
1530
                $table  = [];
1531
1532
                foreach ($tables as $item) {
1533
                    $item = trim($item);
1534
                    if (strpos($item, ' ')) {
1535
                        list($item, $alias) = explode(' ', $item);
1536
                        $this->alias([$item => $alias]);
1537
                        $table[$item] = $alias;
1538
                    } else {
1539
                        $table[] = $item;
1540
                    }
1541
                }
1542
            }
1543
        } elseif (is_array($table)) {
1544
            $tables = $table;
1545
            $table  = [];
1546
1547
            foreach ($tables as $key => $val) {
1548
                if (is_numeric($key)) {
1549
                    $table[] = $val;
1550
                } else {
1551
                    $this->alias([$key => $val]);
1552
                    $table[$key] = $val;
1553
                }
1554
            }
1555
        }
1556
1557
        $this->options['table'] = $table;
1558
1559
        return $this;
1560
    }
1561
1562
    /**
1563
     * USING支持 用于多表删除
1564
     * @access public
1565
     * @param  mixed $using
1566
     * @return $this
1567
     */
1568
    public function using($using)
1569
    {
1570
        $this->options['using'] = $using;
1571
        return $this;
1572
    }
1573
1574
    /**
1575
     * 存储过程调用
1576
     * @access public
1577
     * @param  bool $procedure
1578
     * @return $this
1579
     */
1580
    public function procedure($procedure = true)
1581
    {
1582
        $this->options['procedure'] = $procedure;
1583
        return $this;
1584
    }
1585
1586
    /**
1587
     * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc'])
1588
     * @access public
1589
     * @param  string|array|Raw $field 排序字段
1590
     * @param  string           $order 排序
1591
     * @return $this
1592
     */
1593
    public function order($field, string $order = '')
1594
    {
1595
        if (empty($field)) {
1596
            return $this;
1597
        } elseif ($field instanceof Raw) {
1598
            $this->options['order'][] = $field;
1599
            return $this;
1600
        }
1601
1602
        if (is_string($field)) {
1603
            if (!empty($this->options['via'])) {
1604
                $field = $this->options['via'] . '.' . $field;
1605
            }
1606
            if (strpos($field, ',')) {
1607
                $field = array_map('trim', explode(',', $field));
1608
            } else {
1609
                $field = empty($order) ? $field : [$field => $order];
1610
            }
1611
        } elseif (!empty($this->options['via'])) {
1612
            foreach ($field as $key => $val) {
1613
                if (is_numeric($key)) {
1614
                    $field[$key] = $this->options['via'] . '.' . $val;
1615
                } else {
1616
                    $field[$this->options['via'] . '.' . $key] = $val;
1617
                    unset($field[$key]);
1618
                }
1619
            }
1620
        }
1621
1622
        if (!isset($this->options['order'])) {
1623
            $this->options['order'] = [];
1624
        }
1625
1626
        if (is_array($field)) {
1627
            $this->options['order'] = array_merge($this->options['order'], $field);
1628
        } else {
1629
            $this->options['order'][] = $field;
1630
        }
1631
1632
        return $this;
1633
    }
1634
1635
    /**
1636
     * 表达式方式指定Field排序
1637
     * @access public
1638
     * @param  string $field 排序字段
1639
     * @param  array  $bind  参数绑定
1640
     * @return $this
1641
     */
1642
    public function orderRaw(string $field, array $bind = [])
1643
    {
1644
        if (!empty($bind)) {
1645
            $this->bindParams($field, $bind);
1646
        }
1647
1648
        $this->options['order'][] = new Raw($field);
1649
1650
        return $this;
1651
    }
1652
1653
    /**
1654
     * 指定Field排序 orderField('id',[1,2,3],'desc')
1655
     * @access public
1656
     * @param  string   $field 排序字段
1657
     * @param  array    $values 排序值
1658
     * @param  string   $order
1659
     * @return $this
1660
     */
1661
    public function orderField(string $field, array $values, string $order = '')
1662
    {
1663
        if (!empty($values)) {
1664
            $values['sort'] = $order;
1665
1666
            $this->options['order'][$field] = $values;
1667
        }
1668
1669
        return $this;
1670
    }
1671
1672
    /**
1673
     * 随机排序
1674
     * @access public
1675
     * @return $this
1676
     */
1677
    public function orderRand()
1678
    {
1679
        $this->options['order'][] = '[rand]';
1680
        return $this;
1681
    }
1682
1683
    /**
1684
     * 查询缓存
1685
     * @access public
1686
     * @param  mixed             $key    缓存key
1687
     * @param  integer|\DateTime $expire 缓存有效期
1688
     * @param  string            $tag    缓存标签
1689
     * @return $this
1690
     */
1691
    public function cache($key = true, $expire = null, $tag = null)
1692
    {
1693
        if (false === $key) {
1694
            return $this;
1695
        }
1696
1697
        if ($key instanceof \DateTimeInterface || (is_int($key) && is_null($expire))) {
1698
            $expire = $key;
1699
            $key    = true;
1700
        }
1701
1702
        $this->options['cache'] = [$key, $expire, $tag];
1703
1704
        return $this;
1705
    }
1706
1707
    /**
1708
     * 指定group查询
1709
     * @access public
1710
     * @param  string|array $group GROUP
1711
     * @return $this
1712
     */
1713
    public function group($group)
1714
    {
1715
        $this->options['group'] = $group;
1716
        return $this;
1717
    }
1718
1719
    /**
1720
     * 指定having查询
1721
     * @access public
1722
     * @param  string $having having
1723
     * @return $this
1724
     */
1725
    public function having(string $having)
1726
    {
1727
        $this->options['having'] = $having;
1728
        return $this;
1729
    }
1730
1731
    /**
1732
     * 指定查询lock
1733
     * @access public
1734
     * @param  bool|string $lock 是否lock
1735
     * @return $this
1736
     */
1737
    public function lock($lock = false)
1738
    {
1739
        $this->options['lock'] = $lock;
1740
1741
        if ($lock) {
1742
            $this->options['master'] = true;
1743
        }
1744
1745
        return $this;
1746
    }
1747
1748
    /**
1749
     * 指定distinct查询
1750
     * @access public
1751
     * @param  bool $distinct 是否唯一
1752
     * @return $this
1753
     */
1754
    public function distinct(bool $distinct = true)
1755
    {
1756
        $this->options['distinct'] = $distinct;
1757
        return $this;
1758
    }
1759
1760
    /**
1761
     * 指定数据表别名
1762
     * @access public
1763
     * @param  array|string $alias 数据表别名
1764
     * @return $this
1765
     */
1766
    public function alias($alias)
1767
    {
1768
        if (is_array($alias)) {
1769
            $this->options['alias'] = $alias;
1770
        } else {
1771
            $table = $this->getTable();
1772
1773
            $this->options['alias'][$table] = $alias;
1774
        }
1775
1776
        return $this;
1777
    }
1778
1779
    /**
1780
     * 指定强制索引
1781
     * @access public
1782
     * @param  string $force 索引名称
1783
     * @return $this
1784
     */
1785
    public function force(string $force)
1786
    {
1787
        $this->options['force'] = $force;
1788
        return $this;
1789
    }
1790
1791
    /**
1792
     * 查询注释
1793
     * @access public
1794
     * @param  string $comment 注释
1795
     * @return $this
1796
     */
1797
    public function comment(string $comment)
1798
    {
1799
        $this->options['comment'] = $comment;
1800
        return $this;
1801
    }
1802
1803
    /**
1804
     * 获取执行的SQL语句而不进行实际的查询
1805
     * @access public
1806
     * @param  bool $fetch 是否返回sql
1807
     * @return $this|Fetch
1808
     */
1809
    public function fetchSql(bool $fetch = true)
1810
    {
1811
        $this->options['fetch_sql'] = $fetch;
1812
1813
        if ($fetch) {
1814
            return new Fetch($this);
1815
        }
1816
1817
        return $this;
1818
    }
1819
1820
    /**
1821
     * 设置是否返回数据集对象
1822
     * @access public
1823
     * @param  bool|string  $collection  是否返回数据集对象
1824
     * @return $this
1825
     */
1826
    public function fetchCollection($collection = true)
1827
    {
1828
        $this->options['collection'] = $collection;
1829
        return $this;
1830
    }
1831
1832
    /**
1833
     * 设置是否返回数组
1834
     * @access public
1835
     * @param  bool  $asArray  是否返回数组
1836
     * @return $this
1837
     */
1838
    public function fetchArray(bool $asArray = true)
1839
    {
1840
        $this->options['array'] = $asArray;
1841
        return $this;
1842
    }
1843
1844
    /**
1845
     * 设置从主服务器读取数据
1846
     * @access public
1847
     * @param  bool $readMaster 是否从主服务器读取
1848
     * @return $this
1849
     */
1850
    public function master(bool $readMaster = true)
1851
    {
1852
        $this->options['master'] = $readMaster;
1853
        return $this;
1854
    }
1855
1856
    /**
1857
     * 设置是否严格检查字段名
1858
     * @access public
1859
     * @param  bool $strict 是否严格检查字段
1860
     * @return $this
1861
     */
1862
    public function strict(bool $strict = true)
1863
    {
1864
        $this->options['strict'] = $strict;
1865
        return $this;
1866
    }
1867
1868
    /**
1869
     * 设置查询数据不存在是否抛出异常
1870
     * @access public
1871
     * @param  bool $fail 数据不存在是否抛出异常
1872
     * @return $this
1873
     */
1874
    public function failException(bool $fail = true)
1875
    {
1876
        $this->options['fail'] = $fail;
1877
        return $this;
1878
    }
1879
1880
    /**
1881
     * 设置自增序列名
1882
     * @access public
1883
     * @param  string $sequence 自增序列名
1884
     * @return $this
1885
     */
1886
    public function sequence(string $sequence = null)
1887
    {
1888
        $this->options['sequence'] = $sequence;
1889
        return $this;
1890
    }
1891
1892
    /**
1893
     * 设置当前查询所在的分区
1894
     * @access public
1895
     * @param  string|array  $partition  分区名称
1896
     * @return $this
1897
     */
1898
    public function partition($partition)
1899
    {
1900
        $this->options['partition'] = $partition;
1901
        return $this;
1902
    }
1903
1904
    /**
1905
     * 设置DUPLICATE
1906
     * @access public
1907
     * @param  array|string|Raw  $duplicate
1908
     * @return $this
1909
     */
1910
    public function duplicate($duplicate)
1911
    {
1912
        $this->options['duplicate'] = $duplicate;
1913
        return $this;
1914
    }
1915
1916
    /**
1917
     * 设置查询的额外参数
1918
     * @access public
1919
     * @param  string  $extra
1920
     * @return $this
1921
     */
1922
    public function extra(string $extra)
1923
    {
1924
        $this->options['extra'] = $extra;
1925
        return $this;
1926
    }
1927
1928
    /**
1929
     * 设置需要隐藏的输出属性
1930
     * @access public
1931
     * @param  array $hidden 需要隐藏的字段名
1932
     * @return $this
1933
     */
1934
    public function hidden(array $hidden)
1935
    {
1936
        $this->options['hidden'] = $hidden;
1937
        return $this;
1938
    }
1939
1940
    /**
1941
     * 设置需要输出的属性
1942
     * @access public
1943
     * @param  array $visible 需要输出的属性
1944
     * @return $this
1945
     */
1946
    public function visible(array $visible)
1947
    {
1948
        $this->options['visible'] = $visible;
1949
        return $this;
1950
    }
1951
1952
    /**
1953
     * 设置需要追加输出的属性
1954
     * @access public
1955
     * @param  array $append 需要追加的属性
1956
     * @return $this
1957
     */
1958
    public function append(array $append)
1959
    {
1960
        $this->options['append'] = $append;
1961
        return $this;
1962
    }
1963
1964
    /**
1965
     * 设置JSON字段信息
1966
     * @access public
1967
     * @param  array $json JSON字段
1968
     * @param  bool  $assoc 是否取出数组
1969
     * @return $this
1970
     */
1971
    public function json(array $json = [], bool $assoc = false)
1972
    {
1973
        $this->options['json']       = $json;
1974
        $this->options['json_assoc'] = $assoc;
1975
        return $this;
1976
    }
1977
1978
    /**
1979
     * 添加查询范围
1980
     * @access public
1981
     * @param  array|string|Closure   $scope 查询范围定义
1982
     * @param  array                   $args  参数
1983
     * @return $this
1984
     */
1985
    public function scope($scope, ...$args)
1986
    {
1987
        // 查询范围的第一个参数始终是当前查询对象
1988
        array_unshift($args, $this);
1989
1990
        if ($scope instanceof Closure) {
1991
            call_user_func_array($scope, $args);
1992
            return $this;
1993
        }
1994
1995
        if (is_string($scope)) {
1996
            $scope = explode(',', $scope);
1997
        }
1998
1999
        if ($this->model) {
2000
            // 检查模型类的查询范围方法
2001
            foreach ($scope as $name) {
2002
                $method = 'scope' . trim($name);
2003
2004
                if (method_exists($this->model, $method)) {
2005
                    call_user_func_array([$this->model, $method], $args);
2006
                }
2007
            }
2008
        }
2009
2010
        return $this;
2011
    }
2012
2013
    /**
2014
     * 指定数据表主键
2015
     * @access public
2016
     * @param  string $pk 主键
2017
     * @return $this
2018
     */
2019
    public function pk(string $pk)
2020
    {
2021
        $this->pk = $pk;
2022
        return $this;
2023
    }
2024
2025
    /**
2026
     * 添加日期或者时间查询规则
2027
     * @access public
2028
     * @param  string       $name  时间表达式
2029
     * @param  string|array $rule  时间范围
2030
     * @return $this
2031
     */
2032
    public function timeRule(string $name, $rule)
2033
    {
2034
        $this->timeRule[$name] = $rule;
2035
        return $this;
2036
    }
2037
2038
    /**
2039
     * 查询日期或者时间
2040
     * @access public
2041
     * @param  string       $field 日期字段名
2042
     * @param  string|array $op    比较运算符或者表达式
2043
     * @param  string|array $range 比较范围
2044
     * @param  string       $logic AND OR
2045
     * @return $this
2046
     */
2047
    public function whereTime(string $field, $op, $range = null, string $logic = 'AND')
2048
    {
2049
        if (is_null($range)) {
2050
            if (is_array($op)) {
2051
                $range = $op;
2052
            } else {
2053
                if (isset($this->timeExp[strtolower($op)])) {
2054
                    $op = $this->timeExp[strtolower($op)];
2055
                }
2056
2057
                if (isset($this->timeRule[strtolower($op)])) {
2058
                    $range = $this->timeRule[strtolower($op)];
2059
                } else {
2060
                    $range = $op;
2061
                }
2062
            }
2063
2064
            $op = is_array($range) ? 'between' : '>=';
2065
        } elseif (is_array($op)) {
2066
            throw new Exception('where express error:' . $op);
0 ignored issues
show
Bug introduced by
Are you sure $op of type array can be used in concatenation? ( Ignorable by Annotation )

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

2066
            throw new Exception('where express error:' . /** @scrutinizer ignore-type */ $op);
Loading history...
2067
        }
2068
2069
        return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true);
2070
    }
2071
2072
    /**
2073
     * 查询某个时间间隔数据
2074
     * @access protected
2075
     * @param  string    $field 日期字段名
2076
     * @param  string    $start 开始时间
2077
     * @param  string    $interval 时间间隔单位
2078
     * @param  string    $logic AND OR
2079
     * @return $this
2080
     */
2081
    protected function whereTimeInterval(string $field, string $start, $interval = 'day', string $logic = 'AND')
2082
    {
2083
        $startTime = strtotime($start);
2084
        $endTime   = strtotime('+1 ' . $interval, $startTime);
2085
2086
        return $this->parseWhereExp($logic, $field, 'between time', [$startTime, $endTime], [], true);
2087
    }
2088
2089
    /**
2090
     * 查询月数据 whereMonth('time_field', '2018-1')
2091
     * @access public
2092
     * @param  string    $field 日期字段名
2093
     * @param  string    $month 月份信息
2094
     * @param  string    $logic AND OR
2095
     * @return $this
2096
     */
2097
    public function whereMonth(string $field, string $month, string $logic = 'AND')
2098
    {
2099
        return $this->whereTimeInterval($field, $month, 'month', $logic);
2100
    }
2101
2102
    /**
2103
     * 查询年数据 whereYear('time_field', '2018')
2104
     * @access public
2105
     * @param  string    $field 日期字段名
2106
     * @param  string    $year  年份信息
2107
     * @param  string    $logic AND OR
2108
     * @return $this
2109
     */
2110
    public function whereYear(string $field, string $year, string $logic = 'AND')
2111
    {
2112
        return $this->whereTimeInterval($field, $year . '-1-1', 'year', $logic);
2113
    }
2114
2115
    /**
2116
     * 查询日数据 whereDay('time_field', '2018-1-1')
2117
     * @access public
2118
     * @param  string    $field 日期字段名
2119
     * @param  string    $day   日期信息
2120
     * @param  string    $logic AND OR
2121
     * @return $this
2122
     */
2123
    public function whereDay(string $field, string $day, string $logic = 'AND')
2124
    {
2125
        return $this->whereTimeInterval($field, $day, 'day', $logic);
2126
    }
2127
2128
    /**
2129
     * 查询日期或者时间范围 whereBetweenTime('time_field', '2018-1-1','2018-1-15')
2130
     * @access public
2131
     * @param  string           $field 日期字段名
2132
     * @param  string|int       $startTime    开始时间
2133
     * @param  string|int       $endTime 结束时间
2134
     * @param  string           $logic AND OR
2135
     * @return $this
2136
     */
2137
    public function whereBetweenTime(string $field, $startTime, $endTime = null, string $logic = 'AND')
2138
    {
2139
        if (is_null($endTime)) {
2140
            $time    = is_string($startTime) ? strtotime($startTime) : $startTime;
2141
            $endTime = strtotime('+1 day', $time);
2142
        }
2143
2144
        return $this->parseWhereExp($logic, $field, 'between time', [$startTime, $endTime], [], true);
2145
    }
2146
2147
    /**
2148
     * 查询当前时间在两个时间字段范围 whereBetweenTimeField('start_time', 'end_time')
2149
     * @access public
2150
     * @param  string    $startField    开始时间字段
2151
     * @param  string    $endField      结束时间字段
2152
     * @return $this
2153
     */
2154
    public function whereBetweenTimeField(string $startField, string $endField)
2155
    {
2156
        return $this->whereTime($startField, '<=', time())
2157
            ->whereTime($endField, '>=', time());
2158
    }
2159
2160
    /**
2161
     * 查询当前时间不在两个时间字段范围 whereNotBetweenTimeField('start_time', 'end_time')
2162
     * @access public
2163
     * @param  string    $startField    开始时间字段
2164
     * @param  string    $endField      结束时间字段
2165
     * @return $this
2166
     */
2167
    public function whereNotBetweenTimeField(string $startField, string $endField)
2168
    {
2169
        return $this->whereTime($startField, '>', time())
2170
            ->whereTime($endField, '<', time(), 'OR');
2171
    }
2172
2173
    /**
2174
     * 获取当前数据表的主键
2175
     * @access public
2176
     * @return string|array
2177
     */
2178
    public function getPk()
2179
    {
2180
        if (!empty($this->pk)) {
2181
            $pk = $this->pk;
2182
        } else {
2183
            $this->pk = $pk = $this->connection->getPk($this->getTable());
2184
        }
2185
2186
        return $pk;
2187
    }
2188
2189
    /**
2190
     * 批量参数绑定
2191
     * @access public
2192
     * @param  array   $value 绑定变量值
2193
     * @return $this
2194
     */
2195
    public function bind(array $value)
2196
    {
2197
        $this->bind = array_merge($this->bind, $value);
2198
        return $this;
2199
    }
2200
2201
    /**
2202
     * 单个参数绑定
2203
     * @access public
2204
     * @param  mixed   $value 绑定变量值
2205
     * @param  integer $type  绑定类型
2206
     * @param  string  $name  绑定标识
2207
     * @return string
2208
     */
2209
    public function bindValue($value, int $type = null, string $name = null)
2210
    {
2211
        $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_';
2212
2213
        $this->bind[$name] = [$value, $type ?: PDO::PARAM_STR];
2214
        return $name;
2215
    }
2216
2217
    /**
2218
     * 检测参数是否已经绑定
2219
     * @access public
2220
     * @param  string $key 参数名
2221
     * @return bool
2222
     */
2223
    public function isBind($key)
2224
    {
2225
        return isset($this->bind[$key]);
2226
    }
2227
2228
    /**
2229
     * 参数绑定
2230
     * @access public
2231
     * @param  string $sql    绑定的sql表达式
2232
     * @param  array  $bind   参数绑定
2233
     * @return void
2234
     */
2235
    protected function bindParams(string &$sql, array $bind = []): void
2236
    {
2237
        foreach ($bind as $key => $value) {
2238
            if (is_array($value)) {
2239
                $name = $this->bindValue($value[0], $value[1], $value[2] ?? null);
2240
            } else {
2241
                $name = $this->bindValue($value);
2242
            }
2243
2244
            if (is_numeric($key)) {
2245
                $sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1);
2246
            } else {
2247
                $sql = str_replace(':' . $key, ':' . $name, $sql);
2248
            }
2249
        }
2250
    }
2251
2252
    /**
2253
     * 查询参数批量赋值
2254
     * @access protected
2255
     * @param  array $options 表达式参数
2256
     * @return $this
2257
     */
2258
    protected function options(array $options)
2259
    {
2260
        $this->options = $options;
2261
        return $this;
2262
    }
2263
2264
    /**
2265
     * 获取当前的查询参数
2266
     * @access public
2267
     * @param  string $name 参数名
2268
     * @return mixed
2269
     */
2270
    public function getOptions(string $name = '')
2271
    {
2272
        if ('' === $name) {
2273
            return $this->options;
2274
        }
2275
2276
        return $this->options[$name] ?? null;
2277
    }
2278
2279
    /**
2280
     * 设置当前的查询参数
2281
     * @access public
2282
     * @param  string $option 参数名
2283
     * @param  mixed  $value  参数值
2284
     * @return $this
2285
     */
2286
    public function setOption(string $option, $value)
2287
    {
2288
        $this->options[$option] = $value;
2289
        return $this;
2290
    }
2291
2292
    /**
2293
     * 设置关联查询
2294
     * @access public
2295
     * @param  array $relation 关联名称
2296
     * @return $this
2297
     */
2298
    public function relation(array $relation)
2299
    {
2300
        if (!empty($relation)) {
2301
            $this->options['relation'] = $relation;
2302
        }
2303
2304
        return $this;
2305
    }
2306
2307
    /**
2308
     * 设置关联查询JOIN预查询
2309
     * @access public
2310
     * @param  array $with 关联方法名称(数组)
2311
     * @return $this
2312
     */
2313
    public function with(array $with)
2314
    {
2315
        if (!empty($with)) {
2316
            $this->options['with'] = $with;
2317
        }
2318
2319
        return $this;
2320
    }
2321
2322
    /**
2323
     * 关联预载入 JOIN方式
2324
     * @access protected
2325
     * @param  array        $with 关联方法名
2326
     * @param  string       $joinType JOIN方式
2327
     * @return $this
2328
     */
2329
    public function withJoin(array $with, string $joinType = '')
2330
    {
2331
        if (empty($with)) {
2332
            return $this;
2333
        }
2334
2335
        $first = true;
2336
2337
        /** @var Model $class */
2338
        $class = $this->model;
2339
        foreach ($with as $key => $relation) {
2340
            $closure = null;
2341
            $field   = true;
2342
2343
            if ($relation instanceof Closure) {
2344
                // 支持闭包查询过滤关联条件
2345
                $closure  = $relation;
2346
                $relation = $key;
2347
            } elseif (is_array($relation)) {
2348
                $field    = $relation;
2349
                $relation = $key;
2350
            } elseif (is_string($relation) && strpos($relation, '.')) {
2351
                $relation = strstr($relation, '.', true);
2352
            }
2353
2354
            /** @var Relation $model */
2355
            $relation = App::parseName($relation, 1, false);
2356
            $model    = $class->$relation();
2357
2358
            if ($model instanceof OneToOne) {
2359
                $model->eagerly($this, $relation, $field, $joinType, $closure, $first);
2360
                $first = false;
2361
            } else {
2362
                // 不支持其它关联
2363
                unset($with[$key]);
2364
            }
2365
        }
2366
2367
        $this->via();
2368
2369
        $this->options['with_join'] = $with;
2370
2371
        return $this;
2372
    }
2373
2374
    /**
2375
     * 设置数据字段获取器
2376
     * @access public
2377
     * @param  string       $name       字段名
2378
     * @param  callable     $callback   闭包获取器
2379
     * @return $this
2380
     */
2381
    public function withAttr(string $name, callable $callback)
2382
    {
2383
        $this->options['with_attr'][$name] = $callback;
2384
2385
        return $this;
2386
    }
2387
2388
    /**
2389
     * 设置数据字段获取器
2390
     * @access public
2391
     * @param  array    $attrs       字段获取器
2392
     * @return $this
2393
     */
2394
    public function withAttrs(array $attrs)
2395
    {
2396
        $this->options['with_attr'] = $attrs;
2397
2398
        return $this;
2399
    }
2400
2401
    /**
2402
     * 使用搜索器条件搜索字段
2403
     * @access public
2404
     * @param  array    $fields     搜索字段
2405
     * @param  array    $data       搜索数据
2406
     * @param  string   $prefix     字段前缀标识
2407
     * @return $this
2408
     */
2409
    public function withSearch(array $fields, array $data = [], string $prefix = '')
2410
    {
2411
        foreach ($fields as $key => $field) {
2412
            if ($field instanceof Closure) {
2413
                $field($this, $data[$key] ?? null, $data, $prefix);
2414
            } elseif ($this->model) {
2415
                // 检测搜索器
2416
                $fieldName = is_numeric($key) ? $field : $key;
2417
                $method    = 'search' . App::parseName($fieldName, 1) . 'Attr';
2418
2419
                if (method_exists($this->model, $method)) {
2420
                    $this->model->$method($this, $data[$field] ?? null, $data, $prefix);
2421
                }
2422
            }
2423
        }
2424
2425
        return $this;
2426
    }
2427
2428
    /**
2429
     * 关联统计
2430
     * @access protected
2431
     * @param  array|string $relations 关联方法名
2432
     * @param  string       $aggregate 聚合查询方法
2433
     * @param  string       $field 字段
2434
     * @param  bool         $subQuery 是否使用子查询
2435
     * @return $this
2436
     */
2437
    protected function withAggregate($relations, string $aggregate = 'count', $field = '*', bool $subQuery = true)
2438
    {
2439
        if (is_string($relations)) {
2440
            $relations = explode(',', $relations);
2441
        }
2442
2443
        if (!$subQuery) {
2444
            $this->options['with_count'][] = [$relations, $aggregate, $field];
2445
        } else {
2446
            if (!isset($this->options['field'])) {
2447
                $this->field('*');
2448
            }
2449
2450
            foreach ($relations as $key => $relation) {
2451
                $closure = $aggregateField = null;
2452
2453
                if ($relation instanceof Closure) {
2454
                    $closure  = $relation;
2455
                    $relation = $key;
2456
                } elseif (!is_int($key)) {
2457
                    $aggregateField = $relation;
2458
                    $relation       = $key;
2459
                }
2460
2461
                $relation = App::parseName($relation, 1, false);
2462
2463
                $count = '(' . $this->model->$relation()->getRelationCountQuery($closure, $aggregate, $field, $aggregateField) . ')';
2464
2465
                if (empty($aggregateField)) {
2466
                    $aggregateField = App::parseName($relation) . '_' . $aggregate;
2467
                }
2468
2469
                $this->field([$count => $aggregateField]);
2470
            }
2471
        }
2472
2473
        return $this;
2474
    }
2475
2476
    /**
2477
     * 关联统计
2478
     * @access public
2479
     * @param  string|array $relation 关联方法名
2480
     * @param  bool         $subQuery 是否使用子查询
2481
     * @return $this
2482
     */
2483
    public function withCount($relation, bool $subQuery = true)
2484
    {
2485
        return $this->withAggregate($relation, 'count', '*', $subQuery);
2486
    }
2487
2488
    /**
2489
     * 关联统计Sum
2490
     * @access public
2491
     * @param  string|array $relation 关联方法名
2492
     * @param  string       $field 字段
2493
     * @param  bool         $subQuery 是否使用子查询
2494
     * @return $this
2495
     */
2496
    public function withSum($relation, string $field, bool $subQuery = true)
2497
    {
2498
        return $this->withAggregate($relation, 'sum', $field, $subQuery);
2499
    }
2500
2501
    /**
2502
     * 关联统计Max
2503
     * @access public
2504
     * @param  string|array $relation 关联方法名
2505
     * @param  string       $field 字段
2506
     * @param  bool         $subQuery 是否使用子查询
2507
     * @return $this
2508
     */
2509
    public function withMax($relation, string $field, bool $subQuery = true)
2510
    {
2511
        return $this->withAggregate($relation, 'max', $field, $subQuery);
2512
    }
2513
2514
    /**
2515
     * 关联统计Min
2516
     * @access public
2517
     * @param  string|array $relation 关联方法名
2518
     * @param  string       $field 字段
2519
     * @param  bool         $subQuery 是否使用子查询
2520
     * @return $this
2521
     */
2522
    public function withMin($relation, string $field, bool $subQuery = true)
2523
    {
2524
        return $this->withAggregate($relation, 'min', $field, $subQuery);
2525
    }
2526
2527
    /**
2528
     * 关联统计Avg
2529
     * @access public
2530
     * @param  string|array $relation 关联方法名
2531
     * @param  string       $field 字段
2532
     * @param  bool         $subQuery 是否使用子查询
2533
     * @return $this
2534
     */
2535
    public function withAvg($relation, string $field, bool $subQuery = true)
2536
    {
2537
        return $this->withAggregate($relation, 'avg', $field, $subQuery);
2538
    }
2539
2540
    /**
2541
     * 关联预加载中 获取关联指定字段值
2542
     * example:
2543
     * Model::with(['relation' => function($query){
2544
     *     $query->withField("id,name");
2545
     * }])
2546
     *
2547
     * @access public
2548
     * @param  string | array $field 指定获取的字段
2549
     * @return $this
2550
     */
2551
    public function withField($field)
2552
    {
2553
        $this->options['with_field'] = $field;
2554
2555
        return $this;
2556
    }
2557
2558
    /**
2559
     * 设置当前字段添加的表别名
2560
     * @access public
2561
     * @param  string $via
2562
     * @return $this
2563
     */
2564
    public function via(string $via = '')
2565
    {
2566
        $this->options['via'] = $via;
2567
2568
        return $this;
2569
    }
2570
2571
    /**
2572
     * 插入记录
2573
     * @access public
2574
     * @param  array   $data         数据
2575
     * @param  boolean $replace      是否replace
2576
     * @param  boolean $getLastInsID 返回自增主键
2577
     * @param  string  $sequence     自增序列名
2578
     * @return integer
2579
     */
2580
    public function insert(array $data = [], bool $replace = false, bool $getLastInsID = false, string $sequence = null)
2581
    {
2582
        $this->options['data'] = array_merge($this->options['data'] ?? [], $data);
2583
2584
        return $this->connection->insert($this, $replace, $getLastInsID, $sequence);
2585
    }
2586
2587
    /**
2588
     * 插入记录并获取自增ID
2589
     * @access public
2590
     * @param  array   $data     数据
2591
     * @param  boolean $replace  是否replace
2592
     * @param  string  $sequence 自增序列名
2593
     * @return integer|string
2594
     */
2595
    public function insertGetId(array $data, bool $replace = false, string $sequence = null)
2596
    {
2597
        return $this->insert($data, $replace, true, $sequence);
2598
    }
2599
2600
    /**
2601
     * 批量插入记录
2602
     * @access public
2603
     * @param  array     $dataSet 数据集
2604
     * @param  boolean   $replace 是否replace
2605
     * @param  integer   $limit   每次写入数据限制
2606
     * @return integer
2607
     */
2608
    public function insertAll(array $dataSet = [], bool $replace = false, int $limit = null): int
2609
    {
2610
        if (empty($dataSet)) {
2611
            $dataSet = $this->options['data'] ?? [];
2612
        }
2613
2614
        if (empty($limit) && !empty($this->options['limit'])) {
2615
            $limit = (int) $this->options['limit'];
2616
        }
2617
2618
        return $this->connection->insertAll($this, $dataSet, $replace, $limit);
0 ignored issues
show
Bug introduced by
It seems like $limit can also be of type null; however, parameter $limit of think\db\Connection::insertAll() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

2618
        return $this->connection->insertAll($this, $dataSet, $replace, /** @scrutinizer ignore-type */ $limit);
Loading history...
2619
    }
2620
2621
    /**
2622
     * 通过Select方式插入记录
2623
     * @access public
2624
     * @param  array    $fields 要插入的数据表字段名
2625
     * @param  string   $table  要插入的数据表名
2626
     * @return integer
2627
     * @throws PDOException
2628
     */
2629
    public function selectInsert(array $fields, string $table): int
2630
    {
2631
        return $this->connection->selectInsert($this, $fields, $table);
2632
    }
2633
2634
    /**
2635
     * 更新记录
2636
     * @access public
2637
     * @param  mixed $data 数据
2638
     * @return integer
2639
     * @throws Exception
2640
     * @throws PDOException
2641
     */
2642
    public function update(array $data = []): int
2643
    {
2644
        $data = array_merge($this->options['data'] ?? [], $data);
2645
2646
        if (empty($this->options['where'])) {
2647
            $this->parseUpdateData($data);
2648
        }
2649
2650
        if (empty($this->options['where']) && $this->model) {
2651
            $this->where($this->model->getWhere());
2652
        }
2653
2654
        if (empty($this->options['where'])) {
2655
            // 如果没有任何更新条件则不执行
2656
            throw new Exception('miss update condition');
2657
        }
2658
2659
        $this->options['data'] = $data;
2660
2661
        return $this->connection->update($this);
2662
    }
2663
2664
    /**
2665
     * 删除记录
2666
     * @access public
2667
     * @param  mixed $data 表达式 true 表示强制删除
2668
     * @return int
2669
     * @throws Exception
2670
     * @throws PDOException
2671
     */
2672
    public function delete($data = null): int
2673
    {
2674
        if (!is_null($data) && true !== $data) {
2675
            // AR模式分析主键条件
2676
            $this->parsePkWhere($data);
2677
        }
2678
2679
        if (empty($this->options['where']) && $this->model) {
2680
            $this->where($this->model->getWhere());
2681
        }
2682
2683
        if (true !== $data && empty($this->options['where'])) {
2684
            // 如果条件为空 不进行删除操作 除非设置 1=1
2685
            throw new Exception('delete without condition');
2686
        }
2687
2688
        if (!empty($this->options['soft_delete'])) {
2689
            // 软删除
2690
            list($field, $condition) = $this->options['soft_delete'];
2691
            if ($condition) {
2692
                unset($this->options['soft_delete']);
2693
                $this->options['data'] = [$field => $condition];
2694
2695
                return $this->connection->update($this);
2696
            }
2697
        }
2698
2699
        $this->options['data'] = $data;
2700
2701
        return $this->connection->delete($this);
2702
    }
2703
2704
    /**
2705
     * 执行查询但只返回PDOStatement对象
2706
     * @access public
2707
     * @return PDOStatement
2708
     */
2709
    public function getPdo(): PDOStatement
2710
    {
2711
        return $this->connection->pdo($this);
2712
    }
2713
2714
    /**
2715
     * 使用游标查找记录
2716
     * @access public
2717
     * @param  mixed $data
2718
     * @return \Generator
2719
     */
2720
    public function cursor($data = null)
2721
    {
2722
        if (!is_null($data)) {
2723
            // 主键条件分析
2724
            $this->parsePkWhere($data);
2725
        }
2726
2727
        $this->options['data'] = $data;
2728
2729
        $connection = clone $this->connection;
2730
2731
        return $connection->cursor($this);
2732
    }
2733
2734
    /**
2735
     * 查找记录
2736
     * @access public
2737
     * @param  mixed $data
2738
     * @return Collection|array|ModelCollection
2739
     * @throws DbException
2740
     * @throws ModelNotFoundException
2741
     * @throws DataNotFoundException
2742
     */
2743
    public function select($data = null)
2744
    {
2745
        if (!is_null($data)) {
2746
            // 主键条件分析
2747
            $this->parsePkWhere($data);
2748
        }
2749
2750
        $resultSet = $this->connection->select($this);
2751
2752
        // 返回结果处理
2753
        if (!empty($this->options['fail']) && count($resultSet) == 0) {
2754
            $this->throwNotFound();
2755
        }
2756
2757
        // 数据列表读取后的处理
2758
        if (!empty($this->model) && empty($this->options['array'])) {
2759
            // 生成模型对象
2760
            $resultSet = $this->resultSetToModelCollection($resultSet);
2761
        } else {
2762
            $this->resultSet($resultSet);
2763
        }
2764
2765
        return $resultSet;
2766
    }
2767
2768
    /**
2769
     * 查询数据转换为模型数据集对象
2770
     * @access protected
2771
     * @param  array  $resultSet         数据集
2772
     * @return ModelCollection
2773
     */
2774
    protected function resultSetToModelCollection(array $resultSet): ModelCollection
2775
    {
2776
        if (!empty($this->options['collection']) && is_string($this->options['collection'])) {
2777
            $collection = $this->options['collection'];
2778
        }
2779
2780
        if (empty($resultSet)) {
2781
            return $this->model->toCollection([], $collection ?? null);
2782
        }
2783
2784
        // 检查动态获取器
2785
        if (!empty($this->options['with_attr'])) {
2786
            foreach ($this->options['with_attr'] as $name => $val) {
2787
                if (strpos($name, '.')) {
2788
                    list($relation, $field) = explode('.', $name);
2789
2790
                    $withRelationAttr[$relation][$field] = $val;
2791
                    unset($this->options['with_attr'][$name]);
2792
                }
2793
            }
2794
        }
2795
2796
        $withRelationAttr = $withRelationAttr ?? [];
2797
2798
        foreach ($resultSet as $key => &$result) {
2799
            // 数据转换为模型对象
2800
            $this->resultToModel($result, $this->options, true, $withRelationAttr);
2801
        }
2802
2803
        if (!empty($this->options['with'])) {
2804
            // 预载入
2805
            $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 2798. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2806
        }
2807
2808
        if (!empty($this->options['with_join'])) {
2809
            // 预载入
2810
            $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, true);
2811
        }
2812
2813
        // 模型数据集转换
2814
        return $this->model->toCollection($resultSet, $collection ?? null);
2815
    }
2816
2817
    /**
2818
     * 处理数据集
2819
     * @access public
2820
     * @param  array $resultSet
2821
     * @return void
2822
     */
2823
    protected function resultSet(array &$resultSet): void
2824
    {
2825
        if (!empty($this->options['json'])) {
2826
            foreach ($resultSet as &$result) {
2827
                $this->jsonResult($result, $this->options['json'], true);
2828
            }
2829
        }
2830
2831
        if (!empty($this->options['with_attr'])) {
2832
            foreach ($resultSet as &$result) {
2833
                $this->getResultAttr($result, $this->options['with_attr']);
2834
            }
2835
        }
2836
2837
        if (!empty($this->options['visible']) || !empty($this->options['hidden'])) {
2838
            foreach ($resultSet as &$result) {
2839
                $this->filterResult($result);
2840
            }
2841
        }
2842
2843
        if (!empty($this->options['collection'])) {
2844
            // 返回Collection对象
2845
            $resultSet = new Collection($resultSet);
2846
        }
2847
    }
2848
2849
    /**
2850
     * 查找单条记录
2851
     * @access public
2852
     * @param  mixed $data
2853
     * @return array|Model
2854
     * @throws DbException
2855
     * @throws ModelNotFoundException
2856
     * @throws DataNotFoundException
2857
     */
2858
    public function find($data = null)
2859
    {
2860
        if (!is_null($data)) {
2861
            // AR模式分析主键条件
2862
            $this->parsePkWhere($data);
2863
        }
2864
2865
        $result = $this->connection->find($this);
2866
2867
        // 数据处理
2868
        if (empty($result)) {
2869
            return $this->resultToEmpty();
2870
        }
2871
2872
        if (!empty($this->model) && empty($this->options['array'])) {
2873
            // 返回模型对象
2874
            $this->resultToModel($result, $this->options);
2875
        } else {
2876
            $this->result($result);
2877
        }
2878
2879
        return $result;
2880
    }
2881
2882
    /**
2883
     * 处理空数据
2884
     * @access protected
2885
     * @return array|Model|null
2886
     * @throws DbException
2887
     * @throws ModelNotFoundException
2888
     * @throws DataNotFoundException
2889
     */
2890
    protected function resultToEmpty()
2891
    {
2892
        if (!empty($this->options['fail'])) {
2893
            $this->throwNotFound();
2894
        }
2895
2896
        return !empty($this->model) && empty($this->options['array']) ? $this->model->newInstance([], true) : [];
2897
    }
2898
2899
    /**
2900
     * 获取模型的更新条件
2901
     * @access protected
2902
     * @param  array $options 查询参数
2903
     */
2904
    protected function getModelUpdateCondition(array $options)
2905
    {
2906
        return $options['where']['AND'] ?? null;
2907
    }
2908
2909
    /**
2910
     * 处理数据
2911
     * @access protected
2912
     * @param  array $result     查询数据
2913
     * @return void
2914
     */
2915
    protected function result(array &$result): void
2916
    {
2917
        if (!empty($this->options['json'])) {
2918
            $this->jsonResult($result, $this->options['json'], true);
2919
        }
2920
2921
        if (!empty($this->options['with_attr'])) {
2922
            $this->getResultAttr($result, $this->options['with_attr']);
2923
        }
2924
2925
        $this->filterResult($result);
2926
    }
2927
2928
    /**
2929
     * 处理数据的可见和隐藏
2930
     * @access protected
2931
     * @param  array $result     查询数据
2932
     * @return void
2933
     */
2934
    protected function filterResult(&$result): void
2935
    {
2936
        if (!empty($this->options['visible'])) {
2937
            foreach ($this->options['visible'] as $key) {
2938
                $array[] = $key;
2939
            }
2940
            $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 2937. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2941
        } elseif (!empty($this->options['hidden'])) {
2942
            foreach ($this->options['hidden'] as $key) {
2943
                $array[] = $key;
2944
            }
2945
            $result = array_diff_key($result, array_flip($array));
2946
        }
2947
    }
2948
2949
    /**
2950
     * 使用获取器处理数据
2951
     * @access protected
2952
     * @param  array $result     查询数据
2953
     * @param  array $withAttr   字段获取器
2954
     * @return void
2955
     */
2956
    protected function getResultAttr(array &$result, array $withAttr = []): void
2957
    {
2958
        foreach ($withAttr as $name => $closure) {
2959
            $name = App::parseName($name);
2960
2961
            if (strpos($name, '.')) {
2962
                // 支持JSON字段 获取器定义
2963
                list($key, $field) = explode('.', $name);
2964
2965
                if (isset($result[$key])) {
2966
                    $result[$key][$field] = $closure($result[$key][$field] ?? null, $result[$key]);
2967
                }
2968
            } else {
2969
                $result[$name] = $closure($result[$name] ?? null, $result);
2970
            }
2971
        }
2972
    }
2973
2974
    /**
2975
     * JSON字段数据转换
2976
     * @access protected
2977
     * @param  array $result            查询数据
2978
     * @param  array $json              JSON字段
2979
     * @param  bool  $assoc             是否转换为数组
2980
     * @param  array $withRelationAttr  关联获取器
2981
     * @return void
2982
     */
2983
    protected function jsonResult(array &$result, array $json = [], bool $assoc = false, array $withRelationAttr = []): void
2984
    {
2985
        foreach ($json as $name) {
2986
            if (!isset($result[$name])) {
2987
                continue;
2988
            }
2989
2990
            $result[$name] = json_decode($result[$name], $assoc);
2991
2992
            if (!isset($withRelationAttr[$name])) {
2993
                continue;
2994
            }
2995
2996
            foreach ($withRelationAttr[$name] as $key => $closure) {
2997
                $data = get_object_vars($result[$name]);
2998
2999
                $result[$name]->$key = $closure($result[$name]->$key ?? null, $data);
3000
            }
3001
        }
3002
    }
3003
3004
    /**
3005
     * 查询数据转换为模型对象
3006
     * @access protected
3007
     * @param  array $result            查询数据
3008
     * @param  array $options           查询参数
3009
     * @param  bool  $resultSet         是否为数据集查询
3010
     * @param  array $withRelationAttr  关联字段获取器
3011
     * @return void
3012
     */
3013
    protected function resultToModel(array &$result, array $options = [], bool $resultSet = false, array $withRelationAttr = []): void
3014
    {
3015
        // 动态获取器
3016
        if (!empty($options['with_attr']) && empty($withRelationAttr)) {
3017
            foreach ($options['with_attr'] as $name => $val) {
3018
                if (strpos($name, '.')) {
3019
                    list($relation, $field) = explode('.', $name);
3020
3021
                    $withRelationAttr[$relation][$field] = $val;
3022
                    unset($options['with_attr'][$name]);
3023
                }
3024
            }
3025
        }
3026
3027
        // JSON 数据处理
3028
        if (!empty($options['json'])) {
3029
            $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr);
3030
        }
3031
3032
        $result = $this->model->newInstance($result, true, $resultSet ? null : $this->getModelUpdateCondition($options));
3033
3034
        // 动态获取器
3035
        if (!empty($options['with_attr'])) {
3036
            $result->withAttribute($options['with_attr']);
3037
        }
3038
3039
        // 输出属性控制
3040
        if (!empty($options['visible'])) {
3041
            $result->visible($options['visible']);
3042
        } elseif (!empty($options['hidden'])) {
3043
            $result->hidden($options['hidden']);
3044
        }
3045
3046
        if (!empty($options['append'])) {
3047
            $result->append($options['append']);
3048
        }
3049
3050
        // 关联查询
3051
        if (!empty($options['relation'])) {
3052
            $result->relationQuery($options['relation']);
3053
        }
3054
3055
        // 预载入查询
3056
        if (!$resultSet && !empty($options['with'])) {
3057
            $result->eagerlyResult($result, $options['with'], $withRelationAttr);
3058
        }
3059
3060
        // JOIN预载入查询
3061
        if (!$resultSet && !empty($options['with_join'])) {
3062
            $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, true);
3063
        }
3064
3065
        // 关联统计
3066
        if (!empty($options['with_count'])) {
3067
            foreach ($options['with_count'] as $val) {
3068
                $result->relationCount($result, $val[0], $val[1], $val[2]);
3069
            }
3070
        }
3071
    }
3072
3073
    /**
3074
     * 查询失败 抛出异常
3075
     * @access protected
3076
     * @return void
3077
     * @throws ModelNotFoundException
3078
     * @throws DataNotFoundException
3079
     */
3080
    protected function throwNotFound(): void
3081
    {
3082
        if (!empty($this->model)) {
3083
            $class = get_class($this->model);
3084
            throw new ModelNotFoundException('model data Not Found:' . $class, $class, $this->options);
3085
        }
3086
3087
        $table = $this->getTable();
3088
        throw new DataNotFoundException('table data not Found:' . $table, $table, $this->options);
3089
    }
3090
3091
    /**
3092
     * 查找多条记录 如果不存在则抛出异常
3093
     * @access public
3094
     * @param  array|string|Query|Closure $data
3095
     * @return array|PDOStatement|string|Model
3096
     * @throws DbException
3097
     * @throws ModelNotFoundException
3098
     * @throws DataNotFoundException
3099
     */
3100
    public function selectOrFail($data = null)
3101
    {
3102
        return $this->failException(true)->select($data);
3103
    }
3104
3105
    /**
3106
     * 查找单条记录 如果不存在则抛出异常
3107
     * @access public
3108
     * @param  array|string|Query|Closure $data
3109
     * @return array|PDOStatement|string|Model
3110
     * @throws DbException
3111
     * @throws ModelNotFoundException
3112
     * @throws DataNotFoundException
3113
     */
3114
    public function findOrFail($data = null)
3115
    {
3116
        return $this->failException(true)->find($data);
3117
    }
3118
3119
    /**
3120
     * 分批数据返回处理
3121
     * @access public
3122
     * @param  integer      $count    每次处理的数据数量
3123
     * @param  callable     $callback 处理回调方法
3124
     * @param  string|array $column   分批处理的字段名
3125
     * @param  string       $order    字段排序
3126
     * @return bool
3127
     * @throws DbException
3128
     */
3129
    public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool
3130
    {
3131
        $options = $this->getOptions();
3132
        $column  = $column ?: $this->getPk();
3133
3134
        if (isset($options['order'])) {
3135
            if (Container::pull('app')->isDebug()) {
3136
                throw new DbException('chunk not support call order');
3137
            }
3138
            unset($options['order']);
3139
        }
3140
3141
        $bind = $this->bind;
3142
3143
        if (is_array($column)) {
3144
            $times = 1;
3145
            $query = $this->options($options)->page($times, $count);
3146
        } else {
3147
            $query = $this->options($options)->limit($count);
3148
3149
            if (strpos($column, '.')) {
3150
                list($alias, $key) = explode('.', $column);
3151
            } else {
3152
                $key = $column;
3153
            }
3154
        }
3155
3156
        $resultSet = $query->order($column, $order)->select();
3157
3158
        while (count($resultSet) > 0) {
3159
            if ($resultSet instanceof Collection) {
3160
                $resultSet = $resultSet->all();
3161
            }
3162
3163
            if (false === call_user_func($callback, $resultSet)) {
3164
                return false;
3165
            }
3166
3167
            if (isset($times)) {
3168
                $times++;
3169
                $query = $this->options($options)->page($times, $count);
3170
            } else {
3171
                $end    = end($resultSet);
3172
                $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...
3173
3174
                $query = $this->options($options)
3175
                    ->limit($count)
3176
                    ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
3177
            }
3178
3179
            $resultSet = $query->bind($bind)->order($column, $order)->select();
3180
        }
3181
3182
        return true;
3183
    }
3184
3185
    /**
3186
     * 获取绑定的参数 并清空
3187
     * @access public
3188
     * @param  bool $clear
3189
     * @return array
3190
     */
3191
    public function getBind(bool $clear = true): array
3192
    {
3193
        $bind = $this->bind;
3194
        if ($clear) {
3195
            $this->bind = [];
3196
        }
3197
3198
        return $bind;
3199
    }
3200
3201
    /**
3202
     * 创建子查询SQL
3203
     * @access public
3204
     * @param  bool $sub
3205
     * @return string
3206
     * @throws DbException
3207
     */
3208
    public function buildSql(bool $sub = true): string
3209
    {
3210
        return $sub ? '( ' . $this->fetchSql()->select() . ' )' : $this->fetchSql()->select();
3211
    }
3212
3213
    /**
3214
     * 视图查询处理
3215
     * @access protected
3216
     * @param  array   $options    查询参数
3217
     * @return void
3218
     */
3219
    protected function parseView(array &$options): void
3220
    {
3221
        foreach (['AND', 'OR'] as $logic) {
3222
            if (isset($options['where'][$logic])) {
3223
                foreach ($options['where'][$logic] as $key => $val) {
3224
                    if (array_key_exists($key, $options['map'])) {
3225
                        array_shift($val);
3226
                        array_unshift($val, $options['map'][$key]);
3227
                        $options['where'][$logic][$options['map'][$key]] = $val;
3228
                        unset($options['where'][$logic][$key]);
3229
                    }
3230
                }
3231
            }
3232
        }
3233
3234
        if (isset($options['order'])) {
3235
            // 视图查询排序处理
3236
            foreach ($options['order'] as $key => $val) {
3237
                if (is_numeric($key) && is_string($val)) {
3238
                    if (strpos($val, ' ')) {
3239
                        list($field, $sort) = explode(' ', $val);
3240
                        if (array_key_exists($field, $options['map'])) {
3241
                            $options['order'][$options['map'][$field]] = $sort;
3242
                            unset($options['order'][$key]);
3243
                        }
3244
                    } elseif (array_key_exists($val, $options['map'])) {
3245
                        $options['order'][$options['map'][$val]] = 'asc';
3246
                        unset($options['order'][$key]);
3247
                    }
3248
                } elseif (array_key_exists($key, $options['map'])) {
3249
                    $options['order'][$options['map'][$key]] = $val;
3250
                    unset($options['order'][$key]);
3251
                }
3252
            }
3253
        }
3254
    }
3255
3256
    protected function parseUpdateData(&$data)
3257
    {
3258
        $pk = $this->getPk();
3259
        // 如果存在主键数据 则自动作为更新条件
3260
        if (is_string($pk) && isset($data[$pk])) {
3261
            $this->where($pk, '=', $data[$pk]);
3262
            $this->options['key'] = $data[$pk];
3263
            unset($data[$pk]);
3264
        } elseif (is_array($pk)) {
3265
            // 增加复合主键支持
3266
            foreach ($pk as $field) {
3267
                if (isset($data[$field])) {
3268
                    $this->where($field, '=', $data[$field]);
3269
                } else {
3270
                    // 如果缺少复合主键数据则不执行
3271
                    throw new Exception('miss complex primary data');
3272
                }
3273
                unset($data[$field]);
3274
            }
3275
        }
3276
    }
3277
3278
    /**
3279
     * 把主键值转换为查询条件 支持复合主键
3280
     * @access public
3281
     * @param  array|string $data    主键数据
3282
     * @return void
3283
     * @throws Exception
3284
     */
3285
    public function parsePkWhere($data): void
3286
    {
3287
        $pk = $this->getPk();
3288
3289
        if (is_string($pk)) {
3290
            // 获取数据表
3291
            if (empty($this->options['table'])) {
3292
                $this->options['table'] = $this->getTable();
3293
            }
3294
3295
            $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table'];
3296
3297
            if (!empty($this->options['alias'][$table])) {
3298
                $alias = $this->options['alias'][$table];
3299
            }
3300
3301
            $key = isset($alias) ? $alias . '.' . $pk : $pk;
3302
            // 根据主键查询
3303
            if (is_array($data)) {
3304
                $this->where($key, 'in', $data);
3305
            } else {
3306
                $this->where($key, '=', $data);
3307
                $this->options['key'] = $data;
3308
            }
3309
        }
3310
    }
3311
3312
    /**
3313
     * 分析表达式(可用于查询或者写入操作)
3314
     * @access public
3315
     * @return array
3316
     */
3317
    public function parseOptions(): array
3318
    {
3319
        $options = $this->getOptions();
3320
3321
        // 获取数据表
3322
        if (empty($options['table'])) {
3323
            $options['table'] = $this->getTable();
3324
        }
3325
3326
        if (!isset($options['where'])) {
3327
            $options['where'] = [];
3328
        } elseif (isset($options['view'])) {
3329
            // 视图查询条件处理
3330
            $this->parseView($options);
3331
        }
3332
3333
        if (!isset($options['field'])) {
3334
            $options['field'] = '*';
3335
        }
3336
3337
        foreach (['data', 'order', 'join', 'union'] as $name) {
3338
            if (!isset($options[$name])) {
3339
                $options[$name] = [];
3340
            }
3341
        }
3342
3343
        if (!isset($options['strict'])) {
3344
            $options['strict'] = $this->connection->getConfig('fields_strict');
3345
        }
3346
3347
        foreach (['master', 'lock', 'fetch_sql', 'array', 'distinct', 'procedure'] as $name) {
3348
            if (!isset($options[$name])) {
3349
                $options[$name] = false;
3350
            }
3351
        }
3352
3353
        foreach (['group', 'having', 'limit', 'force', 'comment', 'partition', 'duplicate', 'extra'] as $name) {
3354
            if (!isset($options[$name])) {
3355
                $options[$name] = '';
3356
            }
3357
        }
3358
3359
        if (isset($options['page'])) {
3360
            // 根据页数计算limit
3361
            list($page, $listRows) = $options['page'];
3362
            $page                  = $page > 0 ? $page : 1;
3363
            $listRows              = $listRows ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20);
3364
            $offset                = $listRows * ($page - 1);
3365
            $options['limit']      = $offset . ',' . $listRows;
3366
        }
3367
3368
        $this->options = $options;
3369
3370
        return $options;
3371
    }
3372
3373
    /**
3374
     * 注册回调方法
3375
     * @access public
3376
     * @param  string   $event    事件名
3377
     * @param  callable $callback 回调方法
3378
     * @return void
3379
     */
3380
    public static function event(string $event, callable $callback): void
3381
    {
3382
        self::$event[$event] = $callback;
3383
    }
3384
3385
    /**
3386
     * 触发事件
3387
     * @access public
3388
     * @param  string $event   事件名
3389
     * @return mixed
3390
     */
3391
    public function trigger(string $event)
3392
    {
3393
        $result = false;
3394
3395
        if (isset(self::$event[$event])) {
3396
            $result = Container::getInstance()->invoke(self::$event[$event], [$this]);
3397
        }
3398
3399
        return $result;
3400
    }
3401
3402
}
3403