Passed
Push — 5.2 ( 9ea4f6...c9e9f4 )
by liu
03:40
created

Query::replace()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
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\Model;
28
use think\model\Collection as ModelCollection;
29
use think\model\Relation;
30
use think\model\relation\OneToOne;
31
32
class Query
1 ignored issue
show
Coding Style introduced by
Missing class doc comment
Loading history...
33
{
34
    /**
35
     * 当前数据库连接对象
36
     * @var Connection
37
     */
38
    protected $connection;
39
40
    /**
41
     * 当前模型对象
42
     * @var Model
43
     */
44
    protected $model;
45
46
    /**
47
     * 当前数据表名称(不含前缀)
48
     * @var string
49
     */
50
    protected $name = '';
51
52
    /**
53
     * 当前数据表主键
54
     * @var string|array
55
     */
56
    protected $pk;
57
58
    /**
59
     * 当前数据表前缀
60
     * @var string
61
     */
62
    protected $prefix = '';
63
64
    /**
65
     * 当前查询参数
66
     * @var array
67
     */
68
    protected $options = [];
69
70
    /**
71
     * 分页查询配置
72
     * @var array
73
     */
74
    protected $paginateConfig = [
75
        'query'     => [], //url额外参数
76
        'fragment'  => '', //url锚点
77
        'type'      => 'bootstrap', //分页类名
78
        'var_page'  => 'page', //分页变量
79
        'list_rows' => 15, //每页数量
80
    ];
81
82
    /**
83
     * 当前参数绑定
84
     * @var array
85
     */
86
    protected $bind = [];
87
88
    /**
89
     * 事件回调
90
     * @var array
91
     */
92
    protected static $event = [];
93
94
    /**
95
     * 日期查询表达式
96
     * @var array
97
     */
98
    protected $timeRule = [
99
        'today'      => ['today', 'tomorrow'],
100
        'yesterday'  => ['yesterday', 'today'],
101
        'week'       => ['this week 00:00:00', 'next week 00:00:00'],
102
        'last week'  => ['last week 00:00:00', 'this week 00:00:00'],
103
        'month'      => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00'],
104
        'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00'],
105
        'year'       => ['this year 1/1', 'next year 1/1'],
106
        'last year'  => ['last year 1/1', 'this year 1/1'],
107
    ];
108
109
    /**
110
     * 架构函数
111
     * @access public
112
     * @param  Connection      $connection 数据库连接对象
113
     */
114
    public function __construct(Connection $connection)
115
    {
116
        $this->connection = $connection;
117
118
        $this->prefix = $this->connection->getConfig('prefix');
119
    }
120
121
    /**
122
     * 创建一个新的查询对象
123
     * @access public
124
     * @return Query
125
     */
126
    public function newQuery()
127
    {
128
        return new static($this->connection);
129
    }
130
131
    /**
132
     * 利用__call方法实现一些特殊的Model方法
133
     * @access public
134
     * @param  string $method 方法名称
135
     * @param  array  $args   调用参数
136
     * @return mixed
137
     * @throws DbException
138
     * @throws Exception
139
     */
140
    public function __call(string $method, array $args)
141
    {
142
        if (strtolower(substr($method, 0, 5)) == 'getby') {
143
            // 根据某个字段获取记录
144
            $field = App::parseName(substr($method, 5));
145
            return $this->where($field, '=', $args[0])->find();
146
        } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
147
            // 根据某个字段获取记录的某个值
148
            $name = App::parseName(substr($method, 10));
149
            return $this->where($name, '=', $args[0])->value($args[1]);
150
        } elseif (strtolower(substr($method, 0, 7)) == 'whereor') {
151
            $name = App::parseName(substr($method, 7));
152
            array_unshift($args, $name);
153
            return call_user_func_array([$this, 'whereOr'], $args);
154
        } elseif (strtolower(substr($method, 0, 5)) == 'where') {
155
            $name = App::parseName(substr($method, 5));
156
            array_unshift($args, $name);
157
            return call_user_func_array([$this, 'where'], $args);
158
        } elseif ($this->model && method_exists($this->model, 'scope' . $method)) {
159
            // 动态调用命名范围
160
            $method = 'scope' . $method;
161
            array_unshift($args, $this);
162
163
            call_user_func_array([$this->model, $method], $args);
164
            return $this;
165
        } else {
166
            throw new Exception('method not exist:' . static::class . '->' . $method);
167
        }
168
    }
169
170
    /**
171
     * 获取当前的数据库Connection对象
172
     * @access public
173
     * @return Connection
174
     */
175
    public function getConnection(): Connection
176
    {
177
        return $this->connection;
178
    }
179
180
    /**
181
     * 设置当前的数据库Connection对象
182
     * @access public
183
     * @param  Connection      $connection 数据库连接对象
184
     * @return $this
185
     */
186
    public function setConnection(Connection $connection)
187
    {
188
        $this->connection = $connection;
189
190
        return $this;
191
    }
192
193
    /**
194
     * 指定模型
195
     * @access public
196
     * @param  Model $model 模型对象实例
197
     * @return $this
198
     */
199
    public function model(Model $model)
200
    {
201
        $this->model = $model;
202
        return $this;
203
    }
204
205
    /**
206
     * 获取当前的模型对象
207
     * @access public
208
     * @return Model|null
209
     */
210
    public function getModel()
211
    {
212
        return $this->model ? $this->model->setQuery($this) : null;
213
    }
214
215
    /**
216
     * 指定当前数据表名(不含前缀)
217
     * @access public
218
     * @param  string $name 不含前缀的数据表名字
219
     * @return $this
220
     */
221
    public function name(string $name)
222
    {
223
        $this->name = $name;
224
        return $this;
225
    }
226
227
    /**
228
     * 获取当前的数据表名称
229
     * @access public
230
     * @return string
231
     */
232
    public function getName(): string
233
    {
234
        return $this->name ?: $this->model->getName();
235
    }
236
237
    /**
238
     * 获取数据库的配置参数
239
     * @access public
240
     * @param  string $name 参数名称
241
     * @return mixed
242
     */
243
    public function getConfig(string $name = '')
244
    {
245
        return $this->connection->getConfig($name);
246
    }
247
248
    /**
249
     * 得到当前或者指定名称的数据表
250
     * @access public
251
     * @param  string $name 不含前缀的数据表名字
252
     * @return string
253
     */
254
    public function getTable(string $name = ''): string
255
    {
256
        if (empty($name) && isset($this->options['table'])) {
257
            return $this->options['table'];
258
        }
259
260
        $name = $name ?: $this->name;
261
262
        return $this->prefix . App::parseName($name);
263
    }
264
265
    /**
266
     * 获取数据表字段信息
267
     * @access public
268
     * @param  string $tableName 数据表名
269
     * @return array
270
     */
271
    public function getTableFields($tableName = ''): array
272
    {
273
        if ('' == $tableName) {
274
            $tableName = $this->options['table'] ?? $this->getTable();
275
        }
276
277
        return $this->connection->getTableFields($tableName);
278
    }
279
280
    /**
281
     * 设置字段类型信息
282
     * @access public
283
     * @param  array $type 字段类型信息
284
     * @return $this
285
     */
286
    public function setFieldType(array $type)
287
    {
288
        $this->options['field_type'] = $type;
289
        return $this;
290
    }
291
292
    /**
293
     * 获取字段类型信息
294
     * @access public
295
     * @return array
296
     */
297
    public function getFieldsType(): array
298
    {
299
        if (!empty($this->options['field_type'])) {
300
            return $this->options['field_type'];
301
        }
302
303
        $tableName = $this->options['table'] ?? $this->getTable();
304
305
        return $this->connection->getFieldsType($tableName);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->connection...tFieldsType($tableName) could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
306
    }
307
308
    /**
309
     * 获取字段类型信息
310
     * @access public
311
     * @param  string $field 字段名
312
     * @return string|null
313
     */
314
    public function getFieldType(string $field)
315
    {
316
        $fieldType = $this->getFieldsType();
317
318
        return $fieldType[$field] ?? null;
319
    }
320
321
    /**
322
     * 获取字段类型信息
323
     * @access public
324
     * @return array
325
     */
326
    public function getFieldsBindType(): array
327
    {
328
        $fieldType = $this->getFieldsType();
329
330
        return array_map([$this->connection, 'getFieldBindType'], $fieldType);
331
    }
332
333
    /**
334
     * 获取字段类型信息
335
     * @access public
336
     * @param  string $field 字段名
337
     * @return int
338
     */
339
    public function getFieldBindType(string $field): int
340
    {
341
        $fieldType = $this->getFieldType($field);
342
343
        return $this->connection->getFieldBindType($fieldType ?: '');
344
    }
345
346
    /**
347
     * 执行查询 返回数据集
348
     * @access public
349
     * @param  string $sql    sql指令
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 4 found
Loading history...
350
     * @param  array  $bind   参数绑定
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
351
     * @return array
352
     * @throws BindParamException
353
     * @throws PDOException
354
     */
355
    public function query(string $sql, array $bind = []): array
356
    {
357
        return $this->connection->query($this, $sql, $bind, true);
358
    }
359
360
    /**
361
     * 执行语句
362
     * @access public
363
     * @param  string $sql  sql指令
364
     * @param  array  $bind 参数绑定
365
     * @return int
366
     * @throws BindParamException
367
     * @throws PDOException
368
     */
369
    public function execute(string $sql, array $bind = []): int
370
    {
371
        return $this->connection->execute($this, $sql, $bind);
372
    }
373
374
    /**
375
     * 监听SQL执行
376
     * @access public
377
     * @param  callable $callback 回调方法
378
     * @return void
379
     */
380
    public function listen(callable $callback): void
381
    {
382
        $this->connection->listen($callback);
383
    }
384
385
    /**
386
     * 获取最近插入的ID
387
     * @access public
388
     * @param  string $sequence 自增序列名
389
     * @return string
390
     */
391
    public function getLastInsID(string $sequence = null): string
392
    {
393
        return $this->connection->getLastInsID($sequence);
394
    }
395
396
    /**
397
     * 获取返回或者影响的记录数
398
     * @access public
399
     * @return integer
400
     */
401
    public function getNumRows(): int
402
    {
403
        return $this->connection->getNumRows();
404
    }
405
406
    /**
407
     * 获取最近一次查询的sql语句
408
     * @access public
409
     * @return string
410
     */
411
    public function getLastSql(): string
412
    {
413
        return $this->connection->getLastSql();
414
    }
415
416
    /**
417
     * 执行数据库事务
418
     * @access public
419
     * @param  callable $callback 数据操作方法回调
420
     * @return mixed
421
     */
422
    public function transaction(callable $callback)
423
    {
424
        return $this->connection->transaction($callback);
425
    }
426
427
    /**
428
     * 启动事务
429
     * @access public
430
     * @return void
431
     */
432
    public function startTrans(): void
433
    {
434
        $this->connection->startTrans();
435
    }
436
437
    /**
438
     * 用于非自动提交状态下面的查询提交
439
     * @access public
440
     * @return void
441
     * @throws PDOException
442
     */
443
    public function commit(): void
444
    {
445
        $this->connection->commit();
446
    }
447
448
    /**
449
     * 事务回滚
450
     * @access public
451
     * @return void
452
     * @throws PDOException
453
     */
454
    public function rollback(): void
455
    {
456
        $this->connection->rollback();
457
    }
458
459
    /**
460
     * 批处理执行SQL语句
461
     * 批处理的指令都认为是execute操作
462
     * @access public
463
     * @param  array $sql SQL批处理指令
464
     * @return bool
465
     */
466
    public function batchQuery(array $sql = []): bool
467
    {
468
        return $this->connection->batchQuery($this, $sql);
469
    }
470
471
    /**
472
     * 得到某个字段的值
473
     * @access public
474
     * @param  string $field   字段名
475
     * @param  mixed  $default 默认值
476
     * @return mixed
477
     */
478
    public function value(string $field, $default = null)
479
    {
480
        return $this->connection->value($this, $field, $default);
481
    }
482
483
    /**
484
     * 得到某个列的数组
485
     * @access public
486
     * @param  string $field 字段名 多个字段用逗号分隔
487
     * @param  string $key   索引
488
     * @return array
489
     */
490
    public function column(string $field, string $key = ''): array
491
    {
492
        return $this->connection->column($this, $field, $key);
493
    }
494
495
    /**
496
     * 聚合查询
497
     * @access protected
498
     * @param  string     $aggregate    聚合方法
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
499
     * @param  string|Raw $field        字段名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 8 found
Loading history...
500
     * @param  bool       $force        强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 8 found
Loading history...
501
     * @return mixed
502
     */
503
    protected function aggregate(string $aggregate, $field, bool $force = false)
504
    {
505
        return $this->connection->aggregate($this, $aggregate, $field, $force);
506
    }
507
508
    /**
509
     * COUNT查询
510
     * @access public
511
     * @param  string|Raw $field 字段名
512
     * @return int
513
     */
514
    public function count(string $field = '*'): int
