Passed
Push — 5.2 ( bed7ed...545fbb )
by liu
03:57
created

Query::rightJoin()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 3
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
// +----------------------------------------------------------------------
1 ignored issue
show
Coding Style introduced by
You must use "/**" style comments for a file comment
Loading history...
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\facade\Db;
28
use think\Model;
29
use think\model\Collection as ModelCollection;
30
use think\model\Relation;
31
use think\model\relation\OneToOne;
32
33
class Query
1 ignored issue
show
Coding Style introduced by
Missing class doc comment
Loading history...
34
{
35
    /**
36
     * 当前数据库连接对象
37
     * @var Connection
38
     */
39
    protected $connection;
40
41
    /**
42
     * 当前模型对象
43
     * @var Model
44
     */
45
    protected $model;
46
47
    /**
48
     * 当前数据表名称(不含前缀)
49
     * @var string
50
     */
51
    protected $name = '';
52
53
    /**
54
     * 当前数据表主键
55
     * @var string|array
56
     */
57
    protected $pk;
58
59
    /**
60
     * 当前数据表前缀
61
     * @var string
62
     */
63
    protected $prefix = '';
64
65
    /**
66
     * 当前查询参数
67
     * @var array
68
     */
69
    protected $options = [];
70
71
    /**
72
     * 分页查询配置
73
     * @var array
74
     */
75
    protected $paginateConfig = [
76
        'query'     => [], //url额外参数
77
        'fragment'  => '', //url锚点
78
        'type'      => 'bootstrap', //分页类名
79
        'var_page'  => 'page', //分页变量
80
        'list_rows' => 15, //每页数量
81
    ];
82
83
    /**
84
     * 当前参数绑定
85
     * @var array
86
     */
87
    protected $bind = [];
88
89
    /**
90
     * 事件回调
91
     * @var array
92
     */
93
    protected static $event = [];
94
95
    /**
96
     * 日期查询表达式
97
     * @var array
98
     */
99
    protected $timeRule = [
100
        'today'      => ['today', 'tomorrow'],
101
        'yesterday'  => ['yesterday', 'today'],
102
        'week'       => ['this week 00:00:00', 'next week 00:00:00'],
103
        'last week'  => ['last week 00:00:00', 'this week 00:00:00'],
104
        'month'      => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00'],
105
        'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00'],
106
        'year'       => ['this year 1/1', 'next year 1/1'],
107
        'last year'  => ['last year 1/1', 'this year 1/1'],
108
    ];
109
110
    /**
111
     * 架构函数
112
     * @access public
113
     * @param  Connection      $connection 数据库连接对象
114
     */
115
    public function __construct(Connection $connection)
116
    {
117
        $this->connection = $connection;
118
119
        $this->prefix = $this->connection->getConfig('prefix');
120
    }
121
122
    /**
123
     * 创建一个新的查询对象
124
     * @access public
125
     * @return Query
126
     */
127
    public function newQuery()
128
    {
129
        return new static($this->connection);
130
    }
131
132
    /**
133
     * 利用__call方法实现一些特殊的Model方法
134
     * @access public
135
     * @param  string $method 方法名称
136
     * @param  array  $args   调用参数
137
     * @return mixed
138
     * @throws DbException
139
     * @throws Exception
140
     */
141
    public function __call(string $method, array $args)
142
    {
143
        if (strtolower(substr($method, 0, 5)) == 'getby') {
144
            // 根据某个字段获取记录
145
            $field = App::parseName(substr($method, 5));
146
            return $this->where($field, '=', $args[0])->find();
147
        } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
148
            // 根据某个字段获取记录的某个值
149
            $name = App::parseName(substr($method, 10));
150
            return $this->where($name, '=', $args[0])->value($args[1]);
151
        } elseif (strtolower(substr($method, 0, 7)) == 'whereor') {
152
            $name = App::parseName(substr($method, 7));
153
            array_unshift($args, $name);
154
            return call_user_func_array([$this, 'whereOr'], $args);
155
        } elseif (strtolower(substr($method, 0, 5)) == 'where') {
156
            $name = App::parseName(substr($method, 5));
157
            array_unshift($args, $name);
158
            return call_user_func_array([$this, 'where'], $args);
159
        } elseif ($this->model && method_exists($this->model, 'scope' . $method)) {
160
            // 动态调用命名范围
161
            $method = 'scope' . $method;
162
            array_unshift($args, $this);
163
164
            call_user_func_array([$this->model, $method], $args);
165
            return $this;
166
        } else {
167
            throw new Exception('method not exist:' . static::class . '->' . $method);
168
        }
169
    }
170
171
    /**
172
     * 获取当前的数据库Connection对象
173
     * @access public
174
     * @return Connection
175
     */
176
    public function getConnection()
177
    {
178
        return $this->connection;
179
    }
180
181
    /**
182
     * 设置当前的数据库Connection对象
183
     * @access public
184
     * @param  Connection      $connection 数据库连接对象
185
     * @return $this
186
     */
187
    public function setConnection($connection)
188
    {
189
        $this->connection = $connection;
190
191
        return $this;
192
    }
193
194
    /**
195
     * 指定模型
196
     * @access public
197
     * @param  Model $model 模型对象实例
198
     * @return $this
199
     */
200
    public function model(Model $model)
201
    {
202
        $this->model = $model;
203
        return $this;
204
    }
205
206
    /**
207
     * 获取当前的模型对象
208
     * @access public
209
     * @return Model|null
210
     */
211
    public function getModel()
212
    {
213
        return $this->model ? $this->model->setQuery($this) : null;
214
    }
215
216
    /**
217
     * 指定当前数据表名(不含前缀)
218
     * @access public
219
     * @param  string $name 不含前缀的数据表名字
220
     * @return $this
221
     */
222
    public function name(string $name)
223
    {
224
        $this->name = $name;
225
        return $this;
226
    }
227
228
    /**
229
     * 获取当前的数据表名称
230
     * @access public
231
     * @return string
232
     */
233
    public function getName(): string
234
    {
235
        return $this->name ?: $this->model->getName();
236
    }
237
238
    /**
239
     * 获取数据库的配置参数
240
     * @access public
241
     * @param  string $name 参数名称
242
     * @return mixed
243
     */
244
    public function getConfig(string $name = '')
245
    {
246
        return $this->connection->getConfig($name);
247
    }
248
249
    /**
250
     * 得到当前或者指定名称的数据表
251
     * @access public
252
     * @param  string $name 不含前缀的数据表名字
253
     * @return string
254
     */
255
    public function getTable(string $name = ''): string
256
    {
257
        if (empty($name) && isset($this->options['table'])) {
258
            return $this->options['table'];
259
        }
260
261
        $name = $name ?: $this->name;
262
263
        return $this->prefix . App::parseName($name);
264
    }
265
266
    /**
267
     * 获取数据表字段信息
268
     * @access public
269
     * @param  string $tableName 数据表名
270
     * @return array
271
     */
272
    public function getTableFields($tableName = ''): array
273
    {
274
        if ('' == $tableName) {
275
            $tableName = $this->options['table'] ?? $this->getTable();
276
        }
277
278
        return $this->connection->getTableFields($tableName);
279
    }
