Passed
Push — 5.2 ( 5031d8...9b60ba )
by liu
02:28
created

Query::withBind()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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