515
    {
516
        if (!empty($this->options['group'])) {
517
            // 支持GROUP
518
            $options = $this->getOptions();
519
            $subSql  = $this->options($options)->field('count(' . $field . ') AS think_count')->bind($this->bind)->buildSql();
520
521
            $query = $this->newQuery()->table([$subSql => '_group_count_']);
522
523
            $count = $query->aggregate('COUNT', '*');
524
        } else {
525
            $count = $this->aggregate('COUNT', $field);
526
        }
527
528
        return (int) $count;
529
    }
530
531
    /**
532
     * SUM查询
533
     * @access public
534
     * @param  string|Raw $field 字段名
535
     * @return float
536
     */
537
    public function sum($field): float
538
    {
539
        return $this->aggregate('SUM', $field, true);
540
    }
541
542
    /**
543
     * MIN查询
544
     * @access public
545
     * @param  string|Raw $field    字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
546
     * @param  bool       $force    强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
547
     * @return mixed
548
     */
549
    public function min($field, bool $force = true)
550
    {
551
        return $this->aggregate('MIN', $field, $force);
552
    }
553
554
    /**
555
     * MAX查询
556
     * @access public
557
     * @param  string|Raw $field    字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
558
     * @param  bool       $force    强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
559
     * @return mixed
560
     */
561
    public function max($field, bool $force = true)
562
    {
563
        return $this->aggregate('MAX', $field, $force);
564
    }
565
566
    /**
567
     * AVG查询
568
     * @access public
569
     * @param  string|Raw $field 字段名
570
     * @return float
571
     */
572
    public function avg($field): float
573
    {
574
        return $this->aggregate('AVG', $field, true);
575
    }
576
577
    /**
578
     * 查询SQL组装 join
579
     * @access public
580
     * @param  mixed  $join      关联的表名
581
     * @param  mixed  $condition 条件
582
     * @param  string $type      JOIN类型
583
     * @param  array  $bind      参数绑定
584
     * @return $this
585
     */
586
    public function join($join, string $condition = null, string $type = 'INNER', array $bind = [])
587
    {
588
        $table = $this->getJoinTable($join);
589
590
        if (!empty($bind) && $condition) {
591
            $this->bindParams($condition, $bind);
592
        }
593
594
        $this->options['join'][] = [$table, strtoupper($type), $condition];
595
596
        return $this;
597
    }
598
599
    /**
600
     * LEFT JOIN
601
     * @access public
602
     * @param  mixed  $join      关联的表名
603
     * @param  mixed  $condition 条件
604
     * @param  array  $bind      参数绑定
605
     * @return $this
606
     */
607
    public function leftJoin($join, string $condition = null, array $bind = [])
608
    {
609
        return $this->join($join, $condition, 'LEFT', $bind);
610
    }
611
612
    /**
613
     * RIGHT JOIN
614
     * @access public
615
     * @param  mixed  $join      关联的表名
616
     * @param  mixed  $condition 条件
617
     * @param  array  $bind      参数绑定
618
     * @return $this
619
     */
620
    public function rightJoin($join, string $condition = null, array $bind = [])
621
    {
622
        return $this->join($join, $condition, 'RIGHT', $bind);
623
    }
624
625
    /**
626
     * FULL JOIN
627
     * @access public
628
     * @param  mixed  $join      关联的表名
629
     * @param  mixed  $condition 条件
630
     * @param  array  $bind      参数绑定
631
     * @return $this
632
     */
633
    public function fullJoin($join, string $condition = null, array $bind = [])
634
    {
635
        return $this->join($join, $condition, 'FULL');
636
    }
637
638
    /**
639
     * 获取Join表名及别名 支持
640
     * ['prefix_table或者子查询'=>'alias'] 'table alias'
641
     * @access protected
642
     * @param  array|string|Raw $join  JION表名
643
     * @param  string           $alias 别名
644
     * @return string|array
645
     */
646
    protected function getJoinTable($join, &$alias = null)
647
    {
648
        if (is_array($join)) {
649
            $table = $join;
650
            $alias = array_shift($join);
651
            return $table;
652
        } elseif ($join instanceof Raw) {
653
            return $join;
654
        }
655
656
        $join = trim($join);
657
658
        if (false !== strpos($join, '(')) {
659
            // 使用子查询
660
            $table = $join;
661
        } else {
662
            // 使用别名
663
            if (strpos($join, ' ')) {
664
                // 使用别名
665
                list($table, $alias) = explode(' ', $join);
666
            } else {
667
                $table = $join;
668
                if (false === strpos($join, '.')) {
669
                    $alias = $join;
670
                }
671
            }
672
673
            if ($this->prefix && false === strpos($table, '.') && 0 !== strpos($table, $this->prefix)) {
674
                $table = $this->getTable($table);
675
            }
676
        }
677
678
        if (!empty($alias) && $table != $alias) {
679
            $table = [$table => $alias];
680
        }
681
682
        return $table;
683
    }
684
685
    /**
686
     * 查询SQL组装 union
687
     * @access public
688
     * @param  mixed   $union UNION
689
     * @param  boolean $all   是否适用UNION ALL
690
     * @return $this
691
     */
692
    public function union($union, bool $all = false)
693
    {
694
        $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION';
695
696
        if (is_array($union)) {
697
            $this->options['union'] = array_merge($this->options['union'], $union);
698
        } else {
699
            $this->options['union'][] = $union;
700
        }
701
702
        return $this;
703
    }
704
705
    /**
706
     * 查询SQL组装 union all
707
     * @access public
708
     * @param  mixed   $union UNION数据
709
     * @return $this
710
     */
711
    public function unionAll($union)
712
    {
713
        return $this->union($union, true);
714
    }
715
716
    /**
717
     * 指定查询字段 支持字段排除和指定数据表
718
     * @access public
719
     * @param  mixed   $field     字段信息
720
     * @param  boolean $except    是否排除
721
     * @param  string  $tableName 数据表名
722
     * @param  string  $prefix    字段前缀
723
     * @param  string  $alias     别名前缀
724
     * @return $this
725
     */
726
    public function field($field, bool $except = false, string $tableName = '', string $prefix = '', string $alias = '')
727
    {
728
        if (empty($field)) {
729
            return $this;
730
        } elseif ($field instanceof Raw) {
731
            $this->options['field'][] = $field;
732
            return $this;
733
        }
734
735
        if (is_string($field)) {
736
            if (preg_match('/[\<\'\"\(]/', $field)) {
737
                return $this->fieldRaw($field);
738
            }
739
740
            $field = array_map('trim', explode(',', $field));
741
        }
742
743
        if (true === $field) {
744
            // 获取全部字段
745
            $fields = $this->getTableFields($tableName);
746
            $field  = $fields ?: ['*'];
0 ignored issues
show
introduced by
$fields is an empty array, thus is always false.
Loading history...
747
        } elseif ($except) {
748
            // 字段排除
749
            $fields = $this->getTableFields($tableName);
750
            $field  = $fields ? array_diff($fields, $field) : $field;
0 ignored issues
show
introduced by
$fields is an empty array, thus is always false.
Loading history...
751
        }
752
753
        if ($tableName) {
754
            // 添加统一的前缀
755
            $prefix = $prefix ?: $tableName;
756
            foreach ($field as $key => &$val) {
757
                if (is_numeric($key) && $alias) {
758
                    $field[$prefix . '.' . $val] = $alias . $val;
759
                    unset($field[$key]);
760
                } elseif (is_numeric($key)) {
761
                    $val = $prefix . '.' . $val;
762
                }
763
            }
764
        }
765
766
        if (isset($this->options['field'])) {
767
            $field = array_merge((array) $this->options['field'], $field);
768
        }
769
770
        $this->options['field'] = array_unique($field);
771
772
        return $this;
773
    }
774
775
    /**
776
     * 表达式方式指定查询字段
777
     * @access public
778
     * @param  string $field 字段名
779
     * @return $this
780
     */
781
    public function fieldRaw(string $field)
782
    {
783
        $this->options['field'][] = new Raw($field);
784
785
        return $this;
786
    }
787
788
    /**
789
     * 设置数据
790
     * @access public
791
     * @param  array $data 数据
792
     * @return $this
793
     */
794
    public function data(array $data)
795
    {
796
        $this->options['data'] = $data;
797
798
        return $this;
799
    }
800
801
    /**
802
     * 字段值增长
803
     * @access public
804
     * @param  string  $field 字段名
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
805
     * @param  integer $step  增长值
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 2 found
Loading history...
806
     * @param  integer $lazyTime 延时时间(s)
807
     * @param  string  $op INC/DEC
0 ignored issues
show
Coding Style introduced by
Expected 7 spaces after parameter name; 1 found
Loading history...
808
     * @return $this
809
     */
810
    public function inc(string $field, int $step = 1, int $lazyTime = 0, string $op = 'INC')
811
    {
812
        if ($lazyTime > 0) {
813
            // 延迟写入
814
            $condition = $this->options['where'] ?? [];
815
816
            $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition));
817
            $step = $this->connection->lazyWrite($op, $guid, $step, $lazyTime);
818
819
            if (false === $step) {
820
                return $this;
821
            }
822
823
            $op = 'INC';
824
        }
825
826
        $this->options['data'][$field] = [$op, $step];
827
828
        return $this;
829
    }
830
831
    /**
832
     * 字段值减少
833
     * @access public
834
     * @param  string  $field 字段名
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
835
     * @param  integer $step  增长值
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 2 found
Loading history...
836
     * @param  integer $lazyTime 延时时间(s)
837
     * @return $this
838
     */
839
    public function dec(string $field, int $step = 1, int $lazyTime = 0)
840
    {
841
        return $this->inc($field, $step, $lazyTime, 'DEC');
842
    }
843
844
    /**
845
     * 使用表达式设置数据
846
     * @access public
847
     * @param  string $field 字段名
848
     * @param  string $value 字段值
849
     * @return $this
850
     */
851
    public function exp(string $field, string $value)
852
    {
853
        $this->options['data'][$field] = new Raw($value);
854
        return $this;
855
    }
856
857
    /**
858
     * 指定JOIN查询字段
859
     * @access public
860
     * @param  string|array $join  数据表
861
     * @param  string|array $field 查询字段
862
     * @param  string       $on    JOIN条件
863
     * @param  string       $type  JOIN类型
864
     * @param  array        $bind  参数绑定
865
     * @return $this
866
     */
867
    public function view($join, $field = true, $on = null, string $type = 'INNER', array $bind = [])
868
    {
869
        $this->options['view'] = true;
870
871
        $fields = [];
872
        $table  = $this->getJoinTable($join, $alias);
873
874
        if (true === $field) {
875
            $fields = $alias . '.*';
876
        } else {
877
            if (is_string($field)) {
878
                $field = explode(',', $field);
879
            }
880
881
            foreach ($field as $key => $val) {
882
                if (is_numeric($key)) {
883
                    $fields[] = $alias . '.' . $val;
884
885
                    $this->options['map'][$val] = $alias . '.' . $val;
886
                } else {
887
                    if (preg_match('/[,=\.\'\"\(\s]/', $key)) {
888
                        $name = $key;
889
                    } else {
890
                        $name = $alias . '.' . $key;
891
                    }
892
893
                    $fields[] = $name . ' AS ' . $val;
894
895
                    $this->options['map'][$val] = $name;
896
                }
897
            }
898
        }
899
900
        $this->field($fields);
901
902
        if ($on) {
903
            $this->join($table, $on, $type, $bind);
904
        } else {
905
            $this->table($table);
906
        }
907
908
        return $this;
909
    }
910
911
    /**
912
     * 指定AND查询条件
913
     * @access public
914
     * @param  mixed $field     查询字段
915
     * @param  mixed $op        查询表达式
916
     * @param  mixed $condition 查询条件
917
     * @return $this
918
     */
919
    public function where($field, $op = null, $condition = null)
920
    {
921
        if ($field instanceof $this) {
922
            $this->options['where'] = $field->getOptions('where');
923
            return $this;
924
        }
925
926
        $param = func_get_args();
927
        array_shift($param);
928
        return $this->parseWhereExp('AND', $field, $op, $condition, $param);
929
    }
930
931
    /**
932
     * 指定OR查询条件
933
     * @access public
934
     * @param  mixed $field     查询字段
935
     * @param  mixed $op        查询表达式
936
     * @param  mixed $condition 查询条件
937
     * @return $this
938
     */
939
    public function whereOr($field, $op = null, $condition = null)
940
    {
941
        $param = func_get_args();
942
        array_shift($param);
943
        return $this->parseWhereExp('OR', $field, $op, $condition, $param);
944
    }
945
946
    /**
947
     * 指定XOR查询条件
948
     * @access public
949
     * @param  mixed $field     查询字段
950
     * @param  mixed $op        查询表达式
951
     * @param  mixed $condition 查询条件
952
     * @return $this
953
     */
954
    public function whereXor($field, $op = null, $condition = null)
955
    {
956
        $param = func_get_args();
957
        array_shift($param);
958
        return $this->parseWhereExp('XOR', $field, $op, $condition, $param);
959
    }