280
281
    /**
282
     * 设置字段类型信息
283
     * @access public
284
     * @param  array $type 字段类型信息
285
     * @return $this
286
     */
287
    public function setFieldType(array $type)
288
    {
289
        $this->options['field_type'] = $type;
290
        return $this;
291
    }
292
293
    /**
294
     * 获取字段类型信息
295
     * @access public
296
     * @return array
297
     */
298
    public function getFieldsType(): array
299
    {
300
        if (!empty($this->options['field_type'])) {
301
            return $this->options['field_type'];
302
        }
303
304
        $tableName = $this->options['table'] ?? $this->getTable();
305
306
        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...
307
    }
308
309
    /**
310
     * 获取字段类型信息
311
     * @access public
312
     * @param  string $field 字段名
313
     * @return string|null
314
     */
315
    public function getFieldType(string $field)
316
    {
317
        $fieldType = $this->getFieldsType();
318
319
        return $fieldType[$field] ?? null;
320
    }
321
322
    /**
323
     * 获取字段类型信息
324
     * @access public
325
     * @return array
326
     */
327
    public function getFieldsBindType(): array
328
    {
329
        $fieldType = $this->getFieldsType();
330
331
        return array_map([$this->connection, 'getFieldBindType'], $fieldType);
332
    }
333
334
    /**
335
     * 获取字段类型信息
336
     * @access public
337
     * @param  string $field 字段名
338
     * @return int
339
     */
340
    public function getFieldBindType(string $field): int
341
    {
342
        $fieldType = $this->getFieldType($field);
343
344
        return $this->connection->getFieldBindType($fieldType ?: '');
345
    }
346
347
    /**
348
     * 执行查询 返回数据集
349
     * @access public
350
     * @param  string $sql    sql指令
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 4 found
Loading history...
351
     * @param  array  $bind   参数绑定
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
352
     * @return array
353
     * @throws BindParamException
354
     * @throws PDOException
355
     */
356
    public function query(string $sql, array $bind = []): array
357
    {
358
        return $this->connection->query($this, $sql, $bind, true);
359
    }
360
361
    /**
362
     * 执行语句
363
     * @access public
364
     * @param  string $sql  sql指令
365
     * @param  array  $bind 参数绑定
366
     * @return int
367
     * @throws BindParamException
368
     * @throws PDOException
369
     */
370
    public function execute(string $sql, array $bind = []): int
371
    {
372
        return $this->connection->execute($this, $sql, $bind, true);
373
    }
374
375
    /**
376
     * 监听SQL执行
377
     * @access public
378
     * @param  callable $callback 回调方法
379
     * @return void
380
     */
381
    public function listen(callable $callback): void
382
    {
383
        $this->connection->listen($callback);
384
    }
385
386
    /**
387
     * 获取最近插入的ID
388
     * @access public
389
     * @param  string $sequence 自增序列名
390
     * @return string
391
     */
392
    public function getLastInsID(string $sequence = null): string
393
    {
394
        return $this->connection->getLastInsID($sequence);
395
    }
396
397
    /**
398
     * 获取返回或者影响的记录数
399
     * @access public
400
     * @return integer
401
     */
402
    public function getNumRows(): int
403
    {
404
        return $this->connection->getNumRows();
405
    }
406
407
    /**
408
     * 获取最近一次查询的sql语句
409
     * @access public
410
     * @return string
411
     */
412
    public function getLastSql(): string
413
    {
414
        return $this->connection->getLastSql();
415
    }
416
417
    /**
418
     * 执行数据库事务
419
     * @access public
420
     * @param  callable $callback 数据操作方法回调
421
     * @return mixed
422
     */
423
    public function transaction(callable $callback)
424
    {
425
        return $this->connection->transaction($callback);
426
    }
427
428
    /**
429
     * 启动事务
430
     * @access public
431
     * @return void
432
     */
433
    public function startTrans(): void
434
    {
435
        $this->connection->startTrans();
436
    }
437
438
    /**
439
     * 用于非自动提交状态下面的查询提交
440
     * @access public
441
     * @return void
442
     * @throws PDOException
443
     */
444
    public function commit(): void
445
    {
446
        $this->connection->commit();
447
    }
448
449
    /**
450
     * 事务回滚
451
     * @access public
452
     * @return void
453
     * @throws PDOException
454
     */
455
    public function rollback(): void
456
    {
457
        $this->connection->rollback();
458
    }
459
460
    /**
461
     * 批处理执行SQL语句
462
     * 批处理的指令都认为是execute操作
463
     * @access public
464
     * @param  array $sql SQL批处理指令
465
     * @return bool
466
     */
467
    public function batchQuery(array $sql = []): bool
468
    {
469
        return $this->connection->batchQuery($this, $sql);
470
    }
471
472
    /**
473
     * 得到某个字段的值
474
     * @access public
475
     * @param  string $field   字段名
476
     * @param  mixed  $default 默认值
477
     * @return mixed
478
     */
479
    public function value(string $field, $default = null)
480
    {
481
        return $this->connection->value($this, $field, $default);
482
    }
483
484
    /**
485
     * 得到某个列的数组
486
     * @access public
487
     * @param  string $field 字段名 多个字段用逗号分隔
488
     * @param  string $key   索引
489
     * @return array
490
     */
491
    public function column(string $field, string $key = ''): array
492
    {
493
        return $this->connection->column($this, $field, $key);
494
    }
495
496
    /**
497
     * 聚合查询
498
     * @access protected
499
     * @param  string     $aggregate    聚合方法
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
500
     * @param  string|Raw $field        字段名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 8 found
Loading history...
501
     * @param  bool       $force        强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 8 found
Loading history...
502
     * @return mixed
503
     */
504
    protected function aggregate(string $aggregate, $field, bool $force = false)
505
    {
506
        return $this->connection->aggregate($this, $aggregate, $field, $force);
507
    }
508
509
    /**
510
     * COUNT查询
511
     * @access public
512
     * @param  string|Raw $field 字段名
513
     * @return int
514
     */
515
    public function count(string $field = '*'): int
516
    {
517
        if (!empty($this->options['group'])) {
518
            // 支持GROUP
519
            $options = $this->getOptions();
520
            $subSql  = $this->options($options)->field('count(' . $field . ') AS think_count')->bind($this->bind)->buildSql();
521
522
            $query = $this->newQuery()->table([$subSql => '_group_count_']);
523
524
            $count = $query->aggregate('COUNT', '*');
525
        } else {
526
            $count = $this->aggregate('COUNT', $field);
527
        }
528
529
        return (int) $count;
530
    }
531
532
    /**
533
     * SUM查询
534
     * @access public
535
     * @param  string|Raw $field 字段名
536
     * @return float
537
     */
538
    public function sum($field): float
539
    {
540
        return $this->aggregate('SUM', $field, true);
541
    }
542
543
    /**
544
     * MIN查询
545
     * @access public
546
     * @param  string|Raw $field    字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
547
     * @param  bool       $force    强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
548
     * @return mixed
549
     */
550
    public function min($field, bool $force = true)
551
    {
552
        return $this->aggregate('MIN', $field, $force);
553
    }