960
961
    /**
962
     * 指定Null查询条件
963
     * @access public
964
     * @param  mixed  $field 查询字段
965
     * @param  string $logic 查询逻辑 and or xor
966
     * @return $this
967
     */
968
    public function whereNull(string $field, string $logic = 'AND')
969
    {
970
        return $this->parseWhereExp($logic, $field, 'NULL', null, [], true);
971
    }
972
973
    /**
974
     * 指定NotNull查询条件
975
     * @access public
976
     * @param  mixed  $field 查询字段
977
     * @param  string $logic 查询逻辑 and or xor
978
     * @return $this
979
     */
980
    public function whereNotNull(string $field, string $logic = 'AND')
981
    {
982
        return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true);
983
    }
984
985
    /**
986
     * 指定Exists查询条件
987
     * @access public
988
     * @param  mixed  $condition 查询条件
989
     * @param  string $logic     查询逻辑 and or xor
990
     * @return $this
991
     */
992
    public function whereExists($condition, string $logic = 'AND')
993
    {
994
        if (is_string($condition)) {
995
            $condition = new Raw($condition);
996
        }
997
998
        $this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition];
999
        return $this;
1000
    }
1001
1002
    /**
1003
     * 指定NotExists查询条件
1004
     * @access public
1005
     * @param  mixed  $condition 查询条件
1006
     * @param  string $logic     查询逻辑 and or xor
1007
     * @return $this
1008
     */
1009
    public function whereNotExists($condition, string $logic = 'AND')
1010
    {
1011
        if (is_string($condition)) {
1012
            $condition = new Raw($condition);
1013
        }
1014
1015
        $this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition];
1016
        return $this;
1017
    }
1018
1019
    /**
1020
     * 指定In查询条件
1021
     * @access public
1022
     * @param  mixed  $field     查询字段
1023
     * @param  mixed  $condition 查询条件
1024
     * @param  string $logic     查询逻辑 and or xor
1025
     * @return $this
1026
     */
1027
    public function whereIn(string $field, $condition, string $logic = 'AND')
1028
    {
1029
        return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true);
1030
    }
1031
1032
    /**
1033
     * 指定NotIn查询条件
1034
     * @access public
1035
     * @param  mixed  $field     查询字段
1036
     * @param  mixed  $condition 查询条件
1037
     * @param  string $logic     查询逻辑 and or xor
1038
     * @return $this
1039
     */
1040
    public function whereNotIn(string $field, $condition, string $logic = 'AND')
1041
    {
1042
        return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true);
1043
    }
1044
1045
    /**
1046
     * 指定Like查询条件
1047
     * @access public
1048
     * @param  mixed  $field     查询字段
1049
     * @param  mixed  $condition 查询条件
1050
     * @param  string $logic     查询逻辑 and or xor
1051
     * @return $this
1052
     */
1053
    public function whereLike(string $field, $condition, string $logic = 'AND')
1054
    {
1055
        return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true);
1056
    }
1057
1058
    /**
1059
     * 指定NotLike查询条件
1060
     * @access public
1061
     * @param  mixed  $field     查询字段
1062
     * @param  mixed  $condition 查询条件
1063
     * @param  string $logic     查询逻辑 and or xor
1064
     * @return $this
1065
     */
1066
    public function whereNotLike(string $field, $condition, string $logic = 'AND')
1067
    {
1068
        return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true);
1069
    }
1070
1071
    /**
1072
     * 指定Between查询条件
1073
     * @access public
1074
     * @param  mixed  $field     查询字段
1075
     * @param  mixed  $condition 查询条件
1076
     * @param  string $logic     查询逻辑 and or xor
1077
     * @return $this
1078
     */
1079
    public function whereBetween(string $field, $condition, string $logic = 'AND')
1080
    {
1081
        return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true);
1082
    }
1083
1084
    /**
1085
     * 指定NotBetween查询条件
1086
     * @access public
1087
     * @param  mixed  $field     查询字段
1088
     * @param  mixed  $condition 查询条件
1089
     * @param  string $logic     查询逻辑 and or xor
1090
     * @return $this
1091
     */
1092
    public function whereNotBetween(string $field, $condition, string $logic = 'AND')
1093
    {
1094
        return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true);
1095
    }
1096
1097
    /**
1098
     * 比较两个字段
1099
     * @access public
1100
     * @param  string $field1     查询字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 5 found
Loading history...
1101
     * @param  string $operator   比较操作符
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
1102
     * @param  string $field2     比较字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 5 found
Loading history...
1103
     * @param  string $logic      查询逻辑 and or xor
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 6 found
Loading history...
1104
     * @return $this
1105
     */
1106
    public function whereColumn(string $field1, string $operator, string $field2 = null, string $logic = 'AND')
1107
    {
1108
        if (is_null($field2)) {
1109
            $field2   = $operator;
1110
            $operator = '=';
1111
        }
1112
1113
        return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true);
1114
    }
1115
1116
    /**
1117
     * 设置软删除字段及条件
1118
     * @access public
1119
     * @param  string       $field     查询字段
1120
     * @param  mixed        $condition 查询条件
1121
     * @return $this
1122
     */
1123
    public function useSoftDelete(string $field, $condition = null)
1124
    {
1125
        if ($field) {
1126
            $this->options['soft_delete'] = [$field, $condition];
1127
        }
1128
1129
        return $this;
1130
    }
1131
1132
    /**
1133
     * 指定Exp查询条件
1134
     * @access public
1135
     * @param  mixed  $field 查询字段
1136
     * @param  string $where 查询条件
1137
     * @param  array  $bind  参数绑定
1138
     * @param  string $logic 查询逻辑 and or xor
1139
     * @return $this
1140
     */
1141
    public function whereExp(string $field, string $where, array $bind = [], string $logic = 'AND')
1142
    {
1143
        if (!empty($bind)) {
1144
            $this->bindParams($where, $bind);
1145
        }
1146
1147
        $this->options['where'][$logic][] = [$field, 'EXP', new Raw($where)];
1148
1149
        return $this;
1150
    }
1151
1152
    /**
1153
     * 指定字段Raw查询
1154
     * @access public
1155
     * @param  string $field     查询字段表达式
1156
     * @param  mixed  $op        查询表达式
1157
     * @param  string $condition 查询条件
1158
     * @param  string $logic     查询逻辑 and or xor
1159
     * @return $this
1160
     */
1161
    public function whereFieldRaw(string $field, $op, $condition = null, string $logic = 'AND')
1162
    {
1163
        if (is_null($condition)) {
1164
            $condition = $op;
1165
            $op        = '=';
1166
        }
1167
1168
        $this->options['where'][$logic][] = [new Raw($field), $op, $condition];
1169
        return $this;
1170
    }
1171
1172
    /**
1173
     * 指定表达式查询条件
1174
     * @access public
1175
     * @param  string $where 查询条件
1176
     * @param  array  $bind  参数绑定
1177
     * @param  string $logic 查询逻辑 and or xor
1178
     * @return $this
1179
     */
1180
    public function whereRaw(string $where, array $bind = [], string $logic = 'AND')
1181
    {
1182
        if (!empty($bind)) {
1183
            $this->bindParams($where, $bind);
1184
        }
1185
1186
        $this->options['where'][$logic][] = new Raw($where);
1187
1188
        return $this;
1189
    }
1190
1191
    /**
1192
     * 指定表达式查询条件 OR
1193
     * @access public
1194
     * @param  string $where 查询条件
1195
     * @param  array  $bind  参数绑定
1196
     * @return $this
1197
     */
1198
    public function whereOrRaw(string $where, array $bind = [])
1199
    {
1200
        return $this->whereRaw($where, $bind, 'OR');
1201
    }
1202
1203
    /**
1204
     * 分析查询表达式
1205
     * @access protected
1206
     * @param  string $logic     查询逻辑 and or xor
1207
     * @param  mixed  $field     查询字段
1208
     * @param  mixed  $op        查询表达式
1209
     * @param  mixed  $condition 查询条件
1210
     * @param  array  $param     查询参数
1211
     * @param  bool   $strict    严格模式
1212
     * @return $this
1213
     */
1214
    protected function parseWhereExp(string $logic, $field, $op, $condition, array $param = [], bool $strict = false)
1215
    {
1216
        $logic = strtoupper($logic);
1217
1218
        if (is_string($field) && !empty($this->options['via']) && false === strpos($field, '.')) {
1219
            $field = $this->options['via'] . '.' . $field;
1220
        }
1221
1222
        if ($field instanceof Raw) {
1223
            return $this->whereRaw($field, is_array($op) ? $op : []);
1224
        } elseif ($strict) {
1225
            // 使用严格模式查询
1226
            if ('=' == $op) {
1227
                $where = $this->whereEq($field, $condition);
1228
            } else {
1229
                $where = [$field, $op, $condition, $logic];
1230
            }
1231
        } elseif (is_array($field)) {
1232
            // 解析数组批量查询
1233
            return $this->parseArrayWhereItems($field, $logic);
1234
        } elseif ($field instanceof Closure) {
1235
            $where = $field;
1236
        } elseif (is_string($field)) {
1237
            if (preg_match('/[,=\<\'\"\(\s]/', $field)) {
1238
                return $this->whereRaw($field, is_array($op) ? $op : []);
1239
            } elseif (is_string($op) && strtolower($op) == 'exp') {
1240
                $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : [];
1241
                return $this->whereExp($field, $condition, $bind, $logic);
1242
            }
1243
1244
            $where = $this->parseWhereItem($logic, $field, $op, $condition, $param);
1245
        }
1246
1247
        if (!empty($where)) {
1248
            $this->options['where'][$logic][] = $where;
1249
        }
1250
1251
        return $this;
1252
    }
1253
1254
    /**
1255
     * 分析查询表达式
1256
     * @access protected
1257
     * @param  string   $logic     查询逻辑 and or xor
1258
     * @param  mixed    $field     查询字段
1259
     * @param  mixed    $op        查询表达式
1260
     * @param  mixed    $condition 查询条件
1261
     * @param  array    $param     查询参数
1262
     * @return array
1263
     */
1264
    protected function parseWhereItem(string $logic, $field, $op, $condition, array $param = []): array
1265
    {
1266
        if (is_array($op)) {
1267
            // 同一字段多条件查询
1268
            array_unshift($param, $field);
1269
            $where = $param;
1270
        } elseif (!is_string($op)) {
1271
            $where = $this->whereEq($field, $op);
1272
        } elseif ($field && is_null($condition)) {
1273
            if (in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) {
1274
                // null查询
1275
                $where = [$field, $op, ''];
1276
            } elseif ('=' == $op) {
1277
                $where = [$field, 'NULL', ''];
1278
            } elseif ('<>' == $op) {
1279
                $where = [$field, 'NOTNULL', ''];
1280
            } else {
1281
                // 字段相等查询
1282
                $where = $this->whereEq($field, $op);
1283
            }
1284
        } elseif (in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) {
1285
            $where = [$field, $op, is_string($condition) ? new Raw($condition) : $condition];
1286
        } else {
1287
            $where = $field ? [$field, $op, $condition, $param[2] ?? null] : [];
1288
        }
1289
1290
        return $where;
1291
    }
1292
1293
    /**
1294
     * 相等查询的主键处理
1295
     * @access protected
1296
     * @param  string $field  字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1297
     * @param  mixed  $value  字段值
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1298
     * @return array
1299
     */
1300
    protected function whereEq(string $field, $value): array
1301
    {
1302
        $where = [$field, '=', $value];
1303
        if ($this->getPk() == $field) {
1304
            $this->options['key'] = $value;
1305
        }
1306
1307
        return $where;
1308
    }
1309
1310
    /**
1311
     * 数组批量查询
1312
     * @access protected
1313
     * @param  array  $field 批量查询
1314
     * @param  string $logic 查询逻辑 and or xor
1315
     * @return $this
1316
     */
1317
    protected function parseArrayWhereItems(array $field, string $logic)
1318
    {
1319
        if (key($field) !== 0) {
1320
            $where = [];
1321
            foreach ($field as $key => $val) {
1322
                if ($val instanceof Raw) {
1323
                    $where[] = [$key, 'exp', $val];
1324
                } else {
1325
                    $where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, is_array($val) ? 'IN' : '=', $val];
1326
                }
1327
            }
1328
        } else {
1329
            // 数组批量查询
1330
            $where = $field;
1331
        }
1332
1333
        if (!empty($where)) {
1334
            $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? array_merge($this->options['where'][$logic], $where) : $where;
1335
        }
1336
1337
        return $this;
1338
    }
1339
1340
    /**
1341
     * 去除某个查询条件
1342
     * @access public
1343
     * @param  string $field 查询字段
1344
     * @param  string $logic 查询逻辑 and or xor
1345
     * @return $this
1346
     */
1347
    public function removeWhereField(string $field, string $logic = 'AND')
1348
    {
1349
        $logic = strtoupper($logic);
1350
1351
        if (isset($this->options['where'][$logic])) {
1352
            foreach ($this->options['where'][$logic] as $key => $val) {
1353
                if (is_array($val) && $val[0] == $field) {
1354
                    unset($this->options['where'][$logic][$key]);
1355
                }
1356
            }
1357
        }
1358
1359
        return $this;
1360
    }
1361
1362
    /**
1363
     * 去除查询参数
1364
     * @access public
1365
     * @param  string $option 参数名 留空去除所有参数
1366
     * @return $this
1367
     */
1368
    public function removeOption(string $option = '')
1369
    {
1370
        if ('' === $option) {
1371
            $this->options = [];
1372
            $this->bind    = [];
1373
        } elseif (isset($this->options[$option])) {
1374
            unset($this->options[$option]);
1375
        }
1376
1377
        return $this;
1378
    }
1379
1380
    /**
1381
     * 条件查询
1382
     * @access public
1383
     * @param  mixed         $condition  满足条件(支持闭包)
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1384
     * @param  Closure|array $query      满足条件后执行的查询表达式(闭包或数组)
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 6 found
Loading history...
1385
     * @param  Closure|array $otherwise  不满足条件后执行
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1386
     * @return $this
1387
     */
1388
    public function when($condition, $query, $otherwise = null)
1389
    {
1390
        if ($condition instanceof Closure) {
1391
            $condition = $condition($this);
1392
        }
1393
1394
        if ($condition) {
1395
            if ($query instanceof Closure) {
1396
                $query($this, $condition);
1397
            } elseif (is_array($query)) {
0 ignored issues
show
introduced by
The condition is_array($query) is always true.
Loading history...
1398
                $this->where($query);
1399
            }
1400
        } elseif ($otherwise) {
1401
            if ($otherwise instanceof Closure) {
1402
                $otherwise($this, $condition);
1403
            } elseif (is_array($otherwise)) {
0 ignored issues
show
introduced by
The condition is_array($otherwise) is always true.
Loading history...
1404
                $this->where($otherwise);
1405
            }
1406
        }
1407
1408
        return $this;
1409
    }
1410
1411
    /**
1412
     * 指定查询数量
1413
     * @access public
1414
     * @param  int $offset 起始位置
1415
     * @param  int $length 查询数量
1416
     * @return $this
1417
     */
1418
    public function limit(int $offset, int $length = null)
1419
    {
1420
        $this->options['limit'] = $offset . ($length ? ',' . $length : '');
1421
1422
        return $this;
1423
    }
1424
1425
    /**
1426
     * 指定分页
1427
     * @access public
1428
     * @param  int $page     页数
1429
     * @param  int $listRows 每页数量
1430
     * @return $this
1431
     */
1432
    public function page(int $page, int $listRows = null)
1433
    {
1434
        $this->options['page'] = [$page, $listRows];
1435
1436
        return $this;
1437
    }
1438
1439
    /**
1440
     * 分页查询
1441
     * @access public
1442
     * @param  int|array $listRows 每页数量 数组表示配置参数
1443
     * @param  int|bool  $simple   是否简洁模式或者总记录数
1444
     * @param  array     $config   配置参数
1445
     * @return \think\Paginator
1446
     * @throws DbException
1447
     */
1448
    public function paginate($listRows = null, $simple = false, $config = [])
1449
    {
1450
        if (is_int($simple)) {
1451
            $total  = $simple;
1452
            $simple = false;
1453
        }
1454
1455
        $paginate = array_merge($this->paginateConfig, Container::pull('config')->get('paginate'));
1456
1457
        if (is_array($listRows)) {
1458
            $config   = array_merge($paginate, $listRows);
1459
            $listRows = intval($config['list_rows']);
1460
        } else {
1461
            $config   = array_merge($paginate, $config);
1462
            $listRows = intval($listRows ?: $config['list_rows']);
1463
        }
1464
1465
        $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']);
1466
        $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...
1467
            $class,
1468
            'getCurrentPage',
1469
        ], $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...
1470
1471
        $page = $page < 1 ? 1 : $page;
1472
1473
        $config['path'] = $config['path'] ?? call_user_func([$class, 'getCurrentPath']);
1474
1475
        if (!isset($total) && !$simple) {
1476
            $options = $this->getOptions();
1477
1478
            unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']);
1479
1480
            $bind    = $this->bind;
1481
            $total   = $this->count();
1482
            $results = $this->options($options)->bind($bind)->page($page, $listRows)->select();
1483
        } elseif ($simple) {
1484
            $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
1485
            $total   = null;
1486
        } else {
1487
            $results = $this->page($page, $listRows)->select();
1488
        }
1489
1490
        $this->removeOption('limit');
1491
        $this->removeOption('page');
1492
1493
        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...
1494
    }
1495
1496
    /**
1497
     * 表达式方式指定当前操作的数据表
1498
     * @access public
1499
     * @param  mixed $table 表名
1500
     * @return $this
1501
     */
1502
    public function tableRaw(string $table)
1503
    {
1504
        $this->options['table'] = new Raw($table);
1505
1506
        return $this;
1507
    }
1508
1509
    /**
1510
     * 指定当前操作的数据表
1511
     * @access public
1512
     * @param  mixed $table 表名
1513
     * @return $this
1514
     */
1515
    public function table($table)
1516
    {
1517
        if (is_string($table)) {
1518
            if (strpos($table, ')')) {
1519
                // 子查询
1520
            } else {
1521
                $tables = explode(',', $table);
1522
                $table  = [];
1523
1524
                foreach ($tables as $item) {
1525
                    $item = trim($item);
1526
                    if (strpos($item, ' ')) {
1527
                        list($item, $alias) = explode(' ', $item);
1528
                        $this->alias([$item => $alias]);
1529
                        $table[$item] = $alias;
1530
                    } else {
1531
                        $table[] = $item;
1532
                    }
1533
                }
1534
            }
1535
        } elseif (is_array($table)) {
1536
            $tables = $table;
1537
            $table  = [];
1538
1539
            foreach ($tables as $key => $val) {
1540
                if (is_numeric($key)) {
1541
                    $table[] = $val;
1542
                } else {
1543
                    $this->alias([$key => $val]);
1544
                    $table[$key] = $val;
1545
                }
1546
            }
1547
        }
1548
1549
        $this->options['table'] = $table;
1550
1551
        return $this;
1552
    }
1553
1554
    /**
1555
     * USING支持 用于多表删除
1556
     * @access public
1557
     * @param  mixed $using USING
1558
     * @return $this
1559
     */
1560
    public function using($using)
1561
    {
1562
        $this->options['using'] = $using;
1563
        return $this;
1564
    }
1565
1566
    /**
1567
     * 存储过程调用
1568
     * @access public
1569
     * @param  bool $procedure 是否为存储过程查询
1570
     * @return $this
1571
     */
1572
    public function procedure($procedure = true)
1573
    {
1574
        $this->options['procedure'] = $procedure;
1575
        return $this;
1576
    }
1577
1578
    /**
1579
     * 是否允许返回空数据(或空模型)
1580
     * @access public
1581
     * @param  bool $allowEmpty 是否允许为空
1582
     * @return $this
1583
     */
1584
    public function allowEmpty(bool $allowEmpty = true)
1585
    {
1586
        $this->options['allow_empty'] = $allowEmpty;
1587
        return $this;
1588
    }
1589
1590
    /**
1591
     * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc'])
1592
     * @access public
1593
     * @param  string|array|Raw $field 排序字段
1594
     * @param  string           $order 排序
1595
     * @return $this
1596
     */
1597
    public function order($field, string $order = '')
1598
    {
1599
        if (empty($field)) {
1600
            return $this;
1601
        } elseif ($field instanceof Raw) {
1602
            $this->options['order'][] = $field;
1603
            return $this;
1604
        }
1605
1606
        if (is_string($field)) {
1607
            if (!empty($this->options['via'])) {
1608
                $field = $this->options['via'] . '.' . $field;
1609
            }
1610
            if (strpos($field, ',')) {
1611
                $field = array_map('trim', explode(',', $field));
1612
            } else {
1613
                $field = empty($order) ? $field : [$field => $order];
1614
            }
1615
        } elseif (!empty($this->options['via'])) {
1616
            foreach ($field as $key => $val) {
1617
                if (is_numeric($key)) {
1618
                    $field[$key] = $this->options['via'] . '.' . $val;
1619
                } else {
1620
                    $field[$this->options['via'] . '.' . $key] = $val;
1621
                    unset($field[$key]);
1622
                }
1623
            }
1624
        }
1625
1626
        if (!isset($this->options['order'])) {
1627
            $this->options['order'] = [];
1628
        }
1629
1630
        if (is_array($field)) {
1631
            $this->options['order'] = array_merge($this->options['order'], $field);
1632
        } else {
1633
            $this->options['order'][] = $field;
1634
        }
1635
1636
        return $this;
1637
    }
1638
1639
    /**
1640
     * 表达式方式指定Field排序
1641
     * @access public
1642
     * @param  string $field 排序字段
1643
     * @param  array  $bind  参数绑定
1644
     * @return $this
1645
     */
1646
    public function orderRaw(string $field, array $bind = [])
1647
    {
1648
        if (!empty($bind)) {
1649
            $this->bindParams($field, $bind);
1650
        }
1651
1652
        $this->options['order'][] = new Raw($field);
1653
1654
        return $this;
1655
    }
1656
1657
    /**
1658
     * 指定Field排序 orderField('id',[1,2,3],'desc')
1659
     * @access public
1660
     * @param  string $field 排序字段
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
1661
     * @param  array  $values 排序值
1662
     * @param  string $order 排序 desc/asc
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
1663
     * @return $this
1664
     */
1665
    public function orderField(string $field, array $values, string $order = '')
1666
    {
1667
        if (!empty($values)) {
1668
            $values['sort'] = $order;
1669
1670
            $this->options['order'][$field] = $values;
1671
        }
1672
1673
        return $this;
1674
    }
1675
1676
    /**
1677
     * 随机排序
1678
     * @access public
1679
     * @return $this
1680
     */
1681
    public function orderRand()
1682
    {
1683
        $this->options['order'][] = '[rand]';
1684
        return $this;
1685
    }
1686
1687
    /**
1688
     * 查询缓存
1689
     * @access public
1690
     * @param  mixed             $key    缓存key
1691
     * @param  integer|\DateTime $expire 缓存有效期
1692
     * @param  string            $tag    缓存标签
1693
     * @return $this
1694
     */
1695
    public function cache($key = true, $expire = null, $tag = null)
1696
    {
1697
        if (false === $key) {
1698
            return $this;
1699
        }
1700
1701
        if ($key instanceof \DateTimeInterface || (is_int($key) && is_null($expire))) {
1702
            $expire = $key;
1703
            $key    = true;
1704
        }
1705
1706
        $this->options['cache'] = [$key, $expire, $tag];
1707
1708
        return $this;
1709
    }
1710
1711
    /**
1712
     * 指定group查询
1713
     * @access public
1714
     * @param  string|array $group GROUP
1715
     * @return $this
1716
     */
1717
    public function group($group)
1718
    {
1719
        $this->options['group'] = $group;
1720
        return $this;
1721
    }
1722
1723
    /**
1724
     * 指定having查询
1725
     * @access public
1726
     * @param  string $having having
1727
     * @return $this
1728
     */
1729
    public function having(string $having)
1730
    {
1731
        $this->options['having'] = $having;
1732
        return $this;
1733
    }
1734
1735
    /**
1736
     * 指定查询lock
1737
     * @access public
1738
     * @param  bool|string $lock 是否lock
1739
     * @return $this
1740
     */
1741
    public function lock($lock = false)
1742
    {
1743
        $this->options['lock'] = $lock;
1744
1745
        if ($lock) {
1746
            $this->options['master'] = true;
1747
        }
1748
1749
        return $this;
1750
    }
1751
1752
    /**
1753
     * 指定distinct查询
1754
     * @access public
1755
     * @param  bool $distinct 是否唯一
1756
     * @return $this
1757
     */
1758
    public function distinct(bool $distinct = true)
1759
    {
1760
        $this->options['distinct'] = $distinct;
1761
        return $this;
1762
    }
1763
1764
    /**
1765
     * 指定数据表别名
1766
     * @access public
1767
     * @param  array|string $alias 数据表别名
1768
     * @return $this
1769
     */
1770
    public function alias($alias)
1771
    {
1772
        if (is_array($alias)) {
1773
            $this->options['alias'] = $alias;
1774
        } else {
1775
            $table = $this->getTable();
1776
1777
            $this->options['alias'][$table] = $alias;
1778
        }
1779
1780
        return $this;
1781
    }
1782
1783
    /**
1784
     * 指定强制索引
1785
     * @access public
1786
     * @param  string $force 索引名称
1787
     * @return $this
1788
     */
1789
    public function force(string $force)
1790
    {
1791
        $this->options['force'] = $force;
1792
        return $this;
1793
    }
1794
1795
    /**
1796
     * 查询注释
1797
     * @access public
1798
     * @param  string $comment 注释
1799
     * @return $this
1800
     */
1801
    public function comment(string $comment)
1802
    {
1803
        $this->options['comment'] = $comment;
1804
        return $this;
1805
    }