554
555
    /**
556
     * MAX查询
557
     * @access public
558
     * @param  string|Raw $field    字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
559
     * @param  bool       $force    强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
560
     * @return mixed
561
     */
562
    public function max($field, bool $force = true)
563
    {
564
        return $this->aggregate('MAX', $field, $force);
565
    }
566
567
    /**
568
     * AVG查询
569
     * @access public
570
     * @param  string|Raw $field 字段名
571
     * @return float
572
     */
573
    public function avg($field): float
574
    {
575
        return $this->aggregate('AVG', $field, true);
576
    }
577
578
    /**
579
     * 查询SQL组装 join
580
     * @access public
581
     * @param  mixed  $join      关联的表名
582
     * @param  mixed  $condition 条件
583
     * @param  string $type      JOIN类型
584
     * @param  array  $bind      参数绑定
585
     * @return $this
586
     */
587
    public function join($join, string $condition = null, string $type = 'INNER', array $bind = [])
588
    {
589
        $table = $this->getJoinTable($join);
590
591
        if (!empty($bind) && $condition) {
592
            $this->bindParams($condition, $bind);
593
        }
594
595
        $this->options['join'][] = [$table, strtoupper($type), $condition];
596
597
        return $this;
598
    }
599
600
    /**
601
     * LEFT JOIN
602
     * @access public
603
     * @param  mixed  $join      关联的表名
604
     * @param  mixed  $condition 条件
605
     * @param  array  $bind      参数绑定
606
     * @return $this
607
     */
608
    public function leftJoin($join, string $condition = null, array $bind = [])
609
    {
610
        return $this->join($join, $condition, 'LEFT', $bind);
611
    }
612
613
    /**
614
     * RIGHT JOIN
615
     * @access public
616
     * @param  mixed  $join      关联的表名
617
     * @param  mixed  $condition 条件
618
     * @param  array  $bind      参数绑定
619
     * @return $this
620
     */
621
    public function rightJoin($join, string $condition = null, array $bind = [])
622
    {
623
        return $this->join($join, $condition, 'RIGHT', $bind);
624
    }
625
626
    /**
627
     * FULL JOIN
628
     * @access public
629
     * @param  mixed  $join      关联的表名
630
     * @param  mixed  $condition 条件
631
     * @param  array  $bind      参数绑定
632
     * @return $this
633
     */
634
    public function fullJoin($join, string $condition = null, array $bind = [])
635
    {
636
        return $this->join($join, $condition, 'FULL');
637
    }
638
639
    /**
640
     * 获取Join表名及别名 支持
641
     * ['prefix_table或者子查询'=>'alias'] 'table alias'
642
     * @access protected
643
     * @param  array|string|Raw $join  JION表名
644
     * @param  string           $alias 别名
645
     * @return string|array
646
     */
647
    protected function getJoinTable($join, &$alias = null)
648
    {
649
        if (is_array($join)) {
650
            $table = $join;
651
            $alias = array_shift($join);
652
            return $table;
653
        } elseif ($join instanceof Raw) {
654
            return $join;
655
        }
656
657
        $join = trim($join);
658
659
        if (false !== strpos($join, '(')) {
660
            // 使用子查询
661
            $table = $join;
662
        } else {
663
            // 使用别名
664
            if (strpos($join, ' ')) {
665
                // 使用别名
666
                list($table, $alias) = explode(' ', $join);
667
            } else {
668
                $table = $join;
669
                if (false === strpos($join, '.')) {
670
                    $alias = $join;
671
                }
672
            }
673
674
            if ($this->prefix && false === strpos($table, '.') && 0 !== strpos($table, $this->prefix)) {
675
                $table = $this->getTable($table);
676
            }
677
        }
678
679
        if (!empty($alias) && $table != $alias) {
680
            $table = [$table => $alias];
681
        }
682
683
        return $table;
684
    }
685
686
    /**
687
     * 查询SQL组装 union
688
     * @access public
689
     * @param  mixed   $union UNION
690
     * @param  boolean $all   是否适用UNION ALL
691
     * @return $this
692
     */
693
    public function union($union, bool $all = false)
694
    {
695
        $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION';
696
697
        if (is_array($union)) {
698
            $this->options['union'] = array_merge($this->options['union'], $union);
699
        } else {
700
            $this->options['union'][] = $union;
701
        }
702
703
        return $this;
704
    }
705
706
    /**
707
     * 查询SQL组装 union all
708
     * @access public
709
     * @param  mixed   $union UNION数据
710
     * @return $this
711
     */
712
    public function unionAll($union)
713
    {
714
        return $this->union($union, true);
715
    }
716
717
    /**
718
     * 指定查询字段 支持字段排除和指定数据表
719
     * @access public
720
     * @param  mixed   $field     字段信息
721
     * @param  boolean $except    是否排除
722
     * @param  string  $tableName 数据表名
723
     * @param  string  $prefix    字段前缀
724
     * @param  string  $alias     别名前缀
725
     * @return $this
726
     */
727
    public function field($field, bool $except = false, string $tableName = '', string $prefix = '', string $alias = '')
728
    {
729
        if (empty($field)) {
730
            return $this;
731
        } elseif ($field instanceof Raw) {
732
            $this->options['field'][] = $field;
733
            return $this;
734
        }
735
736
        if (is_string($field)) {
737
            if (preg_match('/[\<\'\"\(]/', $field)) {
738
                return $this->fieldRaw($field);
739
            }
740
741
            $field = array_map('trim', explode(',', $field));
742
        }
743
744
        if (true === $field) {
745
            // 获取全部字段
746
            $fields = $this->getTableFields($tableName);
747
            $field  = $fields ?: ['*'];
0 ignored issues
show
introduced by
$fields is an empty array, thus is always false.
Loading history...
748
        } elseif ($except) {
749
            // 字段排除
750
            $fields = $this->getTableFields($tableName);
751
            $field  = $fields ? array_diff($fields, $field) : $field;
0 ignored issues
show
introduced by
$fields is an empty array, thus is always false.
Loading history...
752
        }
753
754
        if ($tableName) {
755
            // 添加统一的前缀
756
            $prefix = $prefix ?: $tableName;
757
            foreach ($field as $key => &$val) {
758
                if (is_numeric($key) && $alias) {
759
                    $field[$prefix . '.' . $val] = $alias . $val;
760
                    unset($field[$key]);
761
                } elseif (is_numeric($key)) {
762
                    $val = $prefix . '.' . $val;
763
                }
764
            }
765
        }
766
767
        if (isset($this->options['field'])) {
768
            $field = array_merge((array) $this->options['field'], $field);
769
        }
770
771
        $this->options['field'] = array_unique($field);
772
773
        return $this;
774
    }
775
776
    /**
777
     * 表达式方式指定查询字段
778
     * @access public
779
     * @param  string $field 字段名
780
     * @return $this
781
     */
782
    public function fieldRaw(string $field)
783
    {
784
        $this->options['field'][] = new Raw($field);
785
786
        return $this;
787
    }
788
789
    /**
790
     * 设置数据
791
     * @access public
792
     * @param  array $data 数据
793
     * @return $this
794
     */