1806
1807
    /**
1808
     * 获取执行的SQL语句而不进行实际的查询
1809
     * @access public
1810
     * @param  bool $fetch 是否返回sql
1811
     * @return $this|Fetch
1812
     */
1813
    public function fetchSql(bool $fetch = true)
1814
    {
1815
        $this->options['fetch_sql'] = $fetch;
1816
1817
        if ($fetch) {
1818
            return new Fetch($this);
1819
        }
1820
1821
        return $this;
1822
    }
1823
1824
    /**
1825
     * 设置是否返回数据集对象
1826
     * @access public
1827
     * @param  bool|string $collection 是否返回数据集对象
1828
     * @return $this
1829
     */
1830
    public function fetchCollection($collection = true)
1831
    {
1832
        $this->options['collection'] = $collection;
1833
        return $this;
1834
    }
1835
1836
    /**
1837
     * 设置是否返回数组
1838
     * @access public
1839
     * @param  bool $asArray 是否返回数组
1840
     * @return $this
1841
     */
1842
    public function fetchArray(bool $asArray = true)
1843
    {
1844
        $this->options['array'] = $asArray;
1845
        return $this;
1846
    }
1847
1848
    /**
1849
     * 设置从主服务器读取数据
1850
     * @access public
1851
     * @param  bool $readMaster 是否从主服务器读取
1852
     * @return $this
1853
     */
1854
    public function master(bool $readMaster = true)
1855
    {
1856
        $this->options['master'] = $readMaster;
1857
        return $this;
1858
    }
1859
1860
    /**
1861
     * 设置是否严格检查字段名
1862
     * @access public
1863
     * @param  bool $strict 是否严格检查字段
1864
     * @return $this
1865
     */
1866
    public function strict(bool $strict = true)
1867
    {
1868
        $this->options['strict'] = $strict;
1869
        return $this;
1870
    }
1871
1872
    /**
1873
     * 设置查询数据不存在是否抛出异常
1874
     * @access public
1875
     * @param  bool $fail 数据不存在是否抛出异常
1876
     * @return $this
1877
     */
1878
    public function failException(bool $fail = true)
1879
    {
1880
        $this->options['fail'] = $fail;
1881
        return $this;
1882
    }
1883
1884
    /**
1885
     * 设置自增序列名
1886
     * @access public
1887
     * @param  string $sequence 自增序列名
1888
     * @return $this
1889
     */
1890
    public function sequence(string $sequence = null)
1891
    {
1892
        $this->options['sequence'] = $sequence;
1893
        return $this;
1894
    }
1895
1896
    /**
1897
     * 设置是否REPLACE
1898
     * @access public
1899
     * @param  bool $replace 是否使用REPLACE写入数据
1900
     * @return $this
1901
     */
1902
    public function replace(bool $replace = true)
1903
    {
1904
        $this->options['replace'] = $replace;
1905
        return $this;
1906
    }
1907
1908
    /**
1909
     * 设置当前查询所在的分区
1910
     * @access public
1911
     * @param  string|array $partition 分区名称
1912
     * @return $this
1913
     */
1914
    public function partition($partition)
1915
    {
1916
        $this->options['partition'] = $partition;
1917
        return $this;
1918
    }
1919
1920
    /**
1921
     * 设置DUPLICATE
1922
     * @access public
1923
     * @param  array|string|Raw $duplicate DUPLICATE信息
1924
     * @return $this
1925
     */
1926
    public function duplicate($duplicate)
1927
    {
1928
        $this->options['duplicate'] = $duplicate;
1929
        return $this;
1930
    }
1931
1932
    /**
1933
     * 设置查询的额外参数
1934
     * @access public
1935
     * @param  string $extra 额外信息
1936
     * @return $this
1937
     */
1938
    public function extra(string $extra)
1939
    {
1940
        $this->options['extra'] = $extra;
1941
        return $this;
1942
    }
1943
1944
    /**
1945
     * 设置需要隐藏的输出属性
1946
     * @access public
1947
     * @param  array $hidden 需要隐藏的字段名
1948
     * @return $this
1949
     */
1950
    public function hidden(array $hidden)
1951
    {
1952
        $this->options['hidden'] = $hidden;
1953
        return $this;
1954
    }
1955
1956
    /**
1957
     * 设置需要输出的属性
1958
     * @access public
1959
     * @param  array $visible 需要输出的属性
1960
     * @return $this
1961
     */
1962
    public function visible(array $visible)
1963
    {
1964
        $this->options['visible'] = $visible;
1965
        return $this;
1966
    }
1967
1968
    /**
1969
     * 设置需要追加输出的属性
1970
     * @access public
1971
     * @param  array $append 需要追加的属性
1972
     * @return $this
1973
     */
1974
    public function append(array $append)
1975
    {
1976
        $this->options['append'] = $append;
1977
        return $this;
1978
    }
1979
1980
    /**
1981
     * 设置JSON字段信息
1982
     * @access public
1983
     * @param  array $json JSON字段
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
1984
     * @param  bool  $assoc 是否取出数组
1985
     * @return $this
1986
     */
1987
    public function json(array $json = [], bool $assoc = false)
1988
    {
1989
        $this->options['json']       = $json;
1990
        $this->options['json_assoc'] = $assoc;
1991
        return $this;
1992
    }
1993
1994
    /**
0 ignored issues
show
Coding Style introduced by
Parameter ...$args should have a doc-comment as per coding-style.
Loading history...
1995
     * 添加查询范围
1996
     * @access public
1997
     * @param  array|string|Closure $scope 查询范围定义
1998
     * @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...
1999
     * @return $this
2000
     */
2001
    public function scope($scope, ...$args)
2002
    {
2003
        // 查询范围的第一个参数始终是当前查询对象
2004
        array_unshift($args, $this);
2005
2006
        if ($scope instanceof Closure) {
2007
            call_user_func_array($scope, $args);
2008
            return $this;
2009
        }
2010
2011
        if (is_string($scope)) {
2012
            $scope = explode(',', $scope);
2013
        }
2014
2015
        if ($this->model) {
2016
            // 检查模型类的查询范围方法
2017
            foreach ($scope as $name) {
2018
                $method = 'scope' . trim($name);
2019
2020
                if (method_exists($this->model, $method)) {
2021
                    call_user_func_array([$this->model, $method], $args);
2022
                }
2023
            }
2024
        }
2025
2026
        return $this;
2027
    }
2028
2029
    /**
2030
     * 指定数据表主键
2031
     * @access public
2032
     * @param  string $pk 主键
2033
     * @return $this
2034
     */
2035
    public function pk(string $pk)
2036
    {
2037
        $this->pk = $pk;
2038
        return $this;
2039
    }
2040
2041
    /**
2042
     * 添加日期或者时间查询规则
2043
     * @access public
2044
     * @param  string       $name 时间表达式
2045
     * @param  string|array $rule 时间范围
2046
     * @return $this
2047
     */
2048
    public function timeRule(string $name, $rule)
2049
    {
2050
        $this->timeRule[$name] = $rule;
2051
        return $this;
2052
    }
2053
2054
    /**
2055
     * 查询日期或者时间
2056
     * @access public
2057
     * @param  string       $field 日期字段名
2058
     * @param  string       $op    比较运算符或者表达式
2059
     * @param  string|array $range 比较范围
2060
     * @param  string       $logic AND OR
2061
     * @return $this
2062
     */
2063
    public function whereTime(string $field, string $op, $range = null, string $logic = 'AND')
2064
    {
2065
        if (is_null($range) && isset($this->timeRule[$op])) {
2066
            $range = $this->timeRule[$op];
2067
            $op    = 'between';
2068
        }
2069
2070
        return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true);
2071
    }
2072
2073
    /**
2074
     * 查询某个时间间隔数据
2075
     * @access protected
2076
     * @param  string $field 日期字段名
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2077
     * @param  string $start 开始时间
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2078
     * @param  string $interval 时间间隔单位
2079
     * @param  string $logic AND OR
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2080
     * @return $this
2081
     */
2082
    protected function whereTimeInterval(string $field, string $start, string $interval = 'day', string $logic = 'AND')
2083
    {
2084
        $startTime = strtotime($start);
2085
        $endTime   = strtotime('+1 ' . $interval, $startTime);
2086
2087
        return $this->whereTime($field, 'between', [$startTime, $endTime], $logic);
2088
    }
2089
2090
    /**
2091
     * 查询月数据 whereMonth('time_field', '2018-1')
2092
     * @access public
2093
     * @param  string $field 日期字段名
2094
     * @param  string $month 月份信息
2095
     * @param  string $logic AND OR
2096
     * @return $this
2097
     */
2098
    public function whereMonth(string $field, string $month = 'this month', string $logic = 'AND')
2099
    {
2100
        if (in_array($month, ['this month', 'last month'])) {
2101
            $month = date('Y-m', strtotime($month));
2102
        }
2103
2104
        return $this->whereTimeInterval($field, $month, 'month', $logic);
2105
    }
2106
2107
    /**
2108
     * 查询年数据 whereYear('time_field', '2018')
2109
     * @access public
2110
     * @param  string $field 日期字段名
2111
     * @param  string $year  年份信息
2112
     * @param  string $logic AND OR
2113
     * @return $this
2114
     */
2115
    public function whereYear(string $field, string $year = 'this year', string $logic = 'AND')
2116
    {
2117
        if (in_array($year, ['this year', 'last year'])) {
2118
            $year = date('Y', strtotime($year));
2119
        }
2120
2121
        return $this->whereTimeInterval($field, $year . '-1-1', 'year', $logic);
2122
    }
2123
2124
    /**
2125
     * 查询日数据 whereDay('time_field', '2018-1-1')
2126
     * @access public
2127
     * @param  string $field 日期字段名
2128
     * @param  string $day   日期信息
2129
     * @param  string $logic AND OR
2130
     * @return $this
2131
     */
2132
    public function whereDay(string $field, string $day = 'today', string $logic = 'AND')
2133
    {
2134
        if (in_array($day, ['today', 'yesterday'])) {
2135
            $day = date('Y-m-d', strtotime($day));
2136
        }
2137
2138
        return $this->whereTimeInterval($field, $day, 'day', $logic);
2139
    }
2140
2141
    /**
2142
     * 查询日期或者时间范围 whereBetweenTime('time_field', '2018-1-1','2018-1-15')
2143
     * @access public
2144
     * @param  string     $field 日期字段名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2145
     * @param  string|int $startTime    开始时间
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
2146
     * @param  string|int $endTime 结束时间
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
2147
     * @param  string     $logic AND OR
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2148
     * @return $this
2149
     */
2150
    public function whereBetweenTime(string $field, $startTime, $endTime, string $logic = 'AND')
2151
    {
2152
        return $this->whereTime($field, 'between', [$startTime, $endTime], $logic);
2153
    }
2154
2155
    /**
2156
     * 查询日期或者时间范围 whereNotBetweenTime('time_field', '2018-1-1','2018-1-15')
2157
     * @access public
2158
     * @param  string     $field 日期字段名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2159
     * @param  string|int $startTime    开始时间
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
2160
     * @param  string|int $endTime 结束时间
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
2161
     * @return $this
2162
     */
2163
    public function whereNotBetweenTime(string $field, $startTime, $endTime)
2164
    {
2165
        return $this->whereTime($field, '<', $startTime)
2166
            ->whereTime($field, '>', $endTime);
2167
    }
2168
2169
    /**
2170
     * 查询当前时间在两个时间字段范围 whereBetweenTimeField('start_time', 'end_time')
2171
     * @access public
2172
     * @param  string $startField 开始时间字段
2173
     * @param  string $endField   结束时间字段
2174
     * @return $this
2175
     */
2176
    public function whereBetweenTimeField(string $startField, string $endField)
2177
    {
2178
        return $this->whereTime($startField, '<=', time())
2179
            ->whereTime($endField, '>=', time());
2180
    }
2181
2182
    /**
2183
     * 查询当前时间不在两个时间字段范围 whereNotBetweenTimeField('start_time', 'end_time')
2184
     * @access public
2185
     * @param  string $startField 开始时间字段
2186
     * @param  string $endField   结束时间字段
2187
     * @return $this
2188
     */
2189
    public function whereNotBetweenTimeField(string $startField, string $endField)
2190
    {
2191
        return $this->whereTime($startField, '>', time())
2192
            ->whereTime($endField, '<', time(), 'OR');
2193
    }
2194
2195
    /**
2196
     * 获取当前数据表的主键
2197
     * @access public
2198
     * @return string|array
2199
     */
2200
    public function getPk()
2201
    {
2202
        if (!empty($this->pk)) {
2203
            $pk = $this->pk;
2204
        } else {
2205
            $this->pk = $pk = $this->connection->getPk($this->getTable());
2206
        }
2207
2208
        return $pk;
2209
    }
2210
2211
    /**
2212
     * 批量参数绑定
2213
     * @access public
2214
     * @param  array $value 绑定变量值
2215
     * @return $this
2216
     */
2217
    public function bind(array $value)
2218
    {
2219
        $this->bind = array_merge($this->bind, $value);
2220
        return $this;
2221
    }