795
    public function data(array $data)
796
    {
797
        $this->options['data'] = $data;
798
799
        return $this;
800
    }
801
802
    /**
803
     * 字段值增长
804
     * @access public
805
     * @param  string  $field 字段名
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
806
     * @param  integer $step  增长值
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 2 found
Loading history...
807
     * @param  integer $lazyTime 延时时间(s)
808
     * @param  string  $op INC/DEC
0 ignored issues
show
Coding Style introduced by
Expected 7 spaces after parameter name; 1 found
Loading history...
809
     * @return $this
810
     */
811
    public function inc(string $field, int $step = 1, int $lazyTime = 0, string $op = 'INC')
812
    {
813
        if ($lazyTime > 0) {
814
            // 延迟写入
815
            $condition = $this->options['where'] ?? [];
816
817
            $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition));
818
            $step = $this->connection->lazyWrite($op, $guid, $step, $lazyTime);
819
820
            if (false === $step) {
821
                return $this;
822
            }
823
824
            $op = 'INC';
825
        }
826
827
        $this->options['data'][$field] = [$op, $step];
828
829
        return $this;
830
    }
831
832
    /**
833
     * 字段值减少
834
     * @access public
835
     * @param  string  $field 字段名
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
836
     * @param  integer $step  增长值
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 2 found
Loading history...
837
     * @param  integer $lazyTime 延时时间(s)
838
     * @return $this
839
     */
840
    public function dec(string $field, int $step = 1, int $lazyTime = 0)
841
    {
842
        return $this->inc($field, $step, $lazyTime, 'DEC');
843
    }
844
845
    /**
846
     * 使用表达式设置数据
847
     * @access public
848
     * @param  string $field 字段名
849
     * @param  string $value 字段值
850
     * @return $this
851
     */
852
    public function exp(string $field, string $value)
853
    {
854
        $this->options['data'][$field] = new Raw($value);
855
        return $this;
856
    }
857
858
    /**
859
     * 指定JOIN查询字段
860
     * @access public
861
     * @param  string|array $join  数据表
862
     * @param  string|array $field 查询字段
863
     * @param  string       $on    JOIN条件
864
     * @param  string       $type  JOIN类型
865
     * @param  array        $bind  参数绑定
866
     * @return $this
867
     */
868
    public function view($join, $field = true, $on = null, string $type = 'INNER', array $bind = [])
869
    {
870
        $this->options['view'] = true;
871
872
        $fields = [];
873
        $table  = $this->getJoinTable($join, $alias);
874
875
        if (true === $field) {
876
            $fields = $alias . '.*';
877
        } else {
878
            if (is_string($field)) {
879
                $field = explode(',', $field);
880
            }
881
882
            foreach ($field as $key => $val) {
883
                if (is_numeric($key)) {
884
                    $fields[] = $alias . '.' . $val;
885
886
                    $this->options['map'][$val] = $alias . '.' . $val;
887
                } else {
888
                    if (preg_match('/[,=\.\'\"\(\s]/', $key)) {
889
                        $name = $key;
890
                    } else {
891
                        $name = $alias . '.' . $key;
892
                    }
893
894
                    $fields[] = $name . ' AS ' . $val;
895
896
                    $this->options['map'][$val] = $name;
897
                }
898
            }
899
        }
900
901
        $this->field($fields);
902
903
        if ($on) {
904
            $this->join($table, $on, $type, $bind);
905
        } else {
906
            $this->table($table);
907
        }
908
909
        return $this;
910
    }
911
912
    /**
913
     * 指定AND查询条件
914
     * @access public
915
     * @param  mixed $field     查询字段
916
     * @param  mixed $op        查询表达式
917
     * @param  mixed $condition 查询条件
918
     * @return $this
919
     */
920
    public function where($field, $op = null, $condition = null)
921
    {
922
        if ($field instanceof $this) {
923
            $this->options['where'] = $field->getOptions('where');
924
            return $this;
925
        }
926
927
        $param = func_get_args();
928
        array_shift($param);
929
        return $this->parseWhereExp('AND', $field, $op, $condition, $param);
930
    }
931
932
    /**
933
     * 指定OR查询条件
934
     * @access public
935
     * @param  mixed $field     查询字段
936
     * @param  mixed $op        查询表达式
937
     * @param  mixed $condition 查询条件
938
     * @return $this
939
     */
940
    public function whereOr($field, $op = null, $condition = null)
941
    {
942
        $param = func_get_args();
943
        array_shift($param);
944
        return $this->parseWhereExp('OR', $field, $op, $condition, $param);
945
    }
946
947
    /**
948
     * 指定XOR查询条件
949
     * @access public
950
     * @param  mixed $field     查询字段
951
     * @param  mixed $op        查询表达式
952
     * @param  mixed $condition 查询条件
953
     * @return $this
954
     */
955
    public function whereXor($field, $op = null, $condition = null)
956
    {
957
        $param = func_get_args();
958
        array_shift($param);
959
        return $this->parseWhereExp('XOR', $field, $op, $condition, $param);
960
    }
961
962
    /**
963
     * 指定Null查询条件
964
     * @access public
965
     * @param  mixed  $field 查询字段
966
     * @param  string $logic 查询逻辑 and or xor
967
     * @return $this
968
     */
969
    public function whereNull(string $field, string $logic = 'AND')
970
    {
971
        return $this->parseWhereExp($logic, $field, 'NULL', null, [], true);
972
    }
973
974
    /**
975
     * 指定NotNull查询条件
976
     * @access public
977
     * @param  mixed  $field 查询字段
978
     * @param  string $logic 查询逻辑 and or xor
979
     * @return $this
980
     */
981
    public function whereNotNull(string $field, string $logic = 'AND')
982
    {
983
        return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true);
984
    }
985
986
    /**
987
     * 指定Exists查询条件
988
     * @access public
989
     * @param  mixed  $condition 查询条件
990
     * @param  string $logic     查询逻辑 and or xor
991
     * @return $this
992
     */
993
    public function whereExists($condition, string $logic = 'AND')
994
    {
995
        if (is_string($condition)) {
996
            $condition = new Raw($condition);
997
        }
998
999
        $this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition];
1000
        return $this;
1001
    }
1002
1003
    /**
1004
     * 指定NotExists查询条件
1005
     * @access public
1006
     * @param  mixed  $condition 查询条件
1007
     * @param  string $logic     查询逻辑 and or xor
1008
     * @return $this
1009
     */
1010
    public function whereNotExists($condition, string $logic = 'AND')
1011
    {
1012
        if (is_string($condition)) {
1013
            $condition = new Raw($condition);
1014
        }
1015
1016
        $this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition];
1017
        return $this;
1018
    }
1019
1020
    /**
1021
     * 指定In查询条件
1022
     * @access public
1023
     * @param  mixed  $field     查询字段
1024
     * @param  mixed  $condition 查询条件
1025
     * @param  string $logic     查询逻辑 and or xor
1026
     * @return $this
1027
     */
1028
    public function whereIn(string $field, $condition, string $logic = 'AND')
1029
    {
1030
        return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true);
1031
    }
1032
1033
    /**
1034
     * 指定NotIn查询条件
1035
     * @access public
1036
     * @param  mixed  $field     查询字段
1037
     * @param  mixed  $condition 查询条件
1038
     * @param  string $logic     查询逻辑 and or xor
1039
     * @return $this
1040
     */
1041
    public function whereNotIn(string $field, $condition, string $logic = 'AND')
1042
    {
1043
        return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true);
1044
    }
1045
1046
    /**
1047
     * 指定Like查询条件
1048
     * @access public
1049
     * @param  mixed  $field     查询字段
1050
     * @param  mixed  $condition 查询条件
1051
     * @param  string $logic     查询逻辑 and or xor
1052
     * @return $this
1053
     */
1054
    public function whereLike(string $field, $condition, string $logic = 'AND')
1055
    {
1056
        return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true);
1057
    }
1058
1059
    /**
1060
     * 指定NotLike查询条件
1061
     * @access public
1062
     * @param  mixed  $field     查询字段
1063
     * @param  mixed  $condition 查询条件
1064
     * @param  string $logic     查询逻辑 and or xor
1065
     * @return $this
1066
     */
1067
    public function whereNotLike(string $field, $condition, string $logic = 'AND')
1068
    {
1069
        return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true);
1070
    }
1071
1072
    /**
1073
     * 指定Between查询条件
1074
     * @access public
1075
     * @param  mixed  $field     查询字段
1076
     * @param  mixed  $condition 查询条件
1077
     * @param  string $logic     查询逻辑 and or xor
1078
     * @return $this
1079
     */
1080
    public function whereBetween(string $field, $condition, string $logic = 'AND')
1081
    {
1082
        return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true);
1083
    }
1084
1085
    /**
1086
     * 指定NotBetween查询条件
1087
     * @access public
1088
     * @param  mixed  $field     查询字段
1089
     * @param  mixed  $condition 查询条件
1090
     * @param  string $logic     查询逻辑 and or xor
1091
     * @return $this
1092
     */
1093
    public function whereNotBetween(string $field, $condition, string $logic = 'AND')
1094
    {
1095
        return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true);
1096
    }
1097
1098
    /**
1099
     * 指定FIND_IN_SET查询条件
1100
     * @access public
1101
     * @param  mixed  $field     查询字段
1102
     * @param  mixed  $condition 查询条件
1103
     * @param  string $logic     查询逻辑 and or xor
1104
     * @return $this
1105
     */
1106
    public function whereFindInSet(string $field, $condition, string $logic = 'AND')
1107
    {
1108
        return $this->parseWhereExp($logic, $field, 'FIND IN SET', $condition, [], true);
1109
    }
1110
1111
    /**
1112
     * 比较两个字段
1113
     * @access public
1114
     * @param  string $field1     查询字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 5 found
Loading history...
1115
     * @param  string $operator   比较操作符
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
1116
     * @param  string $field2     比较字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 5 found
Loading history...
1117
     * @param  string $logic      查询逻辑 and or xor
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 6 found
Loading history...
1118
     * @return $this
1119
     */
1120
    public function whereColumn(string $field1, string $operator, string $field2 = null, string $logic = 'AND')
1121
    {
1122
        if (is_null($field2)) {
1123
            $field2   = $operator;
1124
            $operator = '=';
1125
        }
1126
1127
        return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true);
1128
    }
1129
1130
    /**
1131
     * 设置软删除字段及条件
1132
     * @access public
1133
     * @param  string       $field     查询字段
1134
     * @param  mixed        $condition 查询条件
1135
     * @return $this
1136
     */
1137
    public function useSoftDelete(string $field, $condition = null)
1138
    {
1139
        if ($field) {
1140
            $this->options['soft_delete'] = [$field, $condition];
1141
        }
1142
1143
        return $this;
1144
    }
1145
1146
    /**
1147
     * 指定Exp查询条件
1148
     * @access public
1149
     * @param  mixed  $field 查询字段
1150
     * @param  string $where 查询条件
1151
     * @param  array  $bind  参数绑定
1152
     * @param  string $logic 查询逻辑 and or xor
1153
     * @return $this
1154
     */
1155
    public function whereExp(string $field, string $where, array $bind = [], string $logic = 'AND')
1156
    {
1157
        if (!empty($bind)) {
1158
            $this->bindParams($where, $bind);
1159
        }
1160
1161
        $this->options['where'][$logic][] = [$field, 'EXP', new Raw($where)];
1162
1163
        return $this;
1164
    }
1165
1166
    /**
1167
     * 指定字段Raw查询
1168
     * @access public
1169
     * @param  string $field     查询字段表达式
1170
     * @param  mixed  $op        查询表达式
1171
     * @param  string $condition 查询条件
1172
     * @param  string $logic     查询逻辑 and or xor
1173
     * @return $this
1174
     */
1175
    public function whereFieldRaw(string $field, $op, $condition = null, string $logic = 'AND')
1176
    {
1177
        if (is_null($condition)) {
1178
            $condition = $op;
1179
            $op        = '=';
1180
        }
1181
1182
        $this->options['where'][$logic][] = [new Raw($field), $op, $condition];
1183
        return $this;
1184
    }
1185
1186
    /**
1187
     * 指定表达式查询条件
1188
     * @access public
1189
     * @param  string $where 查询条件
1190
     * @param  array  $bind  参数绑定
1191
     * @param  string $logic 查询逻辑 and or xor
1192
     * @return $this
1193
     */
1194
    public function whereRaw(string $where, array $bind = [], string $logic = 'AND')
1195
    {
1196
        if (!empty($bind)) {
1197
            $this->bindParams($where, $bind);
1198
        }
1199
1200
        $this->options['where'][$logic][] = new Raw($where);
1201
1202
        return $this;
1203
    }
1204
1205
    /**
1206
     * 指定表达式查询条件 OR
1207
     * @access public
1208
     * @param  string $where 查询条件
1209
     * @param  array  $bind  参数绑定
1210
     * @return $this
1211
     */
1212
    public function whereOrRaw(string $where, array $bind = [])
1213
    {
1214
        return $this->whereRaw($where, $bind, 'OR');
1215
    }
1216
1217
    /**
1218
     * 分析查询表达式
1219
     * @access protected
1220
     * @param  string $logic     查询逻辑 and or xor
1221
     * @param  mixed  $field     查询字段
1222
     * @param  mixed  $op        查询表达式
1223
     * @param  mixed  $condition 查询条件
1224
     * @param  array  $param     查询参数
1225
     * @param  bool   $strict    严格模式
1226
     * @return $this
1227
     */
1228
    protected function parseWhereExp(string $logic, $field, $op, $condition, array $param = [], bool $strict = false)