2222
2223
    /**
2224
     * 单个参数绑定
2225
     * @access public
2226
     * @param  mixed   $value 绑定变量值
2227
     * @param  integer $type  绑定类型
2228
     * @param  string  $name  绑定标识
2229
     * @return string
2230
     */
2231
    public function bindValue($value, int $type = null, string $name = null)
2232
    {
2233
        $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_';
2234
2235
        $this->bind[$name] = [$value, $type ?: PDO::PARAM_STR];
2236
        return $name;
2237
    }
2238
2239
    /**
2240
     * 检测参数是否已经绑定
2241
     * @access public
2242
     * @param  string $key 参数名
2243
     * @return bool
2244
     */
2245
    public function isBind($key)
2246
    {
2247
        return isset($this->bind[$key]);
2248
    }
2249
2250
    /**
2251
     * 参数绑定
2252
     * @access public
2253
     * @param  string $sql  绑定的sql表达式
2254
     * @param  array  $bind 参数绑定
2255
     * @return void
2256
     */
2257
    protected function bindParams(string &$sql, array $bind = []): void
2258
    {
2259
        foreach ($bind as $key => $value) {
2260
            if (is_array($value)) {
2261
                $name = $this->bindValue($value[0], $value[1], $value[2] ?? null);
2262
            } else {
2263
                $name = $this->bindValue($value);
2264
            }
2265
2266
            if (is_numeric($key)) {
2267
                $sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1);
2268
            } else {
2269
                $sql = str_replace(':' . $key, ':' . $name, $sql);
2270
            }
2271
        }
2272
    }
2273
2274
    /**
2275
     * 查询参数批量赋值
2276
     * @access protected
2277
     * @param  array $options 表达式参数
2278
     * @return $this
2279
     */
2280
    protected function options(array $options)
2281
    {
2282
        $this->options = $options;
2283
        return $this;
2284
    }
2285
2286
    /**
2287
     * 获取当前的查询参数
2288
     * @access public
2289
     * @param  string $name 参数名
2290
     * @return mixed
2291
     */
2292
    public function getOptions(string $name = '')
2293
    {
2294
        if ('' === $name) {
2295
            return $this->options;
2296
        }
2297
2298
        return $this->options[$name] ?? null;
2299
    }
2300
2301
    /**
2302
     * 设置当前的查询参数
2303
     * @access public
2304
     * @param  string $option 参数名
2305
     * @param  mixed  $value  参数值
2306
     * @return $this
2307
     */
2308
    public function setOption(string $option, $value)
2309
    {
2310
        $this->options[$option] = $value;
2311
        return $this;
2312
    }
2313
2314
    /**
2315
     * 设置关联查询
2316
     * @access public
2317
     * @param  array $relation 关联名称
2318
     * @return $this
2319
     */
2320
    public function relation(array $relation)
2321
    {
2322
        if (!empty($relation)) {
2323
            $this->options['relation'] = $relation;
2324
        }
2325
2326
        return $this;
2327
    }
2328
2329
    /**
2330
     * 设置关联查询JOIN预查询
2331
     * @access public
2332
     * @param  array $with 关联方法名称(数组)
2333
     * @return $this
2334
     */
2335
    public function with(array $with)
2336
    {
2337
        if (!empty($with)) {
2338
            $this->options['with'] = $with;
2339
        }
2340
2341
        return $this;
2342
    }
2343
2344
    /**
2345
     * 关联预载入 JOIN方式
2346
     * @access protected
2347
     * @param  array  $with 关联方法名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2348
     * @param  string $joinType JOIN方式
2349
     * @return $this
2350
     */
2351
    public function withJoin(array $with, string $joinType = '')
2352
    {
2353
        if (empty($with)) {
2354
            return $this;
2355
        }
2356
2357
        $first = true;
2358
2359
        /** @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...
2360
        $class = $this->model;
2361
        foreach ($with as $key => $relation) {
2362
            $closure = null;
2363
            $field   = true;
2364
2365
            if ($relation instanceof Closure) {
2366
                // 支持闭包查询过滤关联条件
2367
                $closure  = $relation;
2368
                $relation = $key;
2369
            } elseif (is_array($relation)) {
2370
                $field    = $relation;
2371
                $relation = $key;
2372
            } elseif (is_string($relation) && strpos($relation, '.')) {
2373
                $relation = strstr($relation, '.', true);
2374
            }
2375
2376
            /** @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...
2377
            $relation = App::parseName($relation, 1, false);
2378
            $model    = $class->$relation();
2379
2380
            if ($model instanceof OneToOne) {
2381
                $model->eagerly($this, $relation, $field, $joinType, $closure, $first);
2382
                $first = false;
2383
            } else {
2384
                // 不支持其它关联
2385
                unset($with[$key]);
2386
            }
2387
        }
2388
2389
        $this->via();
2390
2391
        $this->options['with_join'] = $with;
2392
2393
        return $this;
2394
    }
2395
2396
    /**
2397
     * 设置数据字段获取器
2398
     * @access public
2399
     * @param  string   $name     字段名
2400
     * @param  callable $callback 闭包获取器
2401
     * @return $this
2402
     */
2403
    public function withAttr(string $name, callable $callback)
2404
    {
2405
        $this->options['with_attr'][$name] = $callback;
2406
2407
        return $this;
2408
    }
2409
2410
    /**
2411
     * 设置数据字段获取器
2412
     * @access public
2413
     * @param  array $attrs 字段获取器
2414
     * @return $this
2415
     */
2416
    public function withAttrs(array $attrs)
2417
    {
2418
        $this->options['with_attr'] = $attrs;
2419
2420
        return $this;
2421
    }
2422
2423
    /**
2424
     * 使用搜索器条件搜索字段
2425
     * @access public
2426
     * @param  array  $fields 搜索字段
2427
     * @param  array  $data   搜索数据
2428
     * @param  string $prefix 字段前缀标识
2429
     * @return $this
2430
     */
2431
    public function withSearch(array $fields, array $data = [], string $prefix = '')
2432
    {
2433
        foreach ($fields as $key => $field) {
2434
            if ($field instanceof Closure) {
2435
                $field($this, $data[$key] ?? null, $data, $prefix);
2436
            } elseif ($this->model) {
2437
                // 检测搜索器
2438
                $fieldName = is_numeric($key) ? $field : $key;
2439
                $method    = 'search' . App::parseName($fieldName, 1) . 'Attr';
2440
2441
                if (method_exists($this->model, $method)) {
2442
                    $this->model->$method($this, $data[$field] ?? null, $data, $prefix);
2443
                }
2444
            }
2445
        }
2446
2447
        return $this;
2448
    }
2449
2450
    /**
2451
     * 关联统计
2452
     * @access protected
2453
     * @param  array|string $relations 关联方法名
2454
     * @param  string       $aggregate 聚合查询方法
2455
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2456
     * @param  bool         $subQuery 是否使用子查询
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
2457
     * @return $this
2458
     */
2459
    protected function withAggregate($relations, string $aggregate = 'count', $field = '*', bool $subQuery = true)
2460
    {
2461
        if (is_string($relations)) {
2462
            $relations = explode(',', $relations);
2463
        }
2464
2465
        if (!$subQuery) {
2466
            $this->options['with_count'][] = [$relations, $aggregate, $field];
2467
        } else {
2468
            if (!isset($this->options['field'])) {
2469
                $this->field('*');
2470
            }
2471
2472
            foreach ($relations as $key => $relation) {
2473
                $closure = $aggregateField = null;
2474
2475
                if ($relation instanceof Closure) {
2476
                    $closure  = $relation;
2477
                    $relation = $key;
2478
                } elseif (!is_int($key)) {
2479
                    $aggregateField = $relation;
2480
                    $relation       = $key;
2481
                }
2482
2483
                $relation = App::parseName($relation, 1, false);
2484
2485
                $count = '(' . $this->model->$relation()->getRelationCountQuery($closure, $aggregate, $field, $aggregateField) . ')';
2486
2487
                if (empty($aggregateField)) {
2488
                    $aggregateField = App::parseName($relation) . '_' . $aggregate;
2489
                }
2490
2491
                $this->field([$count => $aggregateField]);
2492
            }
2493
        }
2494
2495
        return $this;
2496
    }
2497
2498
    /**
2499
     * 关联统计
2500
     * @access public
2501
     * @param  string|array $relation 关联方法名
2502
     * @param  bool         $subQuery 是否使用子查询
2503
     * @return $this
2504
     */
2505
    public function withCount($relation, bool $subQuery = true)
2506
    {
2507
        return $this->withAggregate($relation, 'count', '*', $subQuery);
2508
    }
2509
2510
    /**
2511
     * 关联统计Sum
2512
     * @access public
2513
     * @param  string|array $relation 关联方法名
2514
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2515
     * @param  bool         $subQuery 是否使用子查询
2516
     * @return $this
2517
     */
2518
    public function withSum($relation, string $field, bool $subQuery = true)
2519
    {
2520
        return $this->withAggregate($relation, 'sum', $field, $subQuery);
2521
    }
2522
2523
    /**
2524
     * 关联统计Max
2525
     * @access public
2526
     * @param  string|array $relation 关联方法名
2527
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2528
     * @param  bool         $subQuery 是否使用子查询
2529
     * @return $this
2530
     */
2531
    public function withMax($relation, string $field, bool $subQuery = true)
2532
    {
2533
        return $this->withAggregate($relation, 'max', $field, $subQuery);
2534
    }
2535
2536
    /**
2537
     * 关联统计Min
2538
     * @access public
2539
     * @param  string|array $relation 关联方法名
2540
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2541
     * @param  bool         $subQuery 是否使用子查询
2542
     * @return $this
2543
     */
2544
    public function withMin($relation, string $field, bool $subQuery = true)
2545
    {
2546
        return $this->withAggregate($relation, 'min', $field, $subQuery);
2547
    }
2548
2549
    /**
2550
     * 关联统计Avg
2551
     * @access public
2552
     * @param  string|array $relation 关联方法名
2553
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2554
     * @param  bool         $subQuery 是否使用子查询
2555
     * @return $this
2556
     */
2557
    public function withAvg($relation, string $field, bool $subQuery = true)
2558
    {
2559
        return $this->withAggregate($relation, 'avg', $field, $subQuery);
2560
    }
2561
2562
    /**
2563
     * 关联预加载中 获取关联指定字段值
2564
     * example:
2565
     * Model::with(['relation' => function($query){
2566
     *     $query->withField("id,name");
2567
     * }])
2568
     *
2569
     * @access public
2570
     * @param  string|array $field 指定获取的字段
2571
     * @return $this
2572
     */
2573
    public function withField($field)
2574
    {
2575
        $this->options['with_field'] = $field;
2576
2577
        return $this;
2578
    }
2579
2580
    /**
2581
     * 设置当前字段添加的表别名
2582
     * @access public
2583
     * @param  string $via 临时表别名
2584
     * @return $this
2585
     */
2586
    public function via(string $via = '')
2587
    {
2588
        $this->options['via'] = $via;
2589
2590
        return $this;
2591
    }
2592
2593
    /**
2594
     * 保存记录 自动判断insert或者update
2595
     * @access public
2596
     * @param  array $data        数据
2597
     * @param  bool  $forceInsert 是否强制insert
2598
     * @return integer
2599
     */
2600
    public function save(array $data = [], bool $forceInsert = false)
2601
    {
2602
        if ($forceInsert) {
2603
            return $this->insert($data);
2604
        }
2605
2606
        if (!empty($data)) {
2607
            $this->options['data'] = array_merge($this->options['data'] ?? [], $data);
2608
        }
2609
2610
        $isUpdate = $this->parseUpdateData($this->options['data']);
2611
2612
        return $isUpdate ? $this->update() : $this->insert();
2613
    }
2614
2615
    /**
2616
     * 插入记录
2617
     * @access public
2618
     * @param  array   $data         数据
2619
     * @param  boolean $getLastInsID 返回自增主键
2620
     * @param  string  $sequence     自增序列名
2621
     * @return integer
2622
     */
2623
    public function insert(array $data = [], bool $getLastInsID = false, string $sequence = null)
2624
    {
2625
        if (!empty($data)) {
2626
            $this->options['data'] = array_merge($this->options['data'] ?? [], $data);
2627
        }
2628
2629
        $replace = $this->options['replace'] ?? false;
2630
        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...
2631
    }
2632
2633
    /**
2634
     * 插入记录并获取自增ID
2635
     * @access public
2636
     * @param  array   $data     数据
2637
     * @param  string  $sequence 自增序列名
2638
     * @return integer|string
2639
     */
2640
    public function insertGetId(array $data, string $sequence = null)
2641
    {
2642
        return $this->insert($data, true, $sequence);
2643
    }
2644
2645
    /**
2646
     * 批量插入记录
2647
     * @access public
2648
     * @param  array   $dataSet 数据集
2649
     * @param  integer $limit   每次写入数据限制
2650
     * @return integer
2651
     */
2652
    public function insertAll(array $dataSet = [], int $limit = 0): int
2653
    {
2654
        if (empty($dataSet)) {
2655
            $dataSet = $this->options['data'] ?? [];
2656
        }
2657
2658
        if (empty($limit) && !empty($this->options['limit']) && is_numeric($this->options['limit'])) {
2659
            $limit = (int) $this->options['limit'];
2660
        }
2661
2662
        $replace = $this->options['replace'] ?? false;
2663
        return $this->connection->insertAll($this, $dataSet, $replace, $limit);
2664
    }
2665
2666
    /**
2667
     * 通过Select方式插入记录
2668
     * @access public
2669
     * @param  array  $fields 要插入的数据表字段名
2670
     * @param  string $table  要插入的数据表名
2671
     * @return integer
2672
     * @throws PDOException
2673
     */
2674
    public function selectInsert(array $fields, string $table): int
2675
    {
2676
        return $this->connection->selectInsert($this, $fields, $table);
2677
    }
2678
2679
    /**
2680
     * 更新记录
2681
     * @access public
2682
     * @param  mixed $data 数据
2683
     * @return integer
2684
     * @throws Exception
2685
     * @throws PDOException
2686
     */
2687
    public function update(array $data = []): int
2688
    {
2689
        if (!empty($data)) {
2690
            $data = array_merge($this->options['data'] ?? [], $data);
2691
        }
2692
2693
        if (empty($this->options['where'])) {
2694
            $this->parseUpdateData($data);
2695
        }
2696
2697
        if (empty($this->options['where']) && $this->model) {
2698
            $this->where($this->model->getWhere());
2699
        }
2700
2701
        if (empty($this->options['where'])) {
2702
            // 如果没有任何更新条件则不执行
2703
            throw new Exception('miss update condition');
2704
        }
2705
2706
        $this->options['data'] = $data;
2707
2708
        return $this->connection->update($this);
2709
    }
2710
2711
    /**
2712
     * 删除记录
2713
     * @access public
2714
     * @param  mixed $data 表达式 true 表示强制删除
2715
     * @return int
2716
     * @throws Exception
2717
     * @throws PDOException
2718
     */
2719
    public function delete($data = null): int
2720
    {
2721
        if (!is_null($data) && true !== $data) {
2722
            // AR模式分析主键条件
2723
            $this->parsePkWhere($data);
2724
        }
2725
2726
        if (empty($this->options['where']) && $this->model) {
2727
            $this->where($this->model->getWhere());
2728
        }
2729
2730
        if (true !== $data && empty($this->options['where'])) {
2731
            // 如果条件为空 不进行删除操作 除非设置 1=1
2732
            throw new Exception('delete without condition');
2733
        }
2734
2735
        if (!empty($this->options['soft_delete'])) {
2736
            // 软删除
2737
            list($field, $condition) = $this->options['soft_delete'];
2738
            if ($condition) {
2739
                unset($this->options['soft_delete']);
2740
                $this->options['data'] = [$field => $condition];
2741
2742
                return $this->connection->update($this);
2743
            }
2744
        }
2745
2746
        $this->options['data'] = $data;
2747
2748
        return $this->connection->delete($this);
2749
    }
2750
2751
    /**
2752
     * 执行查询但只返回PDOStatement对象
2753
     * @access public
2754
     * @return PDOStatement
2755
     */
2756
    public function getPdo(): PDOStatement
2757
    {
2758
        return $this->connection->pdo($this);
2759
    }
2760
2761
    /**
2762
     * 使用游标查找记录
2763
     * @access public
2764
     * @param  mixed $data 数据
2765
     * @return \Generator
2766
     */
2767
    public function cursor($data = null)
2768
    {
2769
        if (!is_null($data)) {
2770
            // 主键条件分析
2771
            $this->parsePkWhere($data);
2772
        }
2773
2774
        $this->options['data'] = $data;
2775
2776
        $connection = clone $this->connection;
2777
2778
        return $connection->cursor($this);
2779
    }
2780
2781
    /**
2782
     * 查找记录
2783
     * @access public
2784
     * @param  mixed $data 数据
2785
     * @return Collection|array|ModelCollection
2786
     * @throws DbException
2787
     * @throws ModelNotFoundException
2788
     * @throws DataNotFoundException
2789
     */
2790
    public function select($data = null)
2791
    {
2792
        if (!is_null($data)) {
2793
            // 主键条件分析
2794
            $this->parsePkWhere($data);
2795
        }
2796
2797
        $resultSet = $this->connection->select($this);
2798
2799
        // 返回结果处理
2800
        if (!empty($this->options['fail']) && count($resultSet) == 0) {
2801
            $this->throwNotFound();
2802
        }
2803
2804
        // 数据列表读取后的处理
2805
        if (!empty($this->model) && empty($this->options['array'])) {
2806
            // 生成模型对象
2807
            $resultSet = $this->resultSetToModelCollection($resultSet);
2808
        } else {
2809
            $this->resultSet($resultSet);
2810
        }
2811
2812
        return $resultSet;
2813
    }
2814
2815
    /**
2816
     * 查询数据转换为模型数据集对象
2817
     * @access protected
2818
     * @param  array $resultSet 数据集
2819
     * @return ModelCollection
2820
     */
2821
    protected function resultSetToModelCollection(array $resultSet): ModelCollection
2822
    {
2823
        if (!empty($this->options['collection']) && is_string($this->options['collection'])) {
2824
            $collection = $this->options['collection'];
2825
        }
2826
2827
        if (empty($resultSet)) {
2828
            return $this->model->toCollection([], $collection ?? null);
2829
        }
2830
2831
        // 检查动态获取器
2832
        if (!empty($this->options['with_attr'])) {
2833
            foreach ($this->options['with_attr'] as $name => $val) {
2834
                if (strpos($name, '.')) {
2835
                    list($relation, $field) = explode('.', $name);
2836
2837
                    $withRelationAttr[$relation][$field] = $val;
2838
                    unset($this->options['with_attr'][$name]);
2839
                }
2840
            }
2841
        }
2842
2843
        $withRelationAttr = $withRelationAttr ?? [];
2844
2845
        foreach ($resultSet as $key => &$result) {
2846
            // 数据转换为模型对象
2847
            $this->resultToModel($result, $this->options, true, $withRelationAttr);
2848
        }
2849
2850
        if (!empty($this->options['with'])) {
2851
            // 预载入
2852
            $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 2845. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2853
        }
2854
2855
        if (!empty($this->options['with_join'])) {
2856
            // 预载入
2857
            $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, true);
2858
        }
2859
2860
        // 模型数据集转换
2861
        return $this->model->toCollection($resultSet, $collection ?? null);
2862
    }