1229
    {
1230
        $logic = strtoupper($logic);
1231
1232
        if (is_string($field) && !empty($this->options['via']) && false === strpos($field, '.')) {
1233
            $field = $this->options['via'] . '.' . $field;
1234
        }
1235
1236
        if ($field instanceof Raw) {
1237
            return $this->whereRaw($field, is_array($op) ? $op : []);
1238
        } elseif ($strict) {
1239
            // 使用严格模式查询
1240
            if ('=' == $op) {
1241
                $where = $this->whereEq($field, $condition);
1242
            } else {
1243
                $where = [$field, $op, $condition, $logic];
1244
            }
1245
        } elseif (is_array($field)) {
1246
            // 解析数组批量查询
1247
            return $this->parseArrayWhereItems($field, $logic);
1248
        } elseif ($field instanceof Closure) {
1249
            $where = $field;
1250
        } elseif (is_string($field)) {
1251
            if (preg_match('/[,=\<\'\"\(\s]/', $field)) {
1252
                return $this->whereRaw($field, is_array($op) ? $op : []);
1253
            } elseif (is_string($op) && strtolower($op) == 'exp') {
1254
                $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : [];
1255
                return $this->whereExp($field, $condition, $bind, $logic);
1256
            }
1257
1258
            $where = $this->parseWhereItem($logic, $field, $op, $condition, $param);
1259
        }
1260
1261
        if (!empty($where)) {
1262
            $this->options['where'][$logic][] = $where;
1263
        }
1264
1265
        return $this;
1266
    }
1267
1268
    /**
1269
     * 分析查询表达式
1270
     * @access protected
1271
     * @param  string   $logic     查询逻辑 and or xor
1272
     * @param  mixed    $field     查询字段
1273
     * @param  mixed    $op        查询表达式
1274
     * @param  mixed    $condition 查询条件
1275
     * @param  array    $param     查询参数
1276
     * @return array
1277
     */
1278
    protected function parseWhereItem(string $logic, $field, $op, $condition, array $param = []): array
1279
    {
1280
        if (is_array($op)) {
1281
            // 同一字段多条件查询
1282
            array_unshift($param, $field);
1283
            $where = $param;
1284
        } elseif (!is_string($op)) {
1285
            $where = $this->whereEq($field, $op);
1286
        } elseif ($field && is_null($condition)) {
1287
            if (in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) {
1288
                // null查询
1289
                $where = [$field, $op, ''];
1290
            } elseif ('=' == $op) {
1291
                $where = [$field, 'NULL', ''];
1292
            } elseif ('<>' == $op) {
1293
                $where = [$field, 'NOTNULL', ''];
1294
            } else {
1295
                // 字段相等查询
1296
                $where = $this->whereEq($field, $op);
1297
            }
1298
        } elseif (in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) {
1299
            $where = [$field, $op, is_string($condition) ? new Raw($condition) : $condition];
1300
        } else {
1301
            $where = $field ? [$field, $op, $condition, $param[2] ?? null] : [];
1302
        }
1303
1304
        return $where;
1305
    }
1306
1307
    /**
1308
     * 相等查询的主键处理
1309
     * @access protected
1310
     * @param  string $field  字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1311
     * @param  mixed  $value  字段值
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1312
     * @return array
1313
     */
1314
    protected function whereEq(string $field, $value): array
1315
    {
1316
        $where = [$field, '=', $value];
1317
        if ($this->getPk() == $field) {
1318
            $this->options['key'] = $value;
1319
        }
1320
1321
        return $where;
1322
    }
1323
1324
    /**
1325
     * 数组批量查询
1326
     * @access protected
1327
     * @param  array  $field 批量查询
1328
     * @param  string $logic 查询逻辑 and or xor
1329
     * @return $this
1330
     */
1331
    protected function parseArrayWhereItems(array $field, string $logic)
1332
    {
1333
        if (key($field) !== 0) {
1334
            $where = [];
1335
            foreach ($field as $key => $val) {
1336
                if ($val instanceof Raw) {
1337
                    $where[] = [$key, 'exp', $val];
1338
                } else {
1339
                    $where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, is_array($val) ? 'IN' : '=', $val];
1340
                }
1341
            }
1342
        } else {
1343
            // 数组批量查询
1344
            $where = $field;
1345
        }
1346
1347
        if (!empty($where)) {
1348
            $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? array_merge($this->options['where'][$logic], $where) : $where;
1349
        }
1350
1351
        return $this;
1352
    }
1353
1354
    /**
1355
     * 去除某个查询条件
1356
     * @access public
1357
     * @param  string $field 查询字段
1358
     * @param  string $logic 查询逻辑 and or xor
1359
     * @return $this
1360
     */
1361
    public function removeWhereField(string $field, string $logic = 'AND')
1362
    {
1363
        $logic = strtoupper($logic);
1364
1365
        if (isset($this->options['where'][$logic])) {
1366
            foreach ($this->options['where'][$logic] as $key => $val) {
1367
                if (is_array($val) && $val[0] == $field) {
1368
                    unset($this->options['where'][$logic][$key]);
1369
                }
1370
            }
1371
        }
1372
1373
        return $this;
1374
    }
1375
1376
    /**
1377
     * 去除查询参数
1378
     * @access public
1379
     * @param  string $option 参数名 留空去除所有参数
1380
     * @return $this
1381
     */
1382
    public function removeOption(string $option = '')
1383
    {
1384
        if ('' === $option) {
1385
            $this->options = [];
1386
            $this->bind    = [];
1387
        } elseif (isset($this->options[$option])) {
1388
            unset($this->options[$option]);
1389
        }
1390
1391
        return $this;
1392
    }
1393
1394
    /**
1395
     * 条件查询
1396
     * @access public
1397
     * @param  mixed         $condition  满足条件(支持闭包)
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1398
     * @param  Closure|array $query      满足条件后执行的查询表达式(闭包或数组)
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 6 found
Loading history...
1399
     * @param  Closure|array $otherwise  不满足条件后执行
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1400
     * @return $this
1401
     */
1402
    public function when($condition, $query, $otherwise = null)
1403
    {
1404
        if ($condition instanceof Closure) {
1405
            $condition = $condition($this);
1406
        }
1407
1408
        if ($condition) {
1409
            if ($query instanceof Closure) {
1410
                $query($this, $condition);
1411
            } elseif (is_array($query)) {
0 ignored issues
show
introduced by
The condition is_array($query) is always true.
Loading history...
1412
                $this->where($query);
1413
            }
1414
        } elseif ($otherwise) {
1415
            if ($otherwise instanceof Closure) {
1416
                $otherwise($this, $condition);
1417
            } elseif (is_array($otherwise)) {
0 ignored issues
show
introduced by
The condition is_array($otherwise) is always true.
Loading history...
1418
                $this->where($otherwise);
1419
            }
1420
        }
1421
1422
        return $this;
1423
    }
1424
1425
    /**
1426
     * 指定查询数量
1427
     * @access public
1428
     * @param  int $offset 起始位置
1429
     * @param  int $length 查询数量
1430
     * @return $this
1431
     */
1432
    public function limit(int $offset, int $length = null)
1433
    {
1434
        $this->options['limit'] = $offset . ($length ? ',' . $length : '');
1435
1436
        return $this;
1437
    }
1438
1439
    /**
1440
     * 指定分页
1441
     * @access public
1442
     * @param  int $page     页数
1443
     * @param  int $listRows 每页数量
1444
     * @return $this
1445
     */
1446
    public function page(int $page, int $listRows = null)
1447
    {
1448
        $this->options['page'] = [$page, $listRows];
1449
1450
        return $this;
1451
    }
1452
1453
    /**
1454
     * 分页查询
1455
     * @access public
1456
     * @param  int|array $listRows 每页数量 数组表示配置参数
1457
     * @param  int|bool  $simple   是否简洁模式或者总记录数
1458
     * @param  array     $config   配置参数
1459
     * @return \think\Paginator
1460
     * @throws DbException
1461
     */
1462
    public function paginate($listRows = null, $simple = false, $config = [])
1463
    {
1464
        if (is_int($simple)) {
1465
            $total  = $simple;
1466
            $simple = false;
1467
        }
1468
1469
        $paginate = array_merge($this->paginateConfig, Container::pull('config')->get('paginate'));
1470
1471
        if (is_array($listRows)) {
1472
            $config   = array_merge($paginate, $listRows);
1473
            $listRows = intval($config['list_rows']);
1474
        } else {
1475
            $config   = array_merge($paginate, $config);
1476
            $listRows = intval($listRows ?: $config['list_rows']);
1477
        }
1478
1479
        $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']);
1480
        $page  = isset($config['page']) ? (int) $config['page'] : call_user_func([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
1481
            $class,
1482
            'getCurrentPage',
1483
        ], $config['var_page']);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
1484
1485
        $page = $page < 1 ? 1 : $page;
1486
1487
        $config['path'] = $config['path'] ?? call_user_func([$class, 'getCurrentPath']);
1488
1489
        if (!isset($total) && !$simple) {
1490
            $options = $this->getOptions();
1491
1492
            unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']);
1493
1494
            $bind    = $this->bind;
1495
            $total   = $this->count();
1496
            $results = $this->options($options)->bind($bind)->page($page, $listRows)->select();
1497
        } elseif ($simple) {
1498
            $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
1499
            $total   = null;
1500
        } else {
1501
            $results = $this->page($page, $listRows)->select();
1502
        }
1503
1504
        $this->removeOption('limit');
1505
        $this->removeOption('page');
1506
1507
        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...
1508
    }
1509
1510
    /**
1511
     * 表达式方式指定当前操作的数据表
1512
     * @access public
1513
     * @param  mixed $table 表名
1514
     * @return $this
1515
     */
1516
    public function tableRaw(string $table)
1517
    {
1518
        $this->options['table'] = new Raw($table);
1519
1520
        return $this;
1521
    }
1522
1523
    /**
1524
     * 指定当前操作的数据表
1525
     * @access public
1526
     * @param  mixed $table 表名
1527
     * @return $this
1528
     */
1529
    public function table($table)
1530
    {
1531
        if (is_string($table)) {
1532
            if (strpos($table, ')')) {
1533
                // 子查询
1534
            } else {
1535
                $tables = explode(',', $table);
1536
                $table  = [];
1537
1538
                foreach ($tables as $item) {
1539
                    $item = trim($item);
1540
                    if (strpos($item, ' ')) {
1541
                        list($item, $alias) = explode(' ', $item);
1542
                        $this->alias([$item => $alias]);
1543
                        $table[$item] = $alias;
1544
                    } else {
1545
                        $table[] = $item;
1546
                    }
1547
                }
1548
            }
1549
        } elseif (is_array($table)) {
1550
            $tables = $table;
1551
            $table  = [];
1552
1553
            foreach ($tables as $key => $val) {
1554
                if (is_numeric($key)) {
1555
                    $table[] = $val;
1556
                } else {
1557
                    $this->alias([$key => $val]);
1558
                    $table[$key] = $val;
1559
                }
1560
            }
1561
        }
1562
1563
        $this->options['table'] = $table;
1564
1565
        return $this;
1566
    }
1567
1568
    /**
1569
     * USING支持 用于多表删除
1570
     * @access public
1571
     * @param  mixed $using USING
1572
     * @return $this
1573
     */
1574
    public function using($using)
1575
    {
1576
        $this->options['using'] = $using;
1577
        return $this;
1578
    }
1579
1580
    /**
1581
     * 存储过程调用
1582
     * @access public
1583
     * @param  bool $procedure 是否为存储过程查询
1584
     * @return $this
1585
     */
1586
    public function procedure($procedure = true)
1587
    {
1588
        $this->options['procedure'] = $procedure;
1589
        return $this;
1590
    }
1591
1592
    /**
1593
     * 是否允许返回空数据(或空模型)
1594
     * @access public
1595
     * @param  bool $allowEmpty 是否允许为空
1596
     * @return $this
1597
     */
1598
    public function allowEmpty(bool $allowEmpty = true)
1599
    {
1600
        $this->options['allow_empty'] = $allowEmpty;
1601
        return $this;
1602
    }
1603
1604
    /**
1605
     * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc'])
1606
     * @access public
1607
     * @param  string|array|Raw $field 排序字段
1608
     * @param  string           $order 排序
1609
     * @return $this
1610
     */
1611
    public function order($field, string $order = '')
1612
    {
1613
        if (empty($field)) {
1614
            return $this;
1615
        } elseif ($field instanceof Raw) {
1616
            $this->options['order'][] = $field;
1617
            return $this;
1618
        }
1619
1620
        if (is_string($field)) {
1621
            if (!empty($this->options['via'])) {
1622
                $field = $this->options['via'] . '.' . $field;
1623
            }
1624
            if (strpos($field, ',')) {
1625
                $field = array_map('trim', explode(',', $field));
1626
            } else {
1627
                $field = empty($order) ? $field : [$field => $order];
1628
            }
1629
        } elseif (!empty($this->options['via'])) {
1630
            foreach ($field as $key => $val) {
1631
                if (is_numeric($key)) {
1632
                    $field[$key] = $this->options['via'] . '.' . $val;
1633
                } else {
1634
                    $field[$this->options['via'] . '.' . $key] = $val;
1635
                    unset($field[$key]);
1636
                }
1637
            }
1638
        }
1639
1640
        if (!isset($this->options['order'])) {
1641
            $this->options['order'] = [];
1642
        }
1643
1644
        if (is_array($field)) {
1645
            $this->options['order'] = array_merge($this->options['order'], $field);
1646
        } else {
1647
            $this->options['order'][] = $field;
1648
        }
1649
1650
        return $this;
1651
    }
1652
1653
    /**
1654
     * 表达式方式指定Field排序
1655
     * @access public
1656
     * @param  string $field 排序字段
1657
     * @param  array  $bind  参数绑定
1658
     * @return $this
1659
     */
1660
    public function orderRaw(string $field, array $bind = [])
1661
    {
1662
        if (!empty($bind)) {
1663
            $this->bindParams($field, $bind);
1664
        }
1665
1666
        $this->options['order'][] = new Raw($field);
1667
1668
        return $this;
1669
    }
1670
1671
    /**
1672
     * 指定Field排序 orderField('id',[1,2,3],'desc')
1673
     * @access public
1674
     * @param  string $field 排序字段
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
1675
     * @param  array  $values 排序值
1676
     * @param  string $order 排序 desc/asc
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
1677
     * @return $this
1678
     */
1679
    public function orderField(string $field, array $values, string $order = '')