2863
2864
    /**
2865
     * 处理数据集
2866
     * @access public
2867
     * @param  array $resultSet 数据集
2868
     * @return void
2869
     */
2870
    protected function resultSet(array &$resultSet): void
2871
    {
2872
        if (!empty($this->options['json'])) {
2873
            foreach ($resultSet as &$result) {
2874
                $this->jsonResult($result, $this->options['json'], true);
2875
            }
2876
        }
2877
2878
        if (!empty($this->options['with_attr'])) {
2879
            foreach ($resultSet as &$result) {
2880
                $this->getResultAttr($result, $this->options['with_attr']);
2881
            }
2882
        }
2883
2884
        if (!empty($this->options['visible']) || !empty($this->options['hidden'])) {
2885
            foreach ($resultSet as &$result) {
2886
                $this->filterResult($result);
2887
            }
2888
        }
2889
2890
        if (!empty($this->options['collection'])) {
2891
            // 返回Collection对象
2892
            $resultSet = new Collection($resultSet);
2893
        }
2894
    }
2895
2896
    /**
2897
     * 查找单条记录
2898
     * @access public
2899
     * @param  mixed $data 查询数据
2900
     * @return array|Model|null
2901
     * @throws DbException
2902
     * @throws ModelNotFoundException
2903
     * @throws DataNotFoundException
2904
     */
2905
    public function find($data = null)
2906
    {
2907
        if (!is_null($data)) {
2908
            // AR模式分析主键条件
2909
            $this->parsePkWhere($data);
2910
        }
2911
2912
        $result = $this->connection->find($this);
2913
2914
        // 数据处理
2915
        if (empty($result)) {
2916
            return $this->resultToEmpty();
2917
        }
2918
2919
        if (!empty($this->model) && empty($this->options['array'])) {
2920
            // 返回模型对象
2921
            $this->resultToModel($result, $this->options);
2922
        } else {
2923
            $this->result($result);
2924
        }
2925
2926
        return $result;
2927
    }
2928
2929
    /**
2930
     * 查找单条记录 不存在返回空数据(或者空模型)
2931
     * @access public
2932
     * @param  mixed $data 数据
2933
     * @return array|Model
2934
     */
2935
    public function findOrEmpty($data = null)
2936
    {
2937
        return $this->allowEmpty(true)->find($data);
2938
    }
2939
2940
    /**
2941
     * 处理空数据
2942
     * @access protected
2943
     * @return array|Model|null
2944
     * @throws DbException
2945
     * @throws ModelNotFoundException
2946
     * @throws DataNotFoundException
2947
     */
2948
    protected function resultToEmpty()
2949
    {
2950
        if (!empty($this->options['fail'])) {
2951
            $this->throwNotFound();
2952
        } elseif (!empty($this->options['allow_empty'])) {
2953
            return !empty($this->model) && empty($this->options['array']) ? $this->model->newInstance([], true) : [];
2954
        }
2955
    }
2956
2957
    /**
2958
     * 获取模型的更新条件
2959
     * @access protected
2960
     * @param  array $options 查询参数
2961
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
2962
    protected function getModelUpdateCondition(array $options)
2963
    {
2964
        return $options['where']['AND'] ?? null;
2965
    }
2966
2967
    /**
2968
     * 处理数据
2969
     * @access protected
2970
     * @param  array $result 查询数据
2971
     * @return void
2972
     */
2973
    protected function result(array &$result): void
2974
    {
2975
        if (!empty($this->options['json'])) {
2976
            $this->jsonResult($result, $this->options['json'], true);
2977
        }
2978
2979
        if (!empty($this->options['with_attr'])) {
2980
            $this->getResultAttr($result, $this->options['with_attr']);
2981
        }
2982
2983
        $this->filterResult($result);
2984
    }
2985
2986
    /**
2987
     * 处理数据的可见和隐藏
2988
     * @access protected
2989
     * @param  array $result 查询数据
2990
     * @return void
2991
     */
2992
    protected function filterResult(&$result): void
2993
    {
2994
        if (!empty($this->options['visible'])) {
2995
            foreach ($this->options['visible'] as $key) {
2996
                $array[] = $key;
2997
            }
2998
            $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 2995. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2999
        } elseif (!empty($this->options['hidden'])) {
3000
            foreach ($this->options['hidden'] as $key) {
3001
                $array[] = $key;
3002
            }
3003
            $result = array_diff_key($result, array_flip($array));
3004
        }
3005
    }
3006
3007
    /**
3008
     * 使用获取器处理数据
3009
     * @access protected
3010
     * @param  array $result   查询数据
3011
     * @param  array $withAttr 字段获取器
3012
     * @return void
3013
     */
3014
    protected function getResultAttr(array &$result, array $withAttr = []): void
3015
    {
3016
        foreach ($withAttr as $name => $closure) {
3017
            $name = App::parseName($name);
3018
3019
            if (strpos($name, '.')) {
3020
                // 支持JSON字段 获取器定义
3021
                list($key, $field) = explode('.', $name);
3022
3023
                if (isset($result[$key])) {
3024
                    $result[$key][$field] = $closure($result[$key][$field] ?? null, $result[$key]);
3025
                }
3026
            } else {
3027
                $result[$name] = $closure($result[$name] ?? null, $result);
3028
            }
3029
        }
3030
    }
3031
3032
    /**
3033
     * JSON字段数据转换
3034
     * @access protected
3035
     * @param  array $result           查询数据
3036
     * @param  array $json             JSON字段
3037
     * @param  bool  $assoc            是否转换为数组
3038
     * @param  array $withRelationAttr 关联获取器
3039
     * @return void
3040
     */
3041
    protected function jsonResult(array &$result, array $json = [], bool $assoc = false, array $withRelationAttr = []): void
3042
    {
3043
        foreach ($json as $name) {
3044
            if (!isset($result[$name])) {
3045
                continue;
3046
            }
3047
3048
            $result[$name] = json_decode($result[$name], $assoc);
3049
3050
            if (!isset($withRelationAttr[$name])) {
3051
                continue;
3052
            }
3053
3054
            foreach ($withRelationAttr[$name] as $key => $closure) {
3055
                $data = get_object_vars($result[$name]);
3056
3057
                $result[$name]->$key = $closure($result[$name]->$key ?? null, $data);
3058
            }
3059
        }
3060
    }
3061
3062
    /**
3063
     * 查询数据转换为模型对象
3064
     * @access protected
3065
     * @param  array $result           查询数据
3066
     * @param  array $options          查询参数
3067
     * @param  bool  $resultSet        是否为数据集查询
3068
     * @param  array $withRelationAttr 关联字段获取器
3069
     * @return void
3070
     */