1680
    {
1681
        if (!empty($values)) {
1682
            $values['sort'] = $order;
1683
1684
            $this->options['order'][$field] = $values;
1685
        }
1686
1687
        return $this;
1688
    }
1689
1690
    /**
1691
     * 随机排序
1692
     * @access public
1693
     * @return $this
1694
     */
1695
    public function orderRand()
1696
    {
1697
        $this->options['order'][] = '[rand]';
1698
        return $this;
1699
    }
1700
1701
    /**
1702
     * 查询缓存
1703
     * @access public
1704
     * @param  mixed             $key    缓存key
1705
     * @param  integer|\DateTime $expire 缓存有效期
1706
     * @param  string            $tag    缓存标签
1707
     * @return $this
1708
     */
1709
    public function cache($key = true, $expire = null, $tag = null)
1710
    {
1711
        if (false === $key) {
1712
            return $this;
1713
        }
1714
1715
        if ($key instanceof \DateTimeInterface || (is_int($key) && is_null($expire))) {
1716
            $expire = $key;
1717
            $key    = true;
1718
        }
1719
1720
        $this->options['cache'] = [$key, $expire, $tag];
1721
1722
        return $this;
1723
    }
1724
1725
    /**
1726
     * 指定group查询
1727
     * @access public
1728
     * @param  string|array $group GROUP
1729
     * @return $this
1730
     */
1731
    public function group($group)
1732
    {
1733
        $this->options['group'] = $group;
1734
        return $this;
1735
    }
1736
1737
    /**
1738
     * 指定having查询
1739
     * @access public
1740
     * @param  string $having having
1741
     * @return $this
1742
     */
1743
    public function having(string $having)
1744
    {
1745
        $this->options['having'] = $having;
1746
        return $this;
1747
    }
1748
1749
    /**
1750
     * 指定查询lock
1751
     * @access public
1752
     * @param  bool|string $lock 是否lock
1753
     * @return $this
1754
     */
1755
    public function lock($lock = false)
1756
    {
1757
        $this->options['lock'] = $lock;
1758
1759
        if ($lock) {
1760
            $this->options['master'] = true;
1761
        }
1762
1763
        return $this;
1764
    }
1765
1766
    /**
1767
     * 指定distinct查询
1768
     * @access public
1769
     * @param  bool $distinct 是否唯一
1770
     * @return $this
1771
     */
1772
    public function distinct(bool $distinct = true)
1773
    {
1774
        $this->options['distinct'] = $distinct;
1775
        return $this;
1776
    }
1777
1778
    /**
1779
     * 指定数据表别名
1780
     * @access public
1781
     * @param  array|string $alias 数据表别名
1782
     * @return $this
1783
     */
1784
    public function alias($alias)
1785
    {
1786
        if (is_array($alias)) {
1787
            $this->options['alias'] = $alias;
1788
        } else {
1789
            $table = $this->getTable();
1790
1791
            $this->options['alias'][$table] = $alias;
1792
        }
1793
1794
        return $this;
1795
    }
1796
1797
    /**
1798
     * 指定强制索引
1799
     * @access public
1800
     * @param  string $force 索引名称
1801
     * @return $this
1802
     */
1803
    public function force(string $force)
1804
    {
1805
        $this->options['force'] = $force;
1806
        return $this;
1807
    }
1808
1809
    /**
1810
     * 查询注释
1811
     * @access public
1812
     * @param  string $comment 注释
1813
     * @return $this
1814
     */
1815
    public function comment(string $comment)
1816
    {
1817
        $this->options['comment'] = $comment;
1818
        return $this;
1819
    }
1820
1821
    /**
1822
     * 获取执行的SQL语句而不进行实际的查询
1823
     * @access public
1824
     * @param  bool $fetch 是否返回sql
1825
     * @return $this|Fetch
1826
     */
1827
    public function fetchSql(bool $fetch = true)
1828
    {
1829
        $this->options['fetch_sql'] = $fetch;
1830
1831
        if ($fetch) {
1832
            return new Fetch($this);
1833
        }
1834
1835
        return $this;
1836
    }
1837
1838
    /**
1839
     * 设置是否返回数据集对象
1840
     * @access public
1841
     * @param  bool|string $collection 是否返回数据集对象
1842
     * @return $this
1843
     */
1844
    public function fetchCollection($collection = true)
1845
    {
1846
        $this->options['collection'] = $collection;
1847
        return $this;
1848
    }
1849
1850
    /**
1851
     * 设置是否返回数组
1852
     * @access public
1853
     * @param  bool $asArray 是否返回数组
1854
     * @return $this
1855
     */
1856
    public function fetchArray(bool $asArray = true)
1857
    {
1858
        $this->options['array'] = $asArray;
1859
        return $this;
1860
    }
1861
1862
    /**
1863
     * 设置从主服务器读取数据
1864
     * @access public
1865
     * @param  bool $readMaster 是否从主服务器读取
1866
     * @return $this
1867
     */
1868
    public function master(bool $readMaster = true)
1869
    {
1870
        $this->options['master'] = $readMaster;
1871
        return $this;
1872
    }
1873
1874
    /**
1875
     * 设置后续从主库读取数据
1876
     * @access public
1877
     * @param  bool $all 是否所有表有效
1878
     * @return $this
1879
     */
1880
    public function readMaster(bool $all = false)
1881
    {
1882
        $table = $all ? '*' : $this->getTable();
1883
1884
        Db::readMaster($table);
0 ignored issues
show
Bug introduced by
The method readMaster() does not exist on think\facade\Db. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

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

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

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

3458
        if (is_string($options['table']) && Db::/** @scrutinizer ignore-call */ isReadMaster($options['table'])) {
Loading history...
3459
            $options['master'] = true;
3460
        }
3461
3462
        foreach (['group', 'having', 'limit', 'force', 'comment', 'partition', 'duplicate', 'extra'] as $name) {
3463
            if (!isset($options[$name])) {
3464
                $options[$name] = '';
3465
            }
3466
        }
3467
3468
        if (isset($options['page'])) {
3469
            // 根据页数计算limit
3470
            list($page, $listRows) = $options['page'];
3471
            $page                  = $page > 0 ? $page : 1;
3472
            $listRows              = $listRows ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20);
3473
            $offset                = $listRows * ($page - 1);
3474
            $options['limit']      = $offset . ',' . $listRows;
3475
        }
3476
3477
        $this->options = $options;
3478
3479
        return $options;
3480
    }
3481
3482
    /**
3483
     * 注册回调方法
3484
     * @access public
3485
     * @param  string   $event    事件名
3486
     * @param  callable $callback 回调方法
3487
     * @return void
3488
     */
3489
    public static function event(string $event, callable $callback): void
3490
    {
3491
        self::$event[$event] = $callback;
3492
    }
3493
3494
    /**
3495
     * 触发事件
3496
     * @access public
3497
     * @param  string $event 事件名
3498
     * @return mixed
3499
     */
3500
    public function trigger(string $event)
3501
    {
3502
        $result = false;
3503
3504
        if (isset(self::$event[$event])) {
3505
            $result = Container::getInstance()->invoke(self::$event[$event], [$this]);
3506
        }
3507
3508
        return $result;
3509
    }
3510
3511
}
3512