3071
    protected function resultToModel(array &$result, array $options = [], bool $resultSet = false, array $withRelationAttr = []): void
3072
    {
3073
        // 动态获取器
3074
        if (!empty($options['with_attr']) && empty($withRelationAttr)) {
3075
            foreach ($options['with_attr'] as $name => $val) {
3076
                if (strpos($name, '.')) {
3077
                    list($relation, $field) = explode('.', $name);
3078
3079
                    $withRelationAttr[$relation][$field] = $val;
3080
                    unset($options['with_attr'][$name]);
3081
                }
3082
            }
3083
        }
3084
3085
        // JSON 数据处理
3086
        if (!empty($options['json'])) {
3087
            $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr);
3088
        }
3089
3090
        $result = $this->model->newInstance($result, true, $resultSet ? null : $this->getModelUpdateCondition($options));
3091
3092
        // 动态获取器
3093
        if (!empty($options['with_attr'])) {
3094
            $result->withAttribute($options['with_attr']);
3095
        }
3096
3097
        // 输出属性控制
3098
        if (!empty($options['visible'])) {
3099
            $result->visible($options['visible']);
3100
        } elseif (!empty($options['hidden'])) {
3101
            $result->hidden($options['hidden']);
3102
        }
3103
3104
        if (!empty($options['append'])) {
3105
            $result->append($options['append']);
3106
        }
3107
3108
        // 关联查询
3109
        if (!empty($options['relation'])) {
3110
            $result->relationQuery($options['relation']);
3111
        }
3112
3113
        // 预载入查询
3114
        if (!$resultSet && !empty($options['with'])) {
3115
            $result->eagerlyResult($result, $options['with'], $withRelationAttr);
3116
        }
3117
3118
        // JOIN预载入查询
3119
        if (!$resultSet && !empty($options['with_join'])) {
3120
            $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, true);
3121
        }
3122
3123
        // 关联统计
3124
        if (!empty($options['with_count'])) {
3125
            foreach ($options['with_count'] as $val) {
3126
                $result->relationCount($result, $val[0], $val[1], $val[2]);
3127
            }
3128
        }
3129
    }
3130
3131
    /**
3132
     * 查询失败 抛出异常
3133
     * @access protected
3134
     * @return void
3135
     * @throws ModelNotFoundException
3136
     * @throws DataNotFoundException
3137
     */
3138
    protected function throwNotFound(): void
3139
    {
3140
        if (!empty($this->model)) {
3141
            $class = get_class($this->model);
3142
            throw new ModelNotFoundException('model data Not Found:' . $class, $class, $this->options);
3143
        }
3144
3145
        $table = $this->getTable();
3146
        throw new DataNotFoundException('table data not Found:' . $table, $table, $this->options);
3147
    }
3148
3149
    /**
3150
     * 查找多条记录 如果不存在则抛出异常
3151
     * @access public
3152
     * @param  array|string|Query|Closure $data 数据
3153
     * @return array|PDOStatement|string|Model
3154
     * @throws DbException
3155
     * @throws ModelNotFoundException
3156
     * @throws DataNotFoundException
3157
     */
3158
    public function selectOrFail($data = null)
3159
    {
3160
        return $this->failException(true)->select($data);
3161
    }
3162
3163
    /**
3164
     * 查找单条记录 如果不存在则抛出异常
3165
     * @access public
3166
     * @param  array|string|Query|Closure $data 数据
3167
     * @return array|PDOStatement|string|Model
3168
     * @throws DbException
3169
     * @throws ModelNotFoundException
3170
     * @throws DataNotFoundException
3171
     */
3172
    public function findOrFail($data = null)
3173
    {
3174
        return $this->failException(true)->find($data);
3175
    }
3176
3177
    /**
3178
     * 分批数据返回处理
3179
     * @access public
3180
     * @param  integer      $count    每次处理的数据数量
3181
     * @param  callable     $callback 处理回调方法
3182
     * @param  string|array $column   分批处理的字段名
3183
     * @param  string       $order    字段排序
3184
     * @return bool
3185
     * @throws DbException
3186
     */
3187
    public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool
3188
    {
3189
        $options = $this->getOptions();
3190
        $column  = $column ?: $this->getPk();
3191
3192
        if (isset($options['order'])) {
3193
            if (Container::pull('app')->isDebug()) {
3194
                throw new DbException('chunk not support call order');
3195
            }
3196
            unset($options['order']);
3197
        }
3198
3199
        $bind = $this->bind;
3200
3201
        if (is_array($column)) {
3202
            $times = 1;
3203
            $query = $this->options($options)->page($times, $count);
3204
        } else {
3205
            $query = $this->options($options)->limit($count);
3206
3207
            if (strpos($column, '.')) {
3208
                list($alias, $key) = explode('.', $column);
3209
            } else {
3210
                $key = $column;
3211
            }
3212
        }
3213
3214
        $resultSet = $query->order($column, $order)->select();
3215
3216
        while (count($resultSet) > 0) {
3217
            if ($resultSet instanceof Collection) {
3218
                $resultSet = $resultSet->all();
3219
            }
3220
3221
            if (false === call_user_func($callback, $resultSet)) {
3222
                return false;
3223
            }
3224
3225
            if (isset($times)) {
3226
                $times++;
3227
                $query = $this->options($options)->page($times, $count);
3228
            } else {
3229
                $end    = end($resultSet);
3230
                $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...
3231
3232
                $query = $this->options($options)
3233
                    ->limit($count)
3234
                    ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
3235
            }
3236
3237
            $resultSet = $query->bind($bind)->order($column, $order)->select();
3238
        }
3239
3240
        return true;
3241
    }
3242
3243
    /**
3244
     * 获取绑定的参数 并清空
3245
     * @access public
3246
     * @param  bool $clear 是否清空绑定数据
3247
     * @return array
3248
     */
3249
    public function getBind(bool $clear = true): array
3250
    {
3251
        $bind = $this->bind;
3252
        if ($clear) {
3253
            $this->bind = [];
3254
        }
3255
3256
        return $bind;
3257
    }
3258
3259
    /**
3260
     * 创建子查询SQL
3261
     * @access public
3262
     * @param  bool $sub 是否添加括号
3263
     * @return string
3264
     * @throws DbException
3265
     */
3266
    public function buildSql(bool $sub = true): string
3267
    {
3268
        return $sub ? '( ' . $this->fetchSql()->select() . ' )' : $this->fetchSql()->select();
3269
    }
3270
3271
    /**
3272
     * 视图查询处理
3273
     * @access protected
3274
     * @param  array $options 查询参数
3275
     * @return void
3276
     */
3277
    protected function parseView(array &$options): void
3278
    {
3279
        foreach (['AND', 'OR'] as $logic) {
3280
            if (isset($options['where'][$logic])) {
3281
                foreach ($options['where'][$logic] as $key => $val) {
3282
                    if (array_key_exists($key, $options['map'])) {
3283
                        array_shift($val);
3284
                        array_unshift($val, $options['map'][$key]);
3285
                        $options['where'][$logic][$options['map'][$key]] = $val;
3286
                        unset($options['where'][$logic][$key]);
3287
                    }
3288
                }
3289
            }
3290
        }
3291
3292
        if (isset($options['order'])) {
3293
            // 视图查询排序处理
3294
            foreach ($options['order'] as $key => $val) {
3295
                if (is_numeric($key) && is_string($val)) {
3296
                    if (strpos($val, ' ')) {
3297
                        list($field, $sort) = explode(' ', $val);
3298
                        if (array_key_exists($field, $options['map'])) {
3299
                            $options['order'][$options['map'][$field]] = $sort;
3300
                            unset($options['order'][$key]);
3301
                        }
3302
                    } elseif (array_key_exists($val, $options['map'])) {
3303
                        $options['order'][$options['map'][$val]] = 'asc';
3304
                        unset($options['order'][$key]);
3305
                    }
3306
                } elseif (array_key_exists($key, $options['map'])) {
3307
                    $options['order'][$options['map'][$key]] = $val;
3308
                    unset($options['order'][$key]);
3309
                }
3310
            }
3311
        }
3312
    }
3313
3314
    /**
3315
     * 分析数据是否存在更新条件
3316
     * @access public
3317
     * @param  array $data 数据
3318
     * @return bool
3319
     * @throws Exception
3320
     */
3321
    public function parseUpdateData(&$data): bool
3322
    {
3323
        $pk       = $this->getPk();
3324
        $isUpdate = false;
3325
        // 如果存在主键数据 则自动作为更新条件
3326
        if (is_string($pk) && isset($data[$pk])) {
3327
            $this->where($pk, '=', $data[$pk]);
3328
            $this->options['key'] = $data[$pk];
3329
            unset($data[$pk]);
3330
            $isUpdate = true;
3331
        } elseif (is_array($pk)) {
3332
            // 增加复合主键支持
3333
            foreach ($pk as $field) {
3334
                if (isset($data[$field])) {
3335
                    $this->where($field, '=', $data[$field]);
3336
                    $isUpdate = true;
3337
                } else {
3338
                    // 如果缺少复合主键数据则不执行
3339
                    throw new Exception('miss complex primary data');
3340
                }
3341
                unset($data[$field]);
3342
            }
3343
        }
3344
3345
        return $isUpdate;
3346
    }
3347
3348
    /**
3349
     * 把主键值转换为查询条件 支持复合主键
3350
     * @access public
3351
     * @param  array|string $data 主键数据
3352
     * @return void
3353
     * @throws Exception
3354
     */
3355
    public function parsePkWhere($data): void
3356
    {
3357
        $pk = $this->getPk();
3358
3359
        if (is_string($pk)) {
3360
            // 获取数据表
3361
            if (empty($this->options['table'])) {
3362
                $this->options['table'] = $this->getTable();
3363
            }
3364
3365
            $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table'];
3366
3367
            if (!empty($this->options['alias'][$table])) {
3368
                $alias = $this->options['alias'][$table];
3369
            }
3370
3371
            $key = isset($alias) ? $alias . '.' . $pk : $pk;
3372
            // 根据主键查询
3373
            if (is_array($data)) {
3374
                $this->where($key, 'in', $data);
3375
            } else {
3376
                $this->where($key, '=', $data);
3377
                $this->options['key'] = $data;
3378
            }
3379
        }
3380
    }
3381
3382
    /**
3383
     * 分析表达式(可用于查询或者写入操作)
3384
     * @access public
3385
     * @return array
3386
     */
3387
    public function parseOptions(): array
3388
    {
3389
        $options = $this->getOptions();
3390
3391
        // 获取数据表
3392
        if (empty($options['table'])) {
3393
            $options['table'] = $this->getTable();
3394
        }
3395
3396
        if (!isset($options['where'])) {
3397
            $options['where'] = [];
3398
        } elseif (isset($options['view'])) {
3399
            // 视图查询条件处理
3400
            $this->parseView($options);
3401
        }
3402
3403
        if (!isset($options['field'])) {
3404
            $options['field'] = '*';
3405
        }
3406
3407
        foreach (['data', 'order', 'join', 'union'] as $name) {
3408
            if (!isset($options[$name])) {
3409
                $options[$name] = [];
3410
            }
3411
        }
3412
3413
        if (!isset($options['strict'])) {
3414
            $options['strict'] = $this->connection->getConfig('fields_strict');
3415
        }
3416
3417
        foreach (['master', 'lock', 'fetch_sql', 'array', 'distinct', 'procedure'] as $name) {
3418
            if (!isset($options[$name])) {
3419
                $options[$name] = false;
3420
            }
3421
        }
3422
3423
        foreach (['group', 'having', 'limit', 'force', 'comment', 'partition', 'duplicate', 'extra'] as $name) {
3424
            if (!isset($options[$name])) {
3425
                $options[$name] = '';
3426
            }
3427
        }
3428
3429
        if (isset($options['page'])) {
3430
            // 根据页数计算limit
3431
            list($page, $listRows) = $options['page'];
3432
            $page                  = $page > 0 ? $page : 1;
3433
            $listRows              = $listRows ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20);
3434
            $offset                = $listRows * ($page - 1);
3435
            $options['limit']      = $offset . ',' . $listRows;
3436
        }
3437
3438
        $this->options = $options;
3439
3440
        return $options;
3441
    }
3442
3443
    /**
3444
     * 注册回调方法
3445
     * @access public
3446
     * @param  string   $event    事件名
3447
     * @param  callable $callback 回调方法
3448
     * @return void
3449
     */
3450
    public static function event(string $event, callable $callback): void
3451
    {
3452
        self::$event[$event] = $callback;
3453
    }
3454
3455
    /**
3456
     * 触发事件
3457
     * @access public
3458
     * @param  string $event 事件名
3459
     * @return mixed
3460
     */
3461
    public function trigger(string $event)
3462
    {
3463
        $result = false;
3464
3465
        if (isset(self::$event[$event])) {
3466
            $result = Container::getInstance()->invoke(self::$event[$event], [$this]);
3467
        }
3468
3469
        return $result;
3470
    }
3471
3472
}
